Why GitHub Actions + ArgoCD is the standard
GitHub Actions has become the default CI platform for most engineering teams — it's deeply integrated with Git workflows, has a rich marketplace, and scales from a startup to thousands of engineers. Paired with ArgoCD for continuous delivery, you get a fully GitOps-driven pipeline that's both powerful and maintainable.
This guide walks through building a production-grade pipeline that covers:
- Build, test, and security scan in CI
- OIDC authentication (no long-lived AWS credentials)
- Docker image build and push to ECR
- ArgoCD for GitOps-based deployment to EKS
- Argo Rollouts for canary deployments
Repository structure
.github/
workflows/
ci.yml # Build, test, scan
deploy.yml # Update image tag in GitOps repo
k8s/
apps/
my-app/
base/ # Kubernetes manifests
overlays/
staging/ # Kustomize overlays
production/
OIDC Authentication — no more long-lived credentials
The first thing to get right is authentication to AWS. Never store AWS_ACCESS_KEY_ID or AWS_SECRET_ACCESS_KEY as GitHub secrets — they're long-lived credentials that can be leaked or rotated incorrectly.
Use OIDC federation instead:
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789:role/github-actions-deploy
role-session-name: GitHubActions
aws-region: us-east-1
The IAM role trust policy allows GitHub's OIDC provider to assume it:
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::123456789:oidc-provider/token.actions.githubusercontent.com"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"token.actions.githubusercontent.com:aud": "sts.amazonaws.com",
"token.actions.githubusercontent.com:sub": "repo:your-org/your-repo:environment:production"
}
}
}
The CI workflow
name: CI
on:
push:
branches: [main]
pull_request:
jobs:
build-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run tests
run: npm test -- --coverage
- name: Lint
run: npm run lint
security-scan:
runs-on: ubuntu-latest
needs: build-test
steps:
- uses: actions/checkout@v4
- name: Run Trivy vulnerability scan
uses: aquasecurity/trivy-action@master
with:
scan-type: 'fs'
severity: 'CRITICAL,HIGH'
exit-code: '1'
build-push:
runs-on: ubuntu-latest
needs: [build-test, security-scan]
if: github.ref == 'refs/heads/main'
outputs:
image-tag: ${{ steps.meta.outputs.version }}
steps:
- uses: actions/checkout@v4
- name: Configure AWS credentials (OIDC)
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ vars.AWS_ROLE_ARN }}
aws-region: us-east-1
- name: Login to ECR
uses: aws-actions/amazon-ecr-login@v2
- name: Build and push
uses: docker/build-push-action@v5
with:
push: true
tags: ${{ vars.ECR_REGISTRY }}/my-app:${{ github.sha }}
cache-from: type=gha
cache-to: type=gha,mode=max
ArgoCD for continuous delivery
With the image pushed to ECR, the deployment step updates the image tag in your GitOps repository. ArgoCD watches that repository and syncs the change to your cluster:
deploy-staging:
runs-on: ubuntu-latest
needs: build-push
environment: staging
steps:
- name: Update image tag in GitOps repo
uses: actions/checkout@v4
with:
repository: your-org/k8s-configs
token: ${{ secrets.GITOPS_TOKEN }}
- name: Update staging overlay
run: |
cd apps/my-app/overlays/staging
kustomize edit set image my-app=${{ vars.ECR_REGISTRY }}/my-app:${{ github.sha }}
git commit -am "chore: deploy my-app ${{ github.sha }} to staging"
git push
ArgoCD detects the change and syncs automatically to staging. Production requires a manual promotion or tag-based trigger.
Progressive delivery with Argo Rollouts
For production deployments, use Argo Rollouts for canary analysis:
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
name: my-app
spec:
strategy:
canary:
canaryService: my-app-canary
stableService: my-app-stable
steps:
- setWeight: 10
- pause: { duration: 5m }
- analysis:
templates:
- templateName: success-rate
- setWeight: 50
- pause: { duration: 10m }
- setWeight: 100
If the AnalysisTemplate detects elevated error rates, Argo Rollouts automatically rolls back to the stable version — no manual intervention needed.
Key takeaways
- OIDC over static credentials — always, for every environment
- Security scanning in CI — Trivy, Checkov, or Semgrep before images are pushed
- GitOps for CD — ArgoCD owns what's deployed, not the CI pipeline
- Progressive delivery — canary + automatic rollback for production
- Environment gates — staging is automatic, production requires approval or a tag trigger