Architecture: CI Pipeline (landscaping-assistant)

arch-ci-pipeline-landscaping-assistant Architecture

architecture

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:&lt;SHA&gt;</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 the base-images repo. This means bundle install only 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-push depends on both lint and test succeeding. Lint and test run in parallel after bundle-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 in pal-e-services (Harbor project, robot accounts, namespace, ArgoCD app, Tailscale funnel), and deployment config in pal-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 install instead of uv, 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.
  • arch-ci-pipeline -- shared platform CI pattern (Python-focused; this note is the Rails variant)
  • project-landscaping-assistant -- project page (includes ci-performance user story)
  • argocd-image-updater -- architecture note for the Harbor-to-deployment link
  • arch-secrets-pipeline -- platform-wide secrets architecture (includes Harbor credentials)
  • sop-deploy-recovery -- deployment failure recovery procedures
  • sop-ci-pipeline-recovery -- CI failure triage runbook
  • docs/pipeline.md in the repo -- full pipeline narrative including iOS flows
  • base-images -- pre-built Ruby images on Harbor