Generate and maintain Gitea Actions CI/CD pipelines that build & publish Docker images (applications-backend/*, applications-frontend/*) and OCI Helm charts (helm-charts/*) from a pnpm + Turborepo monorepo to a Harbor registry, authenticated by a project robot account via Gitea org secret + global vars. - SKILL.md: auth model, convention-driven discovery, publish gating, gotchas - references/harbor-auth.md: docker/helm login, robot accounts, secret/var taxonomy, bake-into-runner alternative, troubleshooting - references/pipeline-workflow.md: jobs, discovery loops, tagging, turbo prune - references/conventions.md: Dockerfile/chart locations, image & chart naming - assets/workflows/ci-cd.yaml: ready-to-drop .gitea/workflows pipeline Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
9.7 KiB
name, description, license, metadata, compatibility
| name | description | license | metadata | compatibility | ||||||
|---|---|---|---|---|---|---|---|---|---|---|
| devsecops-ci-cd-gitea | Generate and maintain Gitea Actions CI/CD pipelines that build & publish Docker images and Helm charts for every application in a pnpm + Turborepo monorepo, pushing both to a Harbor registry. Use when adding or fixing a `.gitea/workflows/*` pipeline; building or pushing container images from `applications-backend/<svc>/Dockerfile` or `applications-frontend/<app>/Dockerfile`; packaging and pushing OCI Helm charts from `helm-charts/<chart>/`; wiring `docker login` / `helm registry login` against Harbor with a robot account; setting the Gitea Actions secrets and variables a pipeline needs; or debugging a runner that can't authenticate, can't find Docker, or can't reach an action — even when the user only says "the CI/CD thing", "the docker pipeline", or names their monorepo. | Proprietary |
|
Targets Gitea Actions (act_runner, GitHub-Actions-compatible) pushing to a Harbor OCI registry, for pnpm + Turborepo monorepos (see the development-monorepo-pnpm skill). Requires a runner whose jobs can run docker and helm. Designed for Claude Code, Cline, GitHub Copilot, OpenAI Codex, and other compatible agents. |
DevSecOps CI/CD — Gitea Actions → Harbor
Generate the pipeline that turns a monorepo push into published images and charts: for
every app that has a Dockerfile, build and push its image; for every chart under
helm-charts/, package and push it as an OCI artifact. Both land in Harbor, authenticated
by a project robot account whose credential lives in Gitea Actions secrets/variables.
This skill owns the pipeline up to "the artifact is in Harbor". Deploying that artifact to the cluster is the development-gitops-argo-cd skill's job — see Handoff.
When to use — and which reference to open
| The task | Read |
|---|---|
Wire or fix registry login (docker login / helm registry login), robot accounts, the Gitea secret/variable names, or the "bake into the runner" alternative |
references/harbor-auth.md |
Understand or edit the pipeline itself — jobs, discovery loops, tag derivation, PR-vs-publish gating, turbo prune |
references/pipeline-workflow.md |
| Confirm where Dockerfiles and charts live, how image/chart names are derived, which Harbor project to push to | references/conventions.md |
| Drop a working pipeline into a repo | copy assets/workflows/ci-cd.yaml → <repo>/.gitea/workflows/ci-cd.yaml |
The mental model
git push ─▶ act_runner ─▶ [ discover apps ]
├─ applications-backend/<svc>/Dockerfile ─▶ docker build (repo root) ─▶ push harbor/<project>/<svc>:<tag>
├─ applications-frontend/<app>/Dockerfile ─▶ docker build (repo root) ─▶ push harbor/<project>/<app>:<tag>
└─ helm-charts/<chart>/Chart.yaml ─▶ helm package ─▶ push oci://harbor/<project>/charts
Everything the runner needs to authenticate comes from one org secret + two global variables (below). Nothing sensitive is committed to the repo.
1. Registry authentication (the "docker thing")
Harbor push/pull uses a project-scoped robot account — a non-human account with a long
username and a generated secret. The pipeline logs in with it; it never uses a person's
credentials. Full model and how the robot is created (palantir harbor:robot:* tasks) →
references/harbor-auth.md.
The pipeline reads exactly three values:
| Kind | Name | Scope in Gitea | Example value |
|---|---|---|---|
| Variable | HL_V1_HARBOR_ADDRESS |
global (Site Admin → Actions → Variables) | harbor-v1.apps.lego-cloud.eu |
| Variable | HL_V1_HARBOR_ROBOT_GITEA_ACTIONS_V1_USERNAME |
global | robot$gondor-v1+gitea-actions-v1 |
| Secret | HL_V1_HARBOR_ROBOT_GITEA_ACTIONS_V1_SECRET |
org (per org, Settings → Actions → Secrets) | (the robot's generated secret) |
Why this split: the username and address are not secret, so they go in variables — and variables have a global level, so you set them once for all organisations. Gitea has no global-secret level, so the one genuinely sensitive value is an org secret — the single per-org chore when you onboard a new org.
Login, in every job that touches the registry:
echo "${HARBOR_SECRET}" | docker login "${HARBOR_ADDRESS}" -u "${HARBOR_USERNAME}" --password-stdin
echo "${HARBOR_SECRET}" | helm registry login "${HARBOR_ADDRESS}" -u "${HARBOR_USERNAME}" --password-stdin
Alternative — bake the login into the runner. If you would rather not attach a secret per org at all, pre-seed the runner's Docker
config.jsonso every job is pre-authenticated. It trades central rotation for zero per-org setup. Steps inreferences/harbor-auth.md§4.
2. What gets built, and from where
Discovery is convention-driven — the pipeline finds work, you don't list it. Details and
the naming rules in references/conventions.md; the essentials:
- Images: one per
applications-backend/*/Dockerfileandapplications-frontend/*/Dockerfile. The image repository is the application directory name. Build context is the repository root (docker build -f <path>/Dockerfile … .) because the Dockerfile runsturbo pruneover the whole monorepo — building from the app directory will fail. - Charts: one per
helm-charts/*/Chart.yaml. Charts are centralized underhelm-charts/at the repo root, not co-located inside the app directory. Do not look forapplications-frontend/<app>/helm— that is not where charts live in this monorepo family. - Tag: the git tag on a tag build, otherwise the short commit SHA. On the default branch the
image also gets a moving
latest. Seereferences/pipeline-workflow.md§Tagging.
3. Publish gating — build always, push selectively
| Trigger | Images | Charts |
|---|---|---|
| pull request / feature branch | build only (verify it compiles) | helm lint + helm package (verify) |
push to default branch (test) or main |
build and push | package and push |
tag v* |
build and push (tagged with the git tag) | package and push |
A PR must never publish — it proves the artifact builds, nothing more. The template computes a
PUBLISH flag from the event; do not remove it.
4. Installing the pipeline into a monorepo
- Copy
assets/workflows/ci-cd.yaml→<repo>/.gitea/workflows/ci-cd.yaml. - Set
runs-on:to your runner's label (see Gotchas — it must be a label whose jobs providedocker,helm, andgit). SetHL_V1_HARBOR_PROJECT(defaultgondor-v1) to the Harbor project the robot can push to. - Ensure the three Gitea values from §1 exist (2 global variables once; 1 org secret per org).
- Commit, push a throwaway branch, and confirm the run builds without pushing.
- Merge to the default branch and confirm images + charts appear in Harbor.
Handoff
This skill stops at published to Harbor. To roll the new image/chart into the cluster, hand
the image reference and tag to development-gitops-argo-cd: the tag flows through the Copier
variables into the Helm values Argo CD syncs. Never wire a raw kubectl apply into this
pipeline — deployment is GitOps's responsibility, and a pipeline that also deploys bypasses the
one source of truth for cluster state.
Gotchas
- Build context is the repo root, always.
docker build -f applications-backend/<svc>/Dockerfile … .The Dockerfile'sCOPY . .+turbo pruneneed the whole monorepo. Building from the app directory fails with missing lockfile / workspace errors. - Charts are centralized, not co-located. They live in
helm-charts/<chart>/. An app can ship an image with no chart, and a chart can exist with no matching app — discover the two independently, do not assume a 1:1 pairing. - Gitea has no global secrets — only global variables. The username/address are variables (global, set once); the robot secret is an org secret (per org). Do not try to create a "global secret"; the admin Actions page only offers global variables.
- The robot secret is only shown once, at creation. Harbor never reveals it again on GET.
It's cached under palantir
.harbor/<project>-<robot>.yml(git-ignored). To rotate, re-run the palantir robot task, then update the Gitea org secret. Seereferences/harbor-auth.md. runs-onmaps to a container image — it must havedocker,helm,git.turbo pruneruns inside the image build, so the job itself needs Node only if you add pre-build steps; the default template needs just docker + helm + git. A barenode:alpinelabel has no docker CLI and the build step will fail withdocker: not found.- Marketplace actions may not resolve. Gitea proxies
actions/*to github.com by default; if the runner has no egress,actions/checkoutfails. The template keeps action use toactions/checkout@v4only and documents agit clonefallback inreferences/pipeline-workflow.md. - Frontend build args are public. A frontend image bakes its build-time env into the
bundle. Never pass a secret as a
--build-arg; anything per-environment on the frontend is fetched at runtime, not built in. helm pushneeds an OCI-enabled Helm ≥ 3.8 and a priorhelm registry login. Without the login step,helm pushfails withunauthorizedeven thoughdocker loginsucceeded — they are separate credential stores.- Never
docker loginwith-pon the command line. Use--password-stdin; a-pargument lands in the process list and the runner logs.