Pokhara, NepalUTC+05:45 NPT
Open to conversations

Delivery

A small pipeline I can explain

A GitHub Actions pipeline for a small Go service — lint, test, build an image, push it, then deploy by hand.

2026
  • GitHub Actions
  • Docker
  • GHCR

Start smaller than you think

My first pipeline tried to do everything at once: test, build, scan, deploy, and notify. When it failed, I could not tell which part was at fault. I deleted it and started again with four steps I could explain out loud.

  1. Check out and set up a pinned Go version.
  2. Lint and test the service.
  3. Build and push a container image tagged with the commit SHA.
  4. Deploy a chosen image tag by hand.

That is the whole pipeline. It is not clever, but I can read it end to end and fix it when it breaks.

name: build

on:
  push:
    branches: [main]
  pull_request:

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

  image:
    needs: test
    if: github.ref == 'refs/heads/main'
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write
    steps:
      - uses: actions/checkout@v4
      - uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}
      - uses: docker/build-push-action@v6
        with:
          push: true
          tags: ghcr.io/${{ github.repository }}:${{ github.sha }}

Things that made it more reliable

  • Pin the runner image and tool versions. An unpinned toolchain turns a working pipeline into a moving target.
  • Tag images with the commit SHA. latest is convenient until you need to know what is actually running.
  • Keep the deploy manual at first. A button I press after reading the diff is slower, but it taught me what the pipeline was doing.
  • Fail early. If go vet fails, there is no reason to build an image.

What I would add next

A smoke test that starts the built image and requests its health endpoint would catch the embarrassing case where the build passes and the binary cannot start. After that, a small deployment job that runs only on a tag, so releases are still deliberate.