Architecture: CI Pipeline (landscaping-assistant)
CI Pipeline: landscaping-assistant
How code moves from a git push to a running container in production. Woodpecker CI builds and tests every push/PR; merges to main produce a tagged Docker image in Harbor, which ArgoCD Image Updater picks up for deployment. Pipeline defined in
.woodpecker.yaml at the repo root. This is the Rails variant of the shared arch-ci-pipeline pattern.Diagram
Components
| Component | Purpose | Notes |
|---|---|---|
| <code>clone</code> step | Shallow fetch from Forgejo over in-cluster HTTP | <code>alpine/git</code> image, depth 1, fetches from <code>forgejo-http.forgejo.svc.cluster.local</code> |
| <code>bundle-install</code> step | Install gems to <code>vendor/bundle</code> with 4 parallel jobs | Uses <code>ruby-rails-build:latest</code> base image from Harbor (<code>base-images</code> repo). Runs on push and PR. |
| <code>lint</code> step | RuboCop static analysis | <code>--fail-level=error</code>; depends on bundle-install; runs on push and PR |
| <code>test</code> step | RSpec suite against postgres:17 service container | Runs <code>db:create db:migrate</code> then <code>rspec</code>; depends on bundle-install; runs on push and PR |
| <code>build-and-push</code> step | Docker build via Kaniko, push tagged image to Harbor | Depends on lint + test passing; <strong>main branch only</strong>; tags image with commit SHA; uses Kaniko 2.3.0 with layer caching |
| <code>postgres:17</code> service | Ephemeral test database | Matches production CNPG PostgreSQL 17 version; password <code>postgres</code> |
| Harbor registry | Stores built images at <code>landscaping-assistant/app:<SHA></code> | Robot account credentials stored as Woodpecker repo secrets, provisioned by <code>pal-e-services</code> |
| ArgoCD Image Updater | Detects new tag in Harbor, writes it to <code>pal-e-deployments</code> overlay | Triggers ArgoCD sync automatically; see <code>argocd-image-updater</code> arch note |
| <code>ruby-rails-build</code> base image | Pre-baked Ruby, Bundler, Node, native extension build deps | Built in <code>base-images</code> repo, pushed to Harbor <code>library/</code> project. Shared by CI steps and the production Dockerfile (single-stage build). |
Key Decisions
- In-cluster clone over HTTP -- Woodpecker runs inside k3s, so the clone step fetches from the in-cluster Forgejo service URL rather than a public endpoint. Avoids SSH key management and external network dependency.
- Shared base image for CI and production -- Both the CI steps and the production Dockerfile (
FROM ruby-rails-build:latest) use the same base image from thebase-imagesrepo. This meansbundle installonly compiles app-specific gems since common native extensions are pre-baked, cutting both CI time and Docker build time. - Single-stage production Dockerfile -- Tradeoff: ~200MB larger final image, but eliminates ~1m30s of rootfs unpacking that multi-stage required for Kaniko builds.
- Lint and test gate the image build --
build-and-pushdepends on bothlintandtestsucceeding. Lint and test run in parallel afterbundle-install. - Main-only image push -- PR pipelines run lint and test but do not build or push images. Only merges to main produce container artifacts.
- SHA tagging -- Images are tagged with the full commit SHA (
CI_COMMIT_SHA), giving ArgoCD Image Updater a unique, traceable tag per deploy. - Three-repo deployment model -- Application code lives in
landscaping-assistant, infrastructure provisioning inpal-e-services(Harbor project, robot accounts, namespace, ArgoCD app, Tailscale funnel), and deployment config inpal-e-deployments(kustomize overlays). CI lives in the app repo; the deployment repo is updated automatically by Image Updater. - Rails variant of the shared pattern -- The platform's shared CI pattern (
arch-ci-pipeline) covers Python services. This note documents the Rails-specific differences:bundle installinstead ofuv, RuboCop instead of ruff, RSpec instead of pytest, and a pre-built Ruby base image instead of a Python one.
Procedures
No SOP needed -- the CI pipeline is fully automated on every push. For deployment recovery, see
sop-deploy-recovery. For CI failure triage, see sop-ci-pipeline-recovery.Related
arch-ci-pipeline-- shared platform CI pattern (Python-focused; this note is the Rails variant)project-landscaping-assistant-- project page (includesci-performanceuser story)argocd-image-updater-- architecture note for the Harbor-to-deployment linkarch-secrets-pipeline-- platform-wide secrets architecture (includes Harbor credentials)sop-deploy-recovery-- deployment failure recovery proceduressop-ci-pipeline-recovery-- CI failure triage runbookdocs/pipeline.mdin the repo -- full pipeline narrative including iOS flows- base-images -- pre-built Ruby images on Harbor