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
- You merge a PR into
main. cd.yamlruns: lint, test, validate k8s manifests, build Docker images, push toghcr.io/lcmd-epfl/db/<app>:main-YYYYMMDD-<short-sha>, run Trivy (fails onCRITICAL).- Image Updater (polling every 2 min) notices the new tag matches
^main-\d{8}-[a-f0-9]{7,}$, rewritesspec.source.kustomize.imageson thelcmd-appApplication in-cluster via the ArgoCD API. - 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
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/argocdThis installs ArgoCD, Image Updater, the lcmd-app Application, and the sealed secrets. On first sync, ArgoCD creates the prod namespace and deploys the workloads.