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
- Go to Settings → Environments in your repository
- Click New environment
- Name it (e.g.
stagingorproduction) - Optionally add required reviewers — people who must approve before deployment starts
- 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
