GitHub Actions CD and Deployments

Continuous Delivery (CD) automates the process of releasing software to users. Once a CI pipeline verifies that code is correct, a CD pipeline takes it the rest of the way — packaging it, deploying it to a server, and making it available. This topic builds a complete CD pipeline that deploys automatically after CI passes.

CI vs CD — The Key Difference

CI (Continuous Integration)       CD (Continuous Delivery/Deployment)
──────────────────────────────    ──────────────────────────────────────
Runs on every code change         Runs after CI passes
Tests and validates code          Delivers code to an environment
Goal: find bugs early             Goal: release confidently and quickly
Output: pass/fail report          Output: running application

Deployment Environments

Most teams deploy to multiple environments, each serving a different purpose:

Code change
    ↓
development   → where developers test in isolation
    ↓
staging       → mirrors production, tested by QA
    ↓
production    → what real users access

GitHub natively supports deployment environments. You create them in the repository settings and attach protection rules, required reviewers, and environment-specific secrets to each one.

Creating a GitHub Deployment Environment

  1. Go to SettingsEnvironments in your repository
  2. Click New environment
  3. Name it (e.g. staging or production)
  4. Optionally add required reviewers — people who must approve before deployment starts
  5. Add environment secrets specific to this environment

A Complete CI/CD Workflow

name: CI and CD Pipeline

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

jobs:
  test:
    name: 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

  build:
    name: 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: app-build
          path: ./dist

  deploy-staging:
    name: Deploy to Staging
    needs: build
    runs-on: ubuntu-latest
    environment: staging             ← Uses staging environment
    if: github.ref_name == 'main'   ← Only on main branch
    steps:
      - uses: actions/download-artifact@v4
        with:
          name: app-build
          path: ./dist
      - name: Deploy to staging server
        run: ./scripts/deploy.sh staging
        env:
          SERVER_URL: ${{ secrets.STAGING_SERVER_URL }}
          DEPLOY_KEY: ${{ secrets.STAGING_DEPLOY_KEY }}

  deploy-production:
    name: Deploy to Production
    needs: deploy-staging
    runs-on: ubuntu-latest
    environment: production          ← Requires reviewer approval
    if: github.ref_name == 'main'
    steps:
      - uses: actions/download-artifact@v4
        with:
          name: app-build
          path: ./dist
      - name: Deploy to production server
        run: ./scripts/deploy.sh production
        env:
          SERVER_URL: ${{ secrets.PROD_SERVER_URL }}
          DEPLOY_KEY: ${{ secrets.PROD_DEPLOY_KEY }}

Full Pipeline Flow Diagram

push to main branch
        │
        ▼
    test job
  (runs tests)
        │ passes
        ▼
    build job
  (compiles app, uploads artifact)
        │ passes
        ▼
  deploy-staging
  (downloads artifact, deploys to staging)
        │ passes
        ▼
  [APPROVAL GATE: reviewer clicks Approve]
        │ approved
        ▼
  deploy-production
  (downloads artifact, deploys to production)

Deployment Approval Gates

When you add required reviewers to a GitHub environment, the workflow pauses before jobs targeting that environment. GitHub sends an email to the reviewers. A reviewer visits the Actions tab, reviews the workflow run, and clicks Approve and deploy or Reject.

Approval gates give the team a chance to review staging results before releasing to production. They also enforce accountability — every production deployment has an auditable record of who approved it.

Deployment Rollback Strategy

Automate rollback by tagging releases and deploying the previous tag when a problem occurs:

jobs:
  rollback:
    name: Rollback Production
    runs-on: ubuntu-latest
    environment: production
    if: github.event_name == 'workflow_dispatch'
    steps:
      - name: Deploy previous release
        run: ./scripts/deploy.sh production ${{ inputs.rollback-version }}
        env:
          SERVER_URL: ${{ secrets.PROD_SERVER_URL }}

Trigger rollback manually from the Actions tab whenever you need to revert production to a known-good version.

Deployment Status Notifications

Notify your team after each deployment:

- name: Notify team of successful deployment
  if: success()
  run: |
    curl -X POST "${{ secrets.SLACK_WEBHOOK }}" \
    -H 'Content-type: application/json' \
    --data '{"text": "✅ Deployed to production successfully!"}'

- name: Alert team of deployment failure
  if: failure()
  run: |
    curl -X POST "${{ secrets.SLACK_WEBHOOK }}" \
    -H 'Content-type: application/json' \
    --data '{"text": "🚨 Production deployment FAILED. Investigate immediately."}'

Key CD Principles

  • Always deploy the artifact from the build job, never rebuild in the deploy job
  • Gate production deployments behind staging deployments
  • Use environment-specific secrets for each deployment target
  • Add required reviewers to your production environment
  • Send notifications on success and failure
  • Keep a deployment history using GitHub's environments page

Leave a Comment

Your email address will not be published. Required fields are marked *