iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

Examples

Five end-to-end CI/CD examples: a GitHub Actions matrix build, a Docker build + push, a Terraform plan + apply, an OIDC-authenticated deploy, and a release pipeline. Paste-ready.

Five CI/CD recipes

EXAMPLE
# 1) Test matrix on push + PR
# .github/workflows/test.yml
name: test
on:
  push:    { branches: [main] }
  pull_request: { branches: [main] }

jobs:
  test:
    strategy:
      fail-fast: false
      matrix:
        node: ['18', '20', '22']
        os:   [ubuntu-latest, macos-latest, windows-latest]
    runs-on: ${{ matrix.os }}
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: { node-version: ${{ matrix.node }}, cache: 'npm' }
      - run: npm ci
      - run: npm run lint
      - run: npm run typecheck
      - run: npm test -- --reporter=junit
      - if: always()
        uses: actions/upload-artifact@v4
        with: { name: junit-${{ matrix.os }}-${{ matrix.node }}, path: junit.xml }

# 2) Docker build + push to GHCR
# .github/workflows/docker.yml
name: docker
on:
  push: { branches: [main], tags: ['v*'] }

permissions:
  contents: read
  packages: write

jobs:
  docker:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: docker/setup-buildx-action@v3
      - uses: docker/login-action@v3
        with: { registry: ghcr.io, username: ${{ github.actor }}, password: ${{ secrets.GITHUB_TOKEN }} }
      - uses: docker/metadata-action@v5
        id: meta
        with:
          images: ghcr.io/${{ github.repository }}
          tags: |
            type=ref,event=branch
            type=ref,event=tag
            type=sha,format=long
      - uses: docker/build-push-action@v6
        with:
          context: .
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}
          cache-from: type=gha
          cache-to: type=gha,mode=max

# 3) Terraform plan on PR, apply on merge
# .github/workflows/terraform.yml
name: terraform
on:
  pull_request:
    paths: ['infra/**']
  push:
    branches: [main]
    paths: ['infra/**']

permissions:
  id-token: write
  contents: read
  pull-requests: write

jobs:
  plan:
    if: github.event_name == 'pull_request'
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: aws-actions/configure-aws-credentials@v4
        with: { role-to-assume: ${{ secrets.TF_ROLE_ARN }}, aws-region: ap-southeast-2 }
      - uses: hashicorp/setup-terraform@v3
      - working-directory: infra
        run: |
          terraform init -input=false
          terraform plan -no-color -out plan.bin
          terraform show -no-color plan.bin > plan.txt
      - uses: actions/github-script@v7
        with: |
          const fs = require('fs');
          const body = '### Plan\n```\n' + fs.readFileSync('infra/plan.txt', 'utf8') + '\n```';
          github.rest.issues.createComment({ ...context.repo, issue_number: context.issue.number, body });

  apply:
    if: github.event_name == 'push' && github.ref == 'refs/heads/main'
    runs-on: ubuntu-latest
    environment: production
    steps:
      - uses: actions/checkout@v4
      - uses: aws-actions/configure-aws-credentials@v4
        with: { role-to-assume: ${{ secrets.TF_ROLE_ARN }}, aws-region: ap-southeast-2 }
      - uses: hashicorp/setup-terraform@v3
      - working-directory: infra
        run: |
          terraform init -input=false
          terraform apply -auto-approve -input=false

# 4) OIDC to AWS (no static keys)
# Set up the OIDC trust once in AWS:
# aws iam create-open-id-connect-provider \
#   --url https://token.actions.githubusercontent.com \
#   --client-id-list sts.amazonaws.com
# Then create a role with a trust policy that limits to your repo:
# { "Federated": "...oidc...", "Action": "sts:AssumeRoleWithWebIdentity",
#   "Condition": { "StringEquals": {
#     "token.actions.githubusercontent.com:aud": "sts.amazonaws.com",
#     "token.actions.githubusercontent.com:sub": "repo:owner/repo:ref:refs/heads/main"
#   } } }

# 5) Tag-triggered release pipeline (see cicd/tags lesson)
# .github/workflows/release.yml — triggered on push of v*.*.* tag
# Builds + signs + uploads + creates GitHub release + deploys.

# ===== Patterns to internalise =====
# - Run lint + typecheck + test on every PR, matrix-style
# - Cache aggressively (actions/setup-node cache, Docker buildx --cache-to gha)
# - OIDC for AWS / GCP / Azure (no static keys in secrets)
# - Plan on PR, apply on merge for infra
# - Separate workflow files per concern; reuse via composite actions

# ===== Pitfalls =====
# - Storing AWS keys in secrets (use OIDC)
# - Running tests against a single Node version
# - Forgetting to upload coverage / test reports
# - Long workflows (> 30 min) without parallelism or caching
# - Manual tag pushes (use release-please instead)

Why it matters

OIDC + a per-environment role assumption + plan-on-PR + apply-on-merge is the modern CI/CD shape. No static keys, no manual approvals on tiny changes, and the diff between staging and production is visible in the PR before any apply lands. Once you have it, "we deployed" stops being scary.

Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.

Example

Example
# A complete pipeline: lint → test → e2e → build → publish → deploy → notify.
Try it Yourself »

Discussion

Loading…