Skip to main content

GitHub Actions & CI/CD

Quick Summary

GitHub Actions is GitHub's built-in automation platform. It lets you run code (tests, builds, deployments) automatically when events happen in your repository — like pushing code, opening a PR, or creating a release. This is the backbone of modern CI/CD (Continuous Integration / Continuous Deployment).


Core Concepts

flowchart LR
E["Event<br/>(push, PR, schedule)"] --> W["Workflow<br/>(.yml file)"]
W --> J1["Job 1<br/>(build)"]
W --> J2["Job 2<br/>(test)"]
J1 --> S1["Step 1: Checkout"]
J1 --> S2["Step 2: Install deps"]
J1 --> S3["Step 3: Build"]
J2 --> S4["Step 1: Checkout"]
J2 --> S5["Step 2: Run tests"]
ConceptDefinition
WorkflowAn automated process defined in a .yml file
EventWhat triggers the workflow (push, PR, cron, manual)
JobA set of steps that run on the same machine
StepA single command or action within a job
ActionA reusable unit of code (from GitHub Marketplace or custom)
RunnerThe machine that executes the job (GitHub-hosted or self-hosted)

Your First Workflow

Create .github/workflows/ci.yml:

name: CI Pipeline

# When to run
on:
push:
branches: [main]
pull_request:
branches: [main]

# What to do
jobs:
test:
runs-on: ubuntu-latest

steps:
# Checkout the code
- uses: actions/checkout@v4

# Set up Node.js
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'

# Install dependencies
- run: npm ci

# Run tests
- run: npm test

# Run linter
- run: npm run lint

Common Triggers

on:
# Push to specific branches
push:
branches: [main, develop]
paths:
- 'src/**' # Only when src/ changes

# Pull request events
pull_request:
types: [opened, synchronize]

# Scheduled (cron)
schedule:
- cron: '0 2 * * 1' # Every Monday at 2 AM UTC

# Manual trigger
workflow_dispatch:
inputs:
environment:
description: 'Deploy target'
required: true
default: 'staging'

# On release
release:
types: [published]

Real-World Examples

Node.js CI/CD Pipeline

name: Node.js CI/CD

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

jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [18, 20, 22]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- run: npm ci
- run: npm test

deploy:
needs: test # Only run after tests pass
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm ci && npm run build
- name: Deploy to production
run: rsync -avz ./dist/ user@server:/var/www/app/
env:
SSH_KEY: ${{ secrets.SSH_PRIVATE_KEY }}

Docker Build & Push

name: Docker Build

on:
push:
tags: ['v*']

jobs:
docker:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_TOKEN }}

- uses: docker/build-push-action@v5
with:
push: true
tags: myapp:${{ github.ref_name }}

Secrets and Environment Variables

# Store secrets in: Repository Settings → Secrets → Actions

steps:
- name: Deploy
run: |
echo "$SSH_KEY" > key.pem
chmod 600 key.pem
ssh -i key.pem user@${{ secrets.SERVER_IP }} "deploy.sh"
env:
SSH_KEY: ${{ secrets.SSH_PRIVATE_KEY }}
Never hardcode secrets

Always use ${{ secrets.NAME }}. GitHub masks secret values in logs automatically.


Useful Actions (Marketplace)

ActionPurpose
actions/checkout@v4Clone the repository
actions/setup-node@v4Install Node.js
actions/setup-python@v5Install Python
actions/cache@v4Cache dependencies
docker/build-push-action@v5Build and push Docker images
peaceiris/actions-gh-pages@v4Deploy to GitHub Pages

Best Practices

  • Run tests on every PR — catch bugs before merging
  • Use a build matrix for multiple versions/platforms
  • Cache dependencies (actions/cache) — speeds up workflows significantly
  • Use branch protection rules — require CI to pass before merging
  • Keep secrets in GitHub Secrets — never in code
  • Use needs: for dependencies between jobs

What's Next

  1. Pages & Releases — Host documentation and publish releases
  2. Security Features — Dependabot, code scanning, and secret scanning