DEV Community

InstaDevOps
InstaDevOps

Posted on • Originally published at instadevops.com

GitHub Actions for DevOps: The Complete CI/CD Setup Guide (2026)

GitHub Actions has become the default CI/CD tool for most engineering teams. Here's how to set up a production-grade pipeline from scratch.

Why GitHub Actions?

  • Free for public repos, 2,000 minutes/month for private
  • Native GitHub integration — no third-party OAuth, no webhook setup
  • Massive marketplace — 20,000+ pre-built actions
  • Matrix builds — test across multiple OS/language versions simultaneously

Basic Pipeline Structure

Every GitHub Actions workflow lives in .github/workflows/. Here's a production-ready starting point:

name: CI/CD Pipeline

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]

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
      - run: npm run lint

  build:
    needs: 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 run build
      - uses: actions/upload-artifact@v4
        with:
          name: build
          path: dist/

  deploy:
    needs: build
    if: github.ref == 'refs/heads/main'
    runs-on: ubuntu-latest
    steps:
      - uses: actions/download-artifact@v4
        with:
          name: build
          path: dist/
      - name: Deploy to production
        run: |
          # Your deploy command here
          echo "Deploying to production..."
Enter fullscreen mode Exit fullscreen mode

Key Concepts

Job Dependencies

Use needs: to create a pipeline flow:

test → build → deploy
Enter fullscreen mode Exit fullscreen mode

If tests fail, build and deploy never run. This prevents broken code from reaching production.

Caching

Always cache dependencies. Without caching, npm ci runs fresh every time (30-60 seconds wasted).

- uses: actions/setup-node@v4
  with:
    node-version: 20
    cache: 'npm'  # This caches node_modules
Enter fullscreen mode Exit fullscreen mode

Secrets Management

Never hardcode credentials. Use GitHub Secrets:

- name: Deploy to AWS
  env:
    AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
    AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
  run: aws s3 sync dist/ s3://my-bucket
Enter fullscreen mode Exit fullscreen mode

Matrix Builds

Test across multiple versions simultaneously:

strategy:
  matrix:
    node-version: [18, 20, 22]
    os: [ubuntu-latest, macos-latest]
steps:
  - uses: actions/setup-node@v4
    with:
      node-version: ${{ matrix.node-version }}
Enter fullscreen mode Exit fullscreen mode

Production Best Practices

1. Branch Protection Rules

  • Require PR reviews before merging to main
  • Require status checks (CI must pass)
  • No direct pushes to main

2. Environment Protection

deploy:
  environment: production  # Requires manual approval
  runs-on: ubuntu-latest
Enter fullscreen mode Exit fullscreen mode

3. Concurrency Control

Prevent multiple deploys running simultaneously:

concurrency:
  group: deploy-production
  cancel-in-progress: false
Enter fullscreen mode Exit fullscreen mode

4. Timeout Limits

jobs:
  test:
    timeout-minutes: 10  # Kill stuck jobs
Enter fullscreen mode Exit fullscreen mode

5. Artifact Retention

- uses: actions/upload-artifact@v4
  with:
    name: build
    path: dist/
    retention-days: 7  # Don't store forever
Enter fullscreen mode Exit fullscreen mode

Docker Build and Push

Most production apps deploy containers:

- name: Build and push Docker image
  uses: docker/build-push-action@v5
  with:
    context: .
    push: true
    tags: ghcr.io/myorg/myapp:latest
    cache-from: type=gha
    cache-to: type=gha,mode=max
Enter fullscreen mode Exit fullscreen mode

AWS Deployment Example

Deploy to ECS, Lambda, or S3:

- name: Configure AWS credentials
  uses: aws-actions/configure-aws-credentials@v4
  with:
    role-to-assume: arn:aws:iam::123456789:role/deploy
    aws-region: us-east-1

- name: Deploy to S3 + CloudFront
  run: |
    aws s3 sync dist/ s3://my-bucket --delete
    aws cloudfront create-invalidation \
      --distribution-id E1234 --paths "/*"
Enter fullscreen mode Exit fullscreen mode

Monitoring Your Pipeline

Track these metrics:

  • Build time — should be under 5 minutes
  • Success rate — aim for 95%+
  • Queue time — if jobs wait, add self-hosted runners
  • Flaky tests — identify and fix tests that fail intermittently

Common Mistakes

  1. Not caching dependencies — adds 30-60s per run
  2. Running everything sequentially — use needs: for parallelism
  3. No timeout — stuck jobs burn through minutes quota
  4. Deploying from PRs — always gate deploys to main branch only
  5. Hardcoded secrets — use GitHub Secrets, never commit credentials

Cost Optimization

  • Use ubuntu-latest — Linux runners are cheapest
  • Cancel redundant runs — use concurrency to stop outdated builds
  • Self-hosted runners — free minutes, you pay for compute
  • Cache aggressively — saves minutes on every run

What does your CI/CD pipeline look like? Drop a comment with your setup.

Top comments (0)