WritingHow to Set Up a GitHub Actions CI/CD Pipeline from Scratch — LinkIT
4 min readci-cd, github-actions, deployment, automation

How to Set Up a GitHub Actions CI/CD Pipeline from Scratch

Set up GitHub Actions workflows for tests, release builds, deployment, and review gates.

GitHub Actions can run tests, build release artifacts, and deploy changes through a defined workflow. Setting up those steps makes the release process repeatable and records what happened on each run.

This walkthrough starts with a workflow file and adds build, test, and deployment stages.

Setting Up a GitHub Actions CI/CD Pipeline: The Foundation

GitHub Actions organizes work into workflows — YAML files stored in .github/workflows/. Each workflow runs on a trigger (a push, a pull request, a schedule) and contains one or more jobs that execute on virtual runners.

Create your first workflow file at .github/workflows/ci.yml.

Step 1: Define Your Trigger

name: CI/CD Pipeline
 
on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

This runs the pipeline whenever code is pushed to main or a pull request targets main. Pull requests get CI feedback before merge; pushes to main trigger the full deploy path.

Step 2: Run Tests in a Dedicated Job

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

Always use npm ci instead of npm install in CI — it installs exactly what the lockfile specifies and fails if the lockfile is out of sync. Cache dependencies using the cache key on the setup action; this alone can cut 60-90 seconds off a typical Node.js job.

Step 3: Build and Validate

Add a build job that depends on test passing:

  build:
    needs: 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 build
      - uses: actions/upload-artifact@v4
        with:
          name: dist
          path: dist/

Upload the build artifact so the deploy job can download it instead of rebuilding. This keeps your artifacts reproducible — the exact same bytes that passed tests are the bytes that ship.

Step 4: Deploy Only on Main

  deploy:
    needs: build
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main' && github.event_name == 'push'
    environment: production
    steps:
      - uses: actions/download-artifact@v4
        with:
          name: dist
      - run: ./scripts/deploy.sh
        env:
          DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}

The if condition is critical. Pull requests run test and build but never trigger deploy. Only a direct push to main reaches production. Secrets are stored in GitHub's encrypted secrets store and injected as environment variables at runtime — never hardcode tokens in workflow files.

Managing Environment Variables Safely

Store all sensitive values in Settings > Secrets and variables > Actions. Reference them as ${{ secrets.MY_SECRET }}. For non-sensitive configuration that differs per environment (API base URLs, feature flags), use Variables rather than Secrets so you can inspect them.

Speeding Up the Pipeline

  • Cache aggressively. Node modules, pip packages, Go module caches — cache all of them.
  • Parallelize independent jobs. Tests and lint checks do not need to run sequentially.
  • Limit the trigger scope. Use paths filters so a docs-only commit does not trigger a full deploy.
  • Set timeouts. Add timeout-minutes: 10 to jobs so a stuck test does not burn runner minutes indefinitely.

What a Mature Pipeline Looks Like

A production pipeline typically has five stages:

  1. Lint — enforce code style, fail fast
  2. Test — unit and integration tests in parallel
  3. Security scan — dependency audit, static analysis
  4. Build — produce a reproducible artifact
  5. Deploy — push the artifact, run smoke tests, notify on failure

Start with the middle three and add the outer two once the core flow is stable.

Common First-Time Mistakes

  • Using npm install instead of npm ci in CI
  • Storing secrets in the repository or workflow file
  • Deploying from feature branches instead of main
  • Not caching dependencies, making every run slow
  • Skipping a smoke test after deploy — you find out production is broken from users

Maintain the workflow as the application changes, and test its failure and recovery paths as well as successful releases.

Discuss deployment infrastructure with LinkIT