GitHub Actions
TL;DR
GitHub Actions pipelines live in .github/workflows/*.yaml. Separate build, test, and deploy jobs. Use OIDC (not long-lived secrets) for AWS auth. For GitOps clusters, CI should update the manifest repo — not run kubectl apply directly.
Full CI Pipeline
This template covers lint, test, Docker build + push to ECR, and manifest update for GitOps — all in one workflow triggered on PR and push to main.
yaml.github/workflows/ci.yaml
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
permissions:
contents: read
id-token: write # required for OIDC
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with: {python-version: "3.12"}
- run: pip install -r requirements.txt
- run: pytest --cov=app tests/
build-push:
needs: test
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
outputs:
image: ${{ steps.meta.outputs.tags }}
digest: ${{ steps.push.outputs.digest }}
steps:
- uses: actions/checkout@v4
# Authenticate to AWS using OIDC — no long-lived secrets
- uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789:role/github-actions-ecr
aws-region: us-east-1
- uses: aws-actions/amazon-ecr-login@v2
- name: Docker metadata
id: meta
uses: docker/metadata-action@v5
with:
images: 123456789.dkr.ecr.us-east-1.amazonaws.com/myapp
tags: |
type=sha,prefix=,format=short
type=raw,value=latest,enable={{is_default_branch}}
- uses: docker/build-push-action@v5
id: push
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
cache-from: type=gha
cache-to: type=gha,mode=max
# Sign image with cosign (keyless)
- uses: sigstore/cosign-installer@v3
- run: cosign sign --yes 123456789.dkr.ecr.us-east-1.amazonaws.com/myapp@${{ steps.push.outputs.digest }}
update-manifests:
needs: build-push
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
repository: myorg/k8s-manifests
token: ${{ secrets.MANIFEST_REPO_PAT }}
- name: Update image tag in manifest
run: |
cd production/myapp
sed -i "s|image: .*myapp.*|image: 123456789.dkr.ecr.us-east-1.amazonaws.com/myapp@${{ needs.build-push.outputs.digest }}|" deployment.yaml
git config user.email "ci@myorg.com"
git config user.name "GitHub Actions"
git add -A
git commit -m "chore: update myapp to ${{ github.sha }}" || echo "No changes"
git pushReusable Workflows
Extract common CI logic (Docker build, Trivy scan, Helm lint) into reusable workflows in a central repo; call them from application repos to avoid duplicating pipeline code across projects.
yaml.github/workflows/caller.yaml
jobs:
build:
uses: myorg/platform-workflows/.github/workflows/docker-build.yaml@main
with:
image-name: myapp
ecr-repo: 123456789.dkr.ecr.us-east-1.amazonaws.com/myapp
secrets: inherit # pass all secrets from calling workflowUseful Tips
- Use
actions/checkout@v4withfetch-depth: 0when you need the full history (e.g., forgit log, semantic versioning, orgit diff). - Cache dependencies with
actions/cacheor thesetup-*action's built-in cache to keep builds under 2 minutes. - Use matrix builds to test across Python/Node versions or OS in parallel without duplicating jobs.
- Don't store secrets in workflow files — use GitHub repository/environment secrets and OIDC for cloud auth.
- Pin action versions to commit SHAs (e.g.,
actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683) to prevent supply-chain attacks from tag mutation.