GitHub Actions: From Basics to Production CI/CD
GitHub Actions has become the default CI/CD platform for teams using GitHub — it runs in the same system that hosts your code, integrates with pull requests and branch protection, and eliminates the operational overhead of running Jenkins or a separate CI server. This guide covers production-grade workflow patterns: matrix testing, secrets management, dependency caching, VPS deployment via SSH, Docker image publishing, and release automation.
Workflow YAML Structure
Every GitHub Actions workflow is a YAML file in .github/workflows/. The core structure:
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 buildThe on section controls triggers. The jobs section defines parallel execution units. Steps within a job run sequentially. Use actions/checkout@v4 as the first step in every job that needs code access — it checks out the triggering commit.
Matrix Builds
Matrix builds run your test suite across multiple Node.js versions, operating systems, or environment combinations without duplicating workflow YAML:
jobs:
test:
strategy:
matrix:
node: [18, 20, 22]
os: [ubuntu-latest, windows-latest]
fail-fast: false
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node }}
- run: npm ci && npm testSet fail-fast: false to allow all matrix combinations to complete even if one fails — this gives you complete visibility into which combinations have issues.
Secrets Management
Never hardcode credentials in workflow files. Store secrets in repository Settings → Secrets and Variables → Actions. Reference them in workflows as ${{ secrets.SECRET_NAME }}. For organization-wide secrets shared across repositories, use Organization Secrets. Environment-specific secrets (staging vs production) use GitHub Environments with environment-level protection rules:
jobs:
deploy:
environment: production
steps:
- run: deploy.sh
env:
SERVER_HOST: ${{ secrets.PROD_SERVER_HOST }}
SSH_KEY: ${{ secrets.PROD_SSH_KEY }}Caching Dependencies
The actions/setup-node action with cache: 'npm' automatically caches node_modules based on package-lock.json. For workflows not using setup-node, use the cache action directly:
- uses: actions/cache@v4
with:
path: ~/.npm
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-Proper caching reduces install time from 2–3 minutes to 15–30 seconds on cache hits, making PR checks feel fast and reducing GitHub Actions billing.
Deploying to VPS via SSH
For VPS deployment, use SSH to pull the latest code and restart services. Store your private SSH key as a secret and use it with ssh-action:
- name: Deploy to production
uses: appleboy/ssh-action@v1
with:
host: ${{ secrets.SERVER_HOST }}
username: deploy
key: ${{ secrets.SSH_PRIVATE_KEY }}
script: |
cd /var/www/myapp
git pull origin main
npm ci --only=production
npm run build
pm2 restart myappCreate a dedicated deploy user with limited permissions — access only to the application directory, no sudo. This limits blast radius if the deploy key is ever compromised.
For cPanel Deployment
Deploy to cPanel via Git Version Control or FTP. cPanel's Git integration can automatically deploy when you push to a tracked branch. Alternatively, use cURL to trigger a cPanel API call from GitHub Actions to run a deployment hook script.
Docker Image Builds and Publishing
- name: Build and push Docker image
uses: docker/build-push-action@v5
with:
context: .
push: ${{ github.ref == 'refs/heads/main' }}
tags: |
ghcr.io/${{ github.repository }}:latest
ghcr.io/${{ github.repository }}:${{ github.sha }}
cache-from: type=gha
cache-to: type=gha,mode=maxUse GitHub Container Registry (ghcr.io) for free private image storage. The build cache from GitHub Actions cache service dramatically reduces build times on subsequent pushes.
Release Automation
Automate release creation when a version tag is pushed:
on:
push:
tags:
- 'v*'
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Create Release
uses: softprops/action-gh-release@v2
with:
generate_release_notes: true
files: dist/*.zipBranch Protection and PR Checks
Configure branch protection rules in repository Settings → Branches for the main branch: require status checks to pass before merging (select your CI job names), require PR reviews, and prevent force pushes. This ensures no code reaches production without passing tests and receiving review — the minimum viable quality gate for any production codebase.
GitHub Actions rewards investment in workflow quality. Well-structured workflows with proper caching, matrix testing, and automated deployment create a development velocity multiplier that pays dividends on every code change your team makes.