--- title: CI-CD Pipeline tags: [operations] --- # CI/CD Pipeline How code goes from a push to a running container in production. The CI is **Gitea Actions** running on the same Gitea instance that hosts the repos. The CD is **Watchtower** on the production host (covered in [[Deployment]]). --- ## 1. Where workflows live | Repo | Path | Files | |------|------|-------| | Backend | `.gitea/workflows/` | `docker-build-simple.yml`, `docker-build-dev.yml`, `docker-build-no-cache.yml` | | Frontend | `.gitea/workflows/` | `deploy.yml`, `devDeploy.yml` | Gitea Actions speaks the same YAML dialect as GitHub Actions — most third-party actions (`actions/checkout@v4`, `docker/login-action@v3`, `docker/build-push-action@v5`) work unchanged. --- ## 2. Required secrets Configured per repo at **Settings → Actions → Secrets**. | Secret | Repo | Purpose | |--------|------|---------| | `GITEATOKEN` | both | Personal access token for the `manawenuz` user with `write:packages` scope. Used by every workflow to log into the container registry at `git.manko.yoga`. | | `SENTRY_AUTH_TOKEN` | frontend | (Optional) For source-map upload during Next.js build. Skipped if absent. | The registry itself is implicit: `git.manko.yoga` with `manawenuz` as the user. Image paths are `git.manko.yoga/manawenuz/`. > [!warning] If `GITEATOKEN` expires or is rotated, all workflows fail at the `docker/login-action` step. Rotate proactively (annual reminder). --- ## 3. Backend workflows ### `docker-build-simple.yml` — manual build ```yaml name: Manual Build and Push Docker Image on: workflow_dispatch: inputs: version: description: 'Version to build (leave empty for package.json)' required: false type: string ``` - **Trigger.** Manual only (via Gitea UI → Actions → "Run workflow"). - **Steps.** Checkout → buildx → `docker login` → read version (input or `package.json`) → build `Dockerfile.prod` → push tags `:` and `:dev` → echo result. - **When to use.** Cutting an ad-hoc build of a specific commit without merging to a branch. The `:dev` tag is overwritten — production (`:latest`) is **not** touched. - **Cache.** Uses `type=gha` cache to speed up subsequent runs. ### `docker-build-dev.yml` — dev branch auto-build ```yaml on: push: branches: [ development ] tags: [ 'v*' ] ``` - **Trigger.** Every push to `development` and every tag matching `v*`. - **Tags pushed.** `:dev-` + moving `:dev`. - **Effect.** Refreshes the dev image. The production Watchtower **does not** watch `:dev`, so this is safe to push as often as you want. ### `docker-build-no-cache.yml` — production build ```yaml on: push: branches: [ main, master ] tags: [ 'v*' ] ``` - **Trigger.** Every push to `main` (or `master`) and every `v*` tag. - **Tags pushed.** `:` + moving `:latest`. - **Effect.** Watchtower polls `:latest`, detects the new digest, restarts `nickapp-backend` on the production host. See [[Deployment#routine-deploy]]. - **No cache.** The file is named "No Cache" but actually does not pass `cache-from`/`cache-to`, so each build is from scratch. Slower (~5–8 min) but eliminates a class of stale-layer bugs. The `simple` workflow uses GHA cache for speed. > [!tip] If you need to invalidate a cached layer in the `simple` workflow, run `no-cache` once — the resulting tag overwrites the registry digest and `simple`'s next run will start from a cleaner base. --- ## 4. Frontend workflows Both workflows share the same shape: spin up a `node:22` container, run a deploy shell script that does `docker login + build + push`. ### `deploy.yml` — production ```yaml on: push: branches: [ main, master ] workflow_dispatch: ``` Calls `./scripts/deploy.sh` — see [[Scripts#deployment]]. The script: 1. Reads `package.json` version. 2. `docker login git.manko.yoga -u manawenuz -p $GITEATOKEN`. 3. Builds `git.manko.yoga/manawenuz/escrow-frontend:` and `:latest` from `Dockerfile`. 4. Pushes both tags. `:latest` is what production Watchtower watches → live deploy follows automatically. ### `devDeploy.yml` — development branch Same as `deploy.yml` but triggered on `development` and runs `./scripts/deployDev.sh`, which pushes only `:dev`. --- ## 5. End-to-end timeline (production deploy) ``` t=0 Developer merges PR → main t+5s Gitea webhook fires t+10s Gitea Actions runner pulls repo, starts container t+30s docker/setup-buildx-action initialised t+45s docker/login-action authenticated t+2-5m docker/build-push-action builds Dockerfile.prod t+5m Push to git.manko.yoga/manawenuz/escrow-backend:latest t+5m+ Watchtower (next poll, up to 5 min) detects new digest t+10m Watchtower stops old container, starts new one t+10m40s start_period=40s elapses, healthcheck passes t+11m Nginx routes traffic to the new container ``` **Typical SLA: 10–12 minutes from merge to live.** For an emergency rollback see [[Deployment#roll-back]]. --- ## 6. Versioning automation Tied to `backend/scripts/auto-version.sh` + `ai-enhanced.sh` (and the frontend mirror). Full reference in [[Git Workflow#versioning]] and [[Scripts#auto-version-sh]]. In short: ```bash # Developer side, on the branch they're releasing: npm run smart-release # → AI analyses last commit, picks bump (major/minor/patch/skip) # → bumps package.json # → commits "chore: bump version to vX.Y.Z" # → tags vX.Y.Z # → git push && git push --tags ``` The push to `main` (or the `v*` tag) then triggers `docker-build-no-cache.yml`, which: - Reads the new version from `package.json` (`node -p "require('./package.json').version"`) - Builds and pushes `:` + `:latest` So both the **image tag** and the **git tag** carry the same `vX.Y.Z` — easy to correlate when investigating an issue. --- ## 7. Adding tests to the pipeline The workflows today only build + push; they do **not** run Jest or Playwright. To gate releases on tests, add a `test` job before the build: ```yaml jobs: test: runs-on: ubuntu-latest container: node:22 steps: - uses: actions/checkout@v4 - run: yarn install --frozen-lockfile - run: yarn lint - run: yarn test --ci --runInBand - run: yarn test:e2e # if a service container is available build-and-push: needs: test runs-on: ubuntu-latest # ...existing steps... ``` Or run lint + typecheck as a pre-gate using a separate workflow that triggers on PR opened/synchronised. --- ## 8. Inspecting a build In Gitea: **Actions → workflow → run** to see real-time logs. Useful CLI for the registry from your laptop: ```bash # List images and tags curl -s -u "manawenuz:$GITEATOKEN" \ "https://git.manko.yoga/v2/manawenuz/escrow-backend/tags/list" | jq # Pull a specific tag docker login git.manko.yoga -u manawenuz docker pull git.manko.yoga/manawenuz/escrow-backend:2.6.3 ``` --- ## 9. Self-hosted runner notes Gitea Actions can use either built-in `act_runner` or your own. Currently the workflows are written for `runs-on: ubuntu-latest`, which the act_runner supplies via a generic Ubuntu container. If you need: - More CPU/RAM for builds → register a beefier self-hosted runner and change `runs-on:` to its label. - A Docker-in-Docker setup (frontend `deploy.yml` does this with `options: --privileged`) — confirm the runner trusts the workflow. --- ## 10. Failure modes & remediation | Failure | Most likely cause | Fix | |---------|------------------|-----| | `unauthorized: authentication required` at push | `GITEATOKEN` expired or lacks `write:packages` | Rotate the token, update the repo secret | | `Cannot perform an interactive login from a non TTY device` | Old docker-login-action version | Bump to `docker/login-action@v3` | | Build hangs at `yarn install` | npm registry timeout | Increase `network-timeout` (already 600000); re-run | | Image pushed but Watchtower doesn't roll | Watchtower can't reach the registry | `docker logs watchtower`; verify `/root/.docker/config.json` is mounted into the container | | New container fails healthcheck | App crash on boot | `docker logs nickapp-backend`; check env vars, follow [[Incident Response]] | | Multi-arch warnings about platform | Build runner is arm64 but prod is amd64 | Add `--platform=linux/amd64` to `docker/build-push-action` inputs | | Image size grew suddenly | Dev dep crept into prod stage | Audit `Dockerfile.prod` for missing `--production` flag in the runtime stage | --- ## 11. Pipeline diagram ``` Push to development Push to main │ │ ▼ ▼ ┌───────────────────────────┐ ┌───────────────────────────┐ │ docker-build-dev.yml │ │ docker-build-no-cache.yml │ │ (backend) │ │ (backend) │ │ devDeploy.yml (frontend) │ │ deploy.yml (frontend) │ └───────────────┬───────────┘ └───────────────┬───────────┘ │ │ push :,:dev push :,:latest │ │ ▼ ▼ git.manko.yoga/manawenuz/...:dev git.manko.yoga/manawenuz/...:latest │ │ │ ▼ │ ┌──────────────────────────┐ │ │ Watchtower │ │ │ (poll every 5 minutes) │ │ └──────────────┬───────────┘ │ │ manual pull on staging restart containers │ ▼ Production live ``` Cross-links: [[Deployment]] for what happens on the host, [[Git Workflow]] for what happens upstream, [[Scripts]] for the deploy shell scripts.