LCMD db logoLCMD[db]

CI/CD Pipeline

Merge-to-prod flow driven by GitHub Actions + ArgoCD Image Updater

Every push to main builds container images and pushes them to ghcr.io. ArgoCD Image Updater watches the registry, promotes the new tags into the live lcmd-app Application spec, and ArgoCD auto-syncs the pods. No manual step, no git commits from CI.

Flow

  1. You merge a PR into main.
  2. cd.yaml runs: lint, test, validate k8s manifests, build Docker images, push to ghcr.io/lcmd-epfl/db/<app>:main-YYYYMMDD-<short-sha>, run Trivy (fails on CRITICAL).
  3. Image Updater (polling every 2 min) notices the new tag matches ^main-\d{8}-[a-f0-9]{7,}$, rewrites spec.source.kustomize.images on the lcmd-app Application in-cluster via the ArgoCD API.
  4. ArgoCD's automated sync reconciles, rolling the Deployments.

Release tags (v*) produce images tagged with the release name and annotate the GitHub release body with the image references. They do not trigger a separate deploy — prod rolls forward from main continuously.

Repository layout

kustomization.yaml

The :latest tags in overlays/prod/kustomization.yaml are fallbacks only — Image Updater overrides them at runtime via the Application's kustomize.images field.

Key tools

Kustomize

Renders the application manifests. The base defines Deployments/Services/Ingresses; the prod overlay adds the namespace, env-specific patches (resource limits, ingress hostnames), and the KSOPS-encrypted secrets.

KSOPS + Sealed Secrets

Application secrets (secrets.enc.yaml, tls-secret.enc.yaml in the prod overlay) are SOPS-encrypted with an age key and decrypted by ArgoCD's KSOPS plugin at sync time. ArgoCD-level secrets (repo deploy key, ghcr pull secret) use Bitnami Sealed Secrets, decrypted by the in-cluster controller.

ArgoCD Image Updater

Configured via an ImageUpdater CR in the argocd namespace. See infrastructure/kubernetes/argocd/image-updater/README.md for the full config, pull-secret setup, and rollback instructions.

Rollback

Find a known-good tag

gh api repos/lcmd-epfl/db/packages/container/db%2F<app>/versions \
  --jq '.[].metadata.container.tags[] | select(startswith("main-"))'

Or browse the ghcr package page.

Pin the older tag

argocd app set lcmd-app \
  --kustomize-image ghcr.io/lcmd-epfl/db/<app>=<older-tag>

ArgoCD auto-syncs within seconds.

Un-pin once the root cause is fixed

argocd app unset lcmd-app \
  --kustomize-image ghcr.io/lcmd-epfl/db/<app>

While pinned, Image Updater will not promote newer main-* tags for that image. Forgetting to un-pin = future merges silently stop deploying that workload.

Application history (who/what initiated each sync) is available in the ArgoCD UI or via argocd app history lcmd-app.

Monitoring

# Image Updater activity
kubectl -n argocd logs -l app.kubernetes.io/name=argocd-image-updater -f

# ArgoCD Application sync state
kubectl -n argocd get applications

# Prod workload pods
kubectl -n prod get pods
kubectl -n prod logs <pod-name>

Bootstrapping from scratch

kubectl create namespace argocd
kubectl apply -k infrastructure/kubernetes/argocd

This installs ArgoCD, Image Updater, the lcmd-app Application, and the sealed secrets. On first sync, ArgoCD creates the prod namespace and deploys the workloads.

On this page