GitHub Actions CI/CD Automation

GitHub Actions: CI/CD Pipelines from Zero to Production

NK
Nina Kowalski
DevOps Engineer @ OpenSource
Jun 01, 2025
20 min read

What You'll Learn

How to build CI/CD pipelines with GitHub Actions — workflows, jobs, steps, secrets management, and a complete production example for a Dockerized application.

What is GitHub Actions?

GitHub Actions is a CI/CD platform deeply integrated into GitHub. Instead of running a separate CI server (like Jenkins), your code repository itself executes workflows in response to GitHub events (push, PR, release).

Workflow
The entire automated process (YAML file)
contains →
Jobs
Run in parallel on different runners
contains →
Steps
Run sequentially (commands or actions)

Complete Production Workflow

Workflows live in the .github/workflows/ directory. Here is a complete example of a Node.js application that runs tests on PRs, and builds/deploys a Docker image on merges to main.

.github/workflows/ci-cd.yaml
name: Production CI/CD

# 1. TRGGERS: When should this run?
on:
  push:
    branches: [ "main" ]
  pull_request:
    branches: [ "main" ]
  workflow_dispatch:  # Allows manual triggering from GitHub UI

# Global environment variables
env:
  REGISTRY: ghcr.io
  IMAGE_NAME: ${{ github.repository }}

jobs:
  # ─── JOB 1: Test & Lint (Runs on PR and Push) ───
  test:
    name: Run Tests & Lint
    runs-on: ubuntu-latest
    
    # Run a Redis container for the tests
    services:
      redis:
        image: redis:alpine
        ports: [ "6379:6379" ]

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'  # Speeds up builds by caching node_modules!

      - name: Install dependencies
        run: npm ci

      - name: Run Linter
        run: npm run lint

      - name: Run Unit Tests
        run: npm run test:coverage
        env:
          REDIS_URL: redis://localhost:6379

      - name: Upload coverage report
        uses: actions/upload-artifact@v4
        with:
          name: coverage-report
          path: coverage/
          retention-days: 7

  # ─── JOB 2: Build & Push Docker Image (Only on main) ───
  build-and-push:
    name: Build & Push Docker Image
    needs: test          # Waits for test job to pass!
    if: github.ref == 'refs/heads/main'  # Only run on main branch
    runs-on: ubuntu-latest
    
    # Needs permissions to write to GitHub Container Registry
    permissions:
      contents: read
      packages: write

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Log in to the Container registry
        uses: docker/login-action@v3
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}  # Auto-provided by GitHub

      - name: Extract metadata (tags, labels)
        id: meta
        uses: docker/metadata-action@v5
        with:
          images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
          tags: |
            type=sha,format=short
            type=ref,event=branch
            latest

      - name: Build and push
        uses: docker/build-push-action@v5
        with:
          context: .
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}
          cache-from: type=gha
          cache-to: type=gha,mode=max

  # ─── JOB 3: Deploy to Kubernetes ───
  deploy:
    name: Deploy to Production
    needs: build-and-push
    if: github.ref == 'refs/heads/main'
    runs-on: ubuntu-latest
    
    # Require manual approval (Environment Protection Rules)
    environment: production

    steps:
      - name: Checkout code
        uses: actions/checkout@v4
        
      - name: Set target image tag
        run: echo "IMAGE_TAG=sha-${GITHUB_SHA::7}" >> $GITHUB_ENV

      - name: Configure AWS Credentials
        uses: aws-actions/configure-aws-credentials@v4
        with:
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          aws-region: us-east-1

      - name: Update KubeConfig
        run: aws eks update-kubeconfig --name prod-cluster --region us-east-1

      - name: Deploy to EKS
        run: |
          kubectl set image deployment/api-server app=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ env.IMAGE_TAG }}
          kubectl rollout status deployment/api-server --timeout=5m

GitHub Actions Best Practices

  • Pin action versions: Always use `@v4` or full commit SHAs, never `@master`.
  • Use Caching: Use `actions/cache` or `cache: 'npm'` to cache dependencies and speed up workflows.
  • Use Environments: Configure 'Environments' in repo settings to require manual approvals before deployment jobs run.
  • OIDC for Cloud Auth: Instead of storing long-lived AWS keys, use OpenID Connect (OIDC) to assume roles securely.
  • Matrix builds: Test across multiple Node/Python versions simultaneously using `strategy: matrix`.

Keep Reading

D
DevOps

Docker Networking Demystified: Bridge, Host & Overlay

8 min read Read More
C
Cloud

AWS IAM Roles vs Users vs Policies

10 min read Read More
P
Programming

Understanding Python's GIL & Multiprocessing

14 min read Read More