GitHub Actions Tutorial — Project: Multi-Env Deploy

2 minute read

Project: Multi-Environment Deploy Pipeline

Build a staging → smoke test → production deployment pipeline with OIDC authentication, environment protection rules, and approval gates.


Requirements

Feature Implementation
Test before deploy test job runs first
Deploy to staging (auto) environment: staging — no approvals
Smoke test staging HTTP health check
Deploy to production (manual) environment: production — requires approval
OIDC authentication aws-actions/configure-aws-credentials

Environment Setup

Configure these in Repository Settings → Environments:

staging:

  • No protection rules
  • Variables: APP_URL=https://staging.example.com
  • Secrets: AWS_ROLE_ARN (staging role)

production:

  • Required reviewers: @lead-dev, @devops
  • Wait timer: 5 minutes
  • Branch policy: main only
  • Variables: APP_URL=https://example.com
  • Secrets: AWS_ROLE_ARN (production role)

Complete Workflow


name: Deploy Pipeline

on:
  push:
    branches: [main]

permissions:
  id-token: write
  contents: read

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'
      - run: npm ci
      - run: npm test

  deploy-staging:
    needs: test
    runs-on: ubuntu-latest
    environment: staging
    steps:
      - uses: actions/checkout@v4

      - uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: ${{ secrets.AWS_ROLE_ARN }}
          aws-region: us-east-1

      - uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'

      - run: npm ci && npm run build

      - name: Deploy to staging
        run: aws s3 sync dist/ s3://staging-bucket --delete

  smoke-test:
    needs: deploy-staging
    runs-on: ubuntu-latest
    steps:
      - name: Health check
        run: |
          for i in 1 2 3 4 5; do
            STATUS=$(curl -s -o /dev/null -w '%{http_code}' "${{ vars.APP_URL }}/health")
            if [ "$STATUS" = "200" ]; then
              echo "✅ Health check passed"
              exit 0
            fi
            echo "Attempt $i: got $STATUS, retrying in 10s..."
            sleep 10
          done
          echo "❌ Health check failed after 5 attempts"
          exit 1

  deploy-production:
    needs: smoke-test
    runs-on: ubuntu-latest
    environment: production
    steps:
      - uses: actions/checkout@v4

      - uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: ${{ secrets.AWS_ROLE_ARN }}
          aws-region: us-east-1

      - uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'

      - run: npm ci && npm run build

      - name: Deploy to production
        run: aws s3 sync dist/ s3://production-bucket --delete

      - name: Invalidate CDN
        run: |
          aws cloudfront create-invalidation \
            --distribution-id ${{ secrets.CF_DISTRIBUTION_ID }} \
            --paths "/*"


Pipeline Flow

push to main
    │
    ▼
  test ─────────── fail → stop
    │
    ▼
  deploy-staging
    │
    ▼
  smoke-test ───── fail → stop
    │
    ▼
  [approval gate] ← reviewer must approve
    │
    ▼
  deploy-production

Design Decisions

Decision Reasoning
Separate AWS roles per env Least-privilege; staging role can’t touch prod
Smoke test with retry New deploy may take a few seconds to become healthy
Wait timer on production 5 min buffer after approval to catch last-minute issues
CDN invalidation only in prod Staging doesn’t typically use a CDN

← Previous: Project — Docker + GHCR

Next: Project — Reusable Library →

Updated: