CI/CD with GitHub Actions: A Practical Guide
CI/CD with GitHub Actions: A Practical Guide
GitHub Actions automates your build, test, and deployment pipeline directly in your repository. This guide covers the essentials for setting up a production-ready CI/CD workflow.
Basic Workflow Structure
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
build:
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 lint
- run: npm run build
- run: npm test
Key Concepts
Triggers
on:
push: # On push to branches
pull_request: # On PR creation/update
schedule: # Cron-based
- cron: "0 6 * * 1"
workflow_dispatch: # Manual trigger
Caching
Cache dependencies to speed up workflows:
- uses: actions/setup-node@v4
with:
node-version: 20
cache: npm # Built-in npm/yarn/pnpm caching
Secrets
Store sensitive values in Settings → Secrets:
- run: echo "Deploying..."
env:
SSH_KEY: ${{ secrets.DEPLOY_SSH_KEY }}
API_TOKEN: ${{ secrets.API_TOKEN }}
Matrix Strategy
Test across multiple configurations:
strategy:
matrix:
node-version: [18, 20, 22]
os: [ubuntu-latest, macos-latest]
Production Deployment
Deploy to a Server via SSH
deploy:
needs: build
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v4
- name: Deploy via SSH
uses: appleboy/ssh-action@v1
with:
host: ${{ secrets.SERVER_HOST }}
username: ${{ secrets.SERVER_USER }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
script: |
cd /app
git pull
npm ci --production
npm run build
pm2 restart app
Deploy Docker Image
- name: Build and push Docker image
run: |
docker build -t myapp:latest .
docker tag myapp:latest registry.example.com/myapp:latest
docker push registry.example.com/myapp:latest
Best Practices
Keep workflows fast — cache aggressively, parallelize independent jobs, skip unnecessary steps on draft PRs. Use specific action versions — pin to commit SHAs for security:uses: actions/checkout@abc123 instead of @v4.
Fail fast — put lint and type-check before slower test suites.
Use concurrency — cancel in-progress runs for the same PR:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
Conclusion
GitHub Actions is powerful and flexible. Start with a basic lint + build + test workflow, add deployment for main branch pushes, and optimize with caching and concurrency.