GitHub Actions & CI/CD
What is CI/CD?
CI/CD stands for Continuous Integration / Continuous Deployment. It's the practice of automatically running tests and deploying code every time changes are pushed — removing the "works on my machine" problem and catching bugs before they reach users.
Before CI/CD, teams had a dedicated "release day" with hours of manual testing and deployment. Today, companies like GitHub, Netflix, and Shopify deploy hundreds of times per day — all automatically.
GitHub Actions Concepts
GitHub Actions is GitHub's built-in automation platform. You write YAML files that describe what should happen and when. GitHub runs them on managed virtual machines called runners.
.github/workflows/. Defines what to do and when. A repo can have multiple workflows.run:) or a prebuilt action (uses:).actions/checkout@v4 is the most used action.Your First Workflow
Create a file at .github/workflows/hello.yml in your repository. GitHub automatically detects it and runs it when the trigger fires.
# Workflow name — shown in GitHub UI name: Hello World # Trigger: run on every push to any branch on: push jobs: greet: # The virtual machine to run on runs-on: ubuntu-latest steps: # Step 1: check out your repository code - uses: actions/checkout@v4 # Step 2: run a shell command - name: Say hello run: echo "Hello from GitHub Actions!" # Step 3: show the current directory - name: List files run: ls -la
Real CI Pipeline — Node.js
Here's the standard CI workflow used by Node.js projects worldwide. It checks out code, installs dependencies, and runs tests automatically on every push and PR.
name: CI on: push: branches: [main] pull_request: branches: [main] jobs: test: runs-on: ubuntu-latest steps: # 1. Check out repository code - uses: actions/checkout@v4 # 2. Set up Node.js environment - uses: actions/setup-node@v4 with: node-version: '20' cache: 'npm' # cache node_modules for speed # 3. Install dependencies (clean install) - name: Install dependencies run: npm ci # 4. Run tests - name: Run tests run: npm test # Optional: run linting too - name: Lint run: npm run lint
npm ci instead of npm install. npm ci installs exactly what's in package-lock.json (reproducible), deletes node_modules first (clean), and is faster. It fails if package-lock.json is out of sync, which catches potential issues.Automated Deployment to GitHub Pages
GitHub Pages hosts static sites for free directly from your repository. Combined with Actions, you can auto-deploy on every push to main — no FTP, no manual uploads.
name: Deploy to GitHub Pages on: push: branches: [main] # Allow GitHub Pages deployment permissions: contents: read pages: write id-token: write jobs: deploy: environment: name: github-pages url: ${{ steps.deployment.outputs.page_url }} runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Setup Pages uses: actions/configure-pages@v4 - name: Upload artifact uses: actions/upload-pages-artifact@v3 with: path: '.' # upload root (or './dist' for built projects) - name: Deploy id: deployment uses: actions/deploy-pages@v4
Secrets & Environment Variables
API keys, tokens, and passwords should never be committed to your repository. GitHub Secrets store them securely and inject them into workflows at runtime — they're masked in logs and never exposed.
DEPLOY_TOKEN) and paste the value. Once saved, the value is never shown again — only the name is visible.name: Deploy with Secrets on: push: branches: [main] jobs: deploy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 # Access a secret with ${{ secrets.SECRET_NAME }} - name: Deploy to Netlify env: NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }} NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }} run: npx netlify-cli deploy --prod --dir=dist # Environment variables (non-secret, shared config) - name: Build with env vars env: NODE_ENV: production API_URL: https://api.example.com run: npm run build
Workflow Triggers
The on: key determines what starts your workflow. GitHub Actions supports a wide range of triggers — from simple push events to scheduled cron jobs and manual dispatches.
# Trigger on push to specific branches on: push: branches: [main, develop] paths: ['src/**'] # only if src/ changed # Trigger on pull requests on: pull_request: branches: [main] types: [opened, synchronize, reopened] # Scheduled trigger (cron syntax) on: schedule: # Every Monday at 9:00 AM UTC - cron: '0 9 * * 1' # Manual trigger (adds a "Run workflow" button in GitHub UI) on: workflow_dispatch: inputs: environment: description: 'Target environment' required: true default: 'staging' # Combine multiple triggers on: [push, pull_request]
minute hour day-of-month month day-of-week. * means "every". Examples: 0 0 * * * = midnight daily. 0 9 * * 1-5 = 9am weekdays. */15 * * * * = every 15 minutes. Use crontab.guru to build and test cron expressions visually.on: push: branches: [main] trigger?runs-on: ubuntu-latest specifies:uses: actions/checkout@v4 do in a workflow step?1. Create a GitHub repository (or use an existing one)
2. Create the folder:
mkdir -p .github/workflows3. Create
.github/workflows/ci.yml that:— Triggers on every push to main
— Uses
ubuntu-latest runner— Checks out the code
— Runs:
echo "Tests passed!"4. Commit and push — watch the Actions tab light up green
5. Copy your workflow's status badge URL from the Actions tab
6. Add the badge to your
README.md:7. Push the README update — your badge shows "passing"
💡 Show hints
- The Actions tab is visible at the top of your GitHub repo page
- Click on a workflow run to see the step-by-step log output
- Red X = something failed, Yellow circle = running, Green check = passed
- The badge URL format:
https://github.com/{user}/{repo}/actions/workflows/{filename}/badge.svg - If the workflow file has a YAML error, GitHub shows a red error in the Actions tab with a line number