An end-to-end DevOps reference implementation: a containerized FastAPI service is provisioned, deployed, and continuously delivered to AWS EKS using Terraform, GitHub Actions, and Argo CD — with image vulnerability scanning baked into the pipeline.
flowchart LR
Dev[Developer] -->|git push| GH[GitHub Repo]
GH --> CI[GitHub Actions]
CI -->|build + trivy scan| ECR[Amazon ECR]
CI -->|commit new image tag| GH
Argo[Argo CD] -->|watches k8s/ manifests| GH
Argo -->|auto-sync| EKS[Amazon EKS Cluster]
ECR -->|pulled by| EKS
EKS --> App[FastAPI Service]
Prom[Prometheus] -->|scrapes /metrics| App
Prom --> Graf[Grafana]
Flow:
- Code is pushed to
app/onmain. - GitHub Actions builds the Docker image, scans it with Trivy, and pushes it to ECR (auth via OIDC — no static AWS keys stored in GitHub).
- CI commits the new image tag into
k8s/deployment.yaml. - Argo CD detects the Git change and automatically syncs the cluster to match (with self-healing if anyone manually edits the live cluster state).
This separates "build" (CI) from "deploy" (GitOps via Argo CD) — the pattern most production DevOps teams use.
| Layer | Tool |
|---|---|
| IaC | Terraform (VPC, EKS, IAM, ECR) |
| Compute | Amazon EKS (managed node groups) |
| CI | GitHub Actions |
| Image security | Trivy (fails build on Critical/High CVEs) |
| CD | Argo CD (GitOps, auto-sync + self-heal) |
| App | FastAPI, instrumented with prometheus_client |
.
├── app/ # FastAPI service + Dockerfile
├── terraform/ # VPC, EKS cluster, node group, ECR repo
├── k8s/ # Plain Kubernetes manifests (Argo CD's source of truth)
├── argocd/ # Argo CD Application definition
└── .github/workflows/ # CI/CD pipeline
cd terraform
terraform init
terraform plan
terraform applyThis creates a VPC across 2 AZs, an EKS cluster with a managed node group, and an ECR repository.
# Configure kubectl to point at the new cluster
aws eks update-kubeconfig --region us-east-1 --name gitops-demo-ekskubectl create namespace argocd
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml
kubectl apply -f argocd/application.yamlIn your GitHub repo settings, add:
- An IAM role for GitHub OIDC (
AWS_GHA_ROLE_ARNsecret) — avoids storing static AWS keys - Ensure the ECR repo name in
.github/workflows/ci-cd.ymlmatches the Terraform output
Push a change to app/ and watch the pipeline build, scan, push, and Argo CD auto-deploy.
kubectl get pods -n gitops-demo
kubectl port-forward svc/gitops-demo -n gitops-demo 8000:80
curl localhost:8000/health- OIDC over static keys — GitHub Actions assumes an IAM role rather than storing long-lived AWS credentials as secrets.
- GitOps split — CI never runs
kubectl applydirectly. Argo CD is the only thing that touches the cluster, which gives a full audit trail (every deploy is a Git commit) and automatic drift correction. - Shift-left security — Trivy scans block the pipeline before a vulnerable image ever reaches ECR.
- Non-root containers — the app Dockerfile and Deployment both enforce
runAsNonRoot.
- Add Prometheus + Grafana (Helm) and wire up the
/metricsendpoint already exposed by the app - Add an Ingress + cert-manager for TLS
- Add a staging/prod overlay with Kustomize
- Add OPA/Gatekeeper policies for cluster-wide guardrails