GitHub Actions is a powerful CI/CD platform that starts with a generous free tier and is unlimited for public repositories. Run tests on every push, build containers, deploy to production — all in a single YAML file. This guide walks through a complete CI/CD pipeline for a Node.js project from scratch.
Basic Workflow Structure
Related guides: PostgreSQL optimization · Advanced Git commands · What is Redis · Deploying with Docker · Docker Compose guide
# .github/workflows/ci.yml
name: CI
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 run lint
- run: npm test
Matrix Strategy
Run the same job in parallel across multiple Node versions, operating systems or environments.
jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
node: [18, 20, 22]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node }}
- run: npm ci && npm test
Docker Image Build + Registry Push
jobs:
docker:
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v4
- uses: docker/setup-buildx-action@v3
- uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- uses: docker/build-push-action@v5
with:
context: .
push: true
tags: |
ghcr.io/${{ github.repository }}:latest
ghcr.io/${{ github.repository }}:${{ github.sha }}
cache-from: type=gha
cache-to: type=gha,mode=max
Secret Management
Define secrets under Settings → Secrets and variables → Actions. They are automatically masked in logs. For environment-specific secrets, use environments (manual approval can be added for promotion to production).
jobs:
deploy:
runs-on: ubuntu-latest
environment: production
steps:
- run: echo ${{ secrets.DATABASE_URL }}
- uses: appleboy/ssh-action@v1
with:
host: ${{ secrets.SSH_HOST }}
username: ${{ secrets.SSH_USER }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
script: |
cd /var/www/app
git pull
npm ci --production
pm2 reload ecosystem.config.js
ed25519 format. When you paste it into a GitHub secret, include the BEGIN/END lines and do not let the line breaks get mangled.Service Containers (Testing with a DB)
jobs:
integration-test:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:16
env:
POSTGRES_PASSWORD: test
ports: ['5432:5432']
options: --health-cmd pg_isready --health-interval 5s --health-timeout 5s --health-retries 5
redis:
image: redis:7
ports: ['6379:6379']
env:
DATABASE_URL: postgres://postgres:test@localhost:5432/postgres
REDIS_URL: redis://localhost:6379
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: 20 }
- run: npm ci
- run: npm run migrate
- run: npm run test:integration
Cache Optimization
- uses: actions/cache@v4
with:
path: |
~/.npm
node_modules
.next/cache
key: ${{ runner.os }}-build-${{ hashFiles('**/package-lock.json') }}
restore-keys: ${{ runner.os }}-build-
Reusable Workflows
# .github/workflows/reusable-deploy.yml
name: Deploy
on:
workflow_call:
inputs:
env: { required: true, type: string }
secrets:
SSH_KEY: { required: true }
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- run: echo Deploying to ${{ inputs.env }}
# Call it from the main workflow
jobs:
prod:
uses: ./.github/workflows/reusable-deploy.yml
with: { env: production }
secrets: inherit
Workflow Dispatch (Manual Trigger)
on:
workflow_dispatch:
inputs:
environment:
type: choice
options: [staging, production]
version:
type: string
default: latest
Self-Hosted Runners
The free GitHub-hosted runners have limits (2,000 minutes/month for public repos, more for paid plans). For heavier jobs, run your own self-hosted runner — Settings → Actions → Runners → New self-hosted runner.
Common Pitfalls
GITHUB_TOKENpermissions are restricted by default — addpermissions:to the workflow when needed- Concurrent runs can clash when deploying to the same environment — control with
concurrency: actions/checkout@v4fetches only one commit by default — usefetch-depth: 0for full history- Use
npm ciinstead ofnpm installfor dependency installs — it honors the lockfile exactly
Modern Software Development and DevOps Practices
Professional software development rests on three pillars: version control (Git + GitHub/GitLab pull request flow, mandatory code review), CI/CD pipeline (automated test + lint + build + deploy), and observability (Sentry/Datadog/Grafana for logs, metrics, traces). The test pyramid (unit > integration > e2e) ensures code quality, microservice architecture uses Docker containers with Kubernetes orchestration, and REST or GraphQL APIs follow OpenAPI/GraphQL Schema contracts. Across the SDLC (requirements → design → implementation → test → deploy → maintenance), Agile/Scrum sprints last 1-2 weeks while DevOps teams practice continuous delivery.
Reach out for GitHub Actions, GitLab CI or Jenkins pipeline design and implementation. Contact us