Setting Up CI/CD with GitHub Actions

A continuous integration pipeline earns its keep the first time it catches a broken build before your teammates pull it. GitHub Actions lets you build that pipeline with a single YAML file in your repository, no external service to wire up.

Anatomy of a workflow

Workflows live in .github/workflows/. Each file defines triggers, jobs, and the steps inside them. A minimal Go pipeline that runs on every push and pull request looks like this:

name: CI
on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-go@v5
        with:
          go-version: '1.22'
      - run: go test ./...
      - run: go vet ./...

Cache to stay fast

A pipeline that re-downloads every dependency on each run gets slow enough that people start skipping it. actions/setup-go caches the module download for you; for larger toolchains, the actions/cache action keyed on your lockfile hash is the difference between a 30-second run and a five-minute one.

Gate the merge

A pipeline that does not block merges is decoration. In branch protection settings, mark your test job as a required status check. Now a red build physically prevents the merge button from going green, which is the entire point.

Separate deploy from test

Keep deployment in its own job that depends on tests passing and only runs on the main branch:

deploy:
  needs: test
  if: github.ref == 'refs/heads/main'
  runs-on: ubuntu-latest
  steps:
    - run: ./scripts/deploy.sh

Store secrets in the repository's encrypted secrets, never in the YAML. Start with test-on-every-push, add deploy once you trust the signal.

  • #ci-cd
  • #github
  • #devops