GitOps Kubernetes Delivery Platform
ArgoCD + Helm with promotion by pull request
Basis: CV: standardized Helm-based deployment patterns and GitOps workflows with ArgoCD across dev, staging and production, with drift detection. The published code is a reference implementation of that pattern — all names and values are synthetic.
Overview
CI builds and signs images; a separate config repository holds the desired state per environment; ArgoCD reconciles clusters to it, detects drift, and rolls back by reverting a commit.
Problem
Push-based pipelines that run kubectl against clusters hold cluster credentials in CI, make the live state hard to audit and let manual changes drift unnoticed.
Solution
Pull-based delivery: clusters pull from Git, so CI never needs cluster credentials. Promotion is a pull request that bumps an image tag in the next environment's values file.
Architecture
App repo CI
Test + build
immutable SHA tag
Promotion PR
bumps envs/dev image tag
Config repo (desired state)
charts/orders-api
envs/dev · staging · prod
AppProject + ApplicationSet
Argo CD
dev
auto sync, self-heal, prune
staging
auto sync, no prune
prod
manual sync, deny windows
Prod delivery
Argo Rollouts canary
10% → analysis → 50% → 100%
AnalysisTemplate
success rate ≥ 99%
External Secrets
from Secrets Manager
Failure and recovery path
- A canary whose success rate drops below 99% — or reports no data — fails analysis and the rollout aborts back to the stable version.
- A manual kubectl change shows up as drift; dev and staging self-heal it, prod surfaces it for review.
- Rollback is a git revert of the promotion commit; Argo Rollouts abort/undo covers an in-flight canary.
- An emergency hotfix can skip an environment only with an explicit flag, and the commit records why.
Technology stack
- Argo CD
- ApplicationSet
- Argo Rollouts
- Helm
- External Secrets Operator
- GitHub Actions
- Prometheus analysis
- kubeconform
Implementation
- An ApplicationSet generates one Application per environment directory with per-environment sync policy; a new environment defaults to manual sync.
- promote.py moves an image tag to the next environment by editing only the tag line, refuses mutable tags and skipped environments, and writes hotfix trailers into the commit message.
- A drift report compares live and desired manifests, honouring the same ignoreDifferences Argo CD uses (HPA-managed replicas, status, managed fields).
- A GitOps policy check: no automated sync or prune in prod, no cluster-scoped resources for the app project, no 'latest' tags, canary analysis wired for prod, and a minute-by-minute simulation proving the prod deny windows cover every off-hours minute.
- kubeconform -strict validates every environment render and all Argo CD and Rollouts resources against their CRD schemas; 85 unit tests; the example CI workflow passes the repository's hardening linter.
Security
- CI never holds cluster credentials; it can only open pull requests against the config repo.
- The app's AppProject allows no cluster-scoped resources and only its own namespaces; namespaces and secret stores live in a separate platform project.
- No plaintext secrets in Git: External Secrets pulls from Secrets Manager using workload identity.
- The chart refuses missing or mutable image tags at render time.
Reliability
- Production canary analysis runs only against canary pods, so the stable version's traffic cannot mask a bad release.
- Sync windows block automated changes to prod outside business hours.
Cost
- Argo CD and Rollouts run in-cluster and are cheap; the cost is operational — keep one Argo CD per trust boundary (see the cost doc).
Results
No quantified results are published for this project. Figures from client work are confidential, and this portfolio only shows numbers that can be verified or reproduced.
Code & documentation
Lessons learned
- Keep application code and deployment config in separate repositories, or promotion PRs get noisy.
- Enforce the delivery rules with a policy check in CI, not only in reviewers' heads.
- Known gaps, stated in the README: nothing was installed into a cluster, canary weights follow pod ratio without a traffic router, and one upstream CRD schema needed a documented repair to validate.