Secure CI/CD for EKS: GitHub Actions, Trivy and Argo CD

The problem

An AI-native HR operating system running across multiple EKS clusters needed a delivery pipeline that matched its operational posture. The platform handles sensitive HR data - there was no room for ad-hoc deployments, stored credentials, or images that had not been checked for vulnerabilities before reaching production.

The requirements were clear: secure at every step, with no long-lived credentials anywhere in the pipeline, every image scanned before it could be pushed, and every change driven purely through Git - so the state of every cluster is always auditable, reproducible, and reversible with a simple revert.

What we built

Secure by design, on both sides. The pipeline holds no secrets at all. On the CI side, AWS-GitHub OIDC replaces stored access keys - the workflow assumes an IAM role at runtime and receives short-lived credentials that expire when the job ends. On the CD side, each region's Argo CD connects to the GitOps repository with its own read-only GitHub user - pull access to manifests and Helm values, nothing more. There is nothing to leak, rotate, or steal on either side.

CI pipeline: from commit to ECR. Every image build starts with a GitHub Actions workflow (.github/workflows/docker-image.yml). Images are built from minimal AWS-base container images, keeping the attack surface and image size small. Every image is scanned with Trivy before it can proceed - a vulnerability above the configured severity threshold fails the pipeline, and the image never reaches Amazon ECR. Only clean images enter the registry.

CI pipeline: from commit to Amazon ECR via GitHub Actions, OIDC and Trivy scanning

CD pipeline: an independent Argo CD per region. Delivery does not run through one central controller. Each region runs its own Argo CD instance, watching the same GitOps repository but managing only that region's App-of-Apps and only that region's EKS cluster - which is shared by multiple clients in that region. When a new image tag lands in the GitOps repo, each region's Argo CD independently detects the change and reconciles its own cluster to the desired state, typically within minutes. A region's Argo CD has no visibility into, or effect on, any other region's cluster.

This keeps regions genuinely isolated operationally, not just at the infrastructure layer: a misconfigured sync in one region cannot touch another, and adding a new region means standing up a new Argo CD instance with its own App-of-Apps, not extending a shared one.

CD pipeline: independent Argo CD per region syncing to its own EKS cluster

After setup, everything happens in GitHub. This is the operational payoff. Once the pipeline was in place, the team's entire delivery workflow became Git commits:

  • Ship a new image? Commit the code change. GitHub Actions builds, scans, and pushes automatically.
  • Deploy it? Commit the new image tag to the GitOps repo. Each region's Argo CD picks it up and syncs its cluster within minutes.
  • Something wrong? Revert the commit to the previous version or tag. Each region's Argo CD rolls its own cluster back the same way it rolled forward.

No consoles, no kubectl against production, no deployment scripts. The Git history is the deployment history.

Key outcomes

  • Secure on both sides, credentials nowhere. OIDC short-lived tokens on the CI side, a read-only GitHub user per region on the CD side. There are no stored secrets to leak, rotate, or steal - and a compromised Argo CD instance can read manifests for its own region, nothing else.
  • Every image is scanned before it ships. Trivy runs on every build. A failing scan stops the pipeline - no image reaches ECR or any cluster without passing.
  • Everything is a Git commit. New image, new deployment, configuration change - all of it happens by checking in to GitHub. Builds trigger automatically, each region's Argo CD syncs in minutes.
  • Rollback is a revert. Any bad change is undone by reverting the commit to the previous version or tag. Every region's cluster follows Git, in both directions.
  • Minimal base images reduce the attack surface. AWS-base minimal containers keep packages and layers lean - less for Trivy to flag, less for clusters to run.
  • Regions are truly isolated. No central Argo CD watches every cluster. Each region syncs only its own EKS cluster, shared by that region's clients - a problem in one region cannot reach another.