--- name: devsecops-ci-cd-gitea--3darch description: > 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//Dockerfile` or `applications-frontend//Dockerfile`; packaging and pushing OCI Helm charts from `helm-charts//`; 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. license: Proprietary metadata: author: home-v1-skills-code-agent version: "1.0" spec: agentskills.io/specification compatibility: > 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 ## 3D Architecture Wizzard Project Adoption This is the primary project-adopted skill for **3D Architecture Wizzard** (`3darch`). - Central source: https://gitea.lego-cloud.eu/home-v1-skills-code-agent/devsecops-ci-cd-gitea - Source branch: `test` - Source commit: `8f46c3faa5ba074b8a48971b87365e0d3e2a4c40` - Adopted repository: https://gitea.lego-cloud.eu/corp-v1-3darch-skills-code-agent/devsecops-ci-cd-gitea--3darch - Application: https://gitea.lego-cloud.eu/corp-v1-3darch/corp-v1-3darch - Documentation: https://gitea.lego-cloud.eu/corp-v1-3darch/corp-v1-3darch-documentation - Environment namespace: `CORP_V1_3DARCH_*` - Global diagram skill: https://gitea.lego-cloud.eu/home-v1-skills-code-agent/drawio-main - Global glossary skill: https://gitea.lego-cloud.eu/home-v1-skills-code-agent/corp-v1--glossary - Project automation: none; no scheduler job is authorized. Project engineering peers: - `development-branching-strategy--3darch` — https://gitea.lego-cloud.eu/corp-v1-3darch-skills-code-agent/development-branching-strategy--3darch - `development-gitops-argo-cd--3darch` — https://gitea.lego-cloud.eu/corp-v1-3darch-skills-code-agent/development-gitops-argo-cd--3darch - `development-monorepo-pnpm--3darch` — https://gitea.lego-cloud.eu/corp-v1-3darch-skills-code-agent/development-monorepo-pnpm--3darch - `development-scripts--3darch` — https://gitea.lego-cloud.eu/corp-v1-3darch-skills-code-agent/development-scripts--3darch - `devsecops-ci-cd-gitea--3darch` — https://gitea.lego-cloud.eu/corp-v1-3darch-skills-code-agent/devsecops-ci-cd-gitea--3darch - `documentation-docusaurus--3darch` — https://gitea.lego-cloud.eu/corp-v1-3darch-skills-code-agent/documentation-docusaurus--3darch - `template-engine-copier--3darch` — https://gitea.lego-cloud.eu/corp-v1-3darch-skills-code-agent/template-engine-copier--3darch 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](#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` → `/.gitea/workflows/ci-cd.yaml` | ## The mental model ```text git push ─▶ act_runner ─▶ [ discover apps ] ├─ applications-backend//Dockerfile ─▶ docker build (repo root) ─▶ push harbor//: ├─ applications-frontend//Dockerfile ─▶ docker build (repo root) ─▶ push harbor//: └─ helm-charts//Chart.yaml ─▶ helm package ─▶ push oci://harbor//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: ```bash 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.json` so every job is pre-authenticated. It > trades central rotation for zero per-org setup. Steps in `references/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/*/Dockerfile` and `applications-frontend/*/Dockerfile`. The image repository is the **application directory name**. Build context is the **repository root** (`docker build -f /Dockerfile … .`) because the Dockerfile runs `turbo prune` over the whole monorepo — building from the app directory **will fail**. - **Charts**: one per `helm-charts/*/Chart.yaml`. Charts are **centralized under `helm-charts/` at the repo root**, *not* co-located inside the app directory. Do not look for `applications-frontend//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`. See `references/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 1. Copy `assets/workflows/ci-cd.yaml` → `/.gitea/workflows/ci-cd.yaml`. 2. Set `runs-on:` to your runner's label (see Gotchas — it must be a label whose jobs provide `docker`, `helm`, and `git`). Set `HL_V1_HARBOR_PROJECT` (default `gondor-v1`) to the Harbor project the robot can **push** to. 3. Ensure the three Gitea values from §1 exist (2 global variables once; 1 org secret per org). 4. Commit, push a throwaway branch, and confirm the run **builds without pushing**. 5. 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//Dockerfile … .` The Dockerfile's `COPY . .` + `turbo prune` need 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//`. 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/-.yml` (git-ignored). To rotate, re-run the palantir robot task, then update the Gitea org secret. See `references/harbor-auth.md`. - **`runs-on` maps to a container image — it must have `docker`, `helm`, `git`.** `turbo prune` runs *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 bare `node:alpine` label has no docker CLI and the build step will fail with `docker: not found`. - **Marketplace actions may not resolve.** Gitea proxies `actions/*` to github.com by default; if the runner has no egress, `actions/checkout` fails. The template keeps action use to `actions/checkout@v4` only and documents a `git clone` fallback in `references/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 push` needs an OCI-enabled Helm ≥ 3.8 and a prior `helm registry login`.** Without the login step, `helm push` fails with `unauthorized` even though `docker login` succeeded — they are separate credential stores. - **Never `docker login` with `-p` on the command line.** Use `--password-stdin`; a `-p` argument lands in the process list and the runner logs.