Skip to content

Your first Go pipeline

In this tutorial you'll take a brand-new Go project with no CI at all and, in four small steps, give it a full merge-request gate — lint, test, and five security scanners — plus a release pipeline ready to fire the moment you tag a release. By the end you'll have a green pipeline on a real merge request, and you'll understand what each piece is doing and why.

This tutorial assumes you already have a Go project on GitLab (any empty or near-empty one will do) and are comfortable with git and opening a merge request. It doesn't require any prior familiarity with GitLab CI/CD components.

What you'll end up with

A .gitlab-ci.yml at your project root that:

  • Runs golangci-lint and go test -race on every merge request.
  • Runs five security scanners on every merge request.
  • Builds and publishes a release with goreleaser whenever you push a version tag.

All from four include: blocks — no hand-rolled job definitions.

Step 1 — declare your stages

Every component is told which pipeline stage to run in, so start by declaring the stages you'll need. Create (or open) .gitlab-ci.yml at your project root:

.gitlab-ci.yml
stages:
  - lint
  - test
  - security
  - release

Step 2 — add the lint gate

go-lint runs golangci-lint over your packages. Add it:

.gitlab-ci.yml
stages:
  - lint
  - test
  - security
  - release

include:
  - component: gitlab.com/phpboyscout/cicd/[email protected]

That's the whole thing — no inputs are required. By default it runs only on merge-request pipelines, lints ./... (your whole module), and reads .golangci.yml from your project root if you have one (it works fine without one too, using golangci-lint's own defaults).

Commit this and open a merge request. You should see a golangci-lint job appear in the lint stage. If your project has no lint findings, it'll go green; if it does, you'll see them in the job log — worth fixing now, but not required to keep following this tutorial.

Step 3 — add the test gate

go-test runs go test -race -coverprofile and wires up a coverage badge. Add the include:

.gitlab-ci.yml
include:
  - component: gitlab.com/phpboyscout/cicd/[email protected]
  - component: gitlab.com/phpboyscout/cicd/[email protected]

Push this to your merge request branch. A go-test job should appear in the test stage. If your module has no _test.go files yet, that's fine — go test with nothing to test still exits 0.

Skip this if you don't have an end-to-end suite

go-test has an optional go-test-e2e job for a separate e2e test path, off by default (enable_e2e: false). You won't see it in your pipeline unless you turn it on — see the Reference page if you have an e2e suite to wire up later.

Step 4 — add the security gate

go-security runs five scanners — govulncheck, trivy, gitleaks, osv-scanner, and analyze (semgrep) — each as its own job:

.gitlab-ci.yml
include:
  - component: gitlab.com/phpboyscout/cicd/[email protected]
  - component: gitlab.com/phpboyscout/cicd/[email protected]
  - component: gitlab.com/phpboyscout/cicd/[email protected]

Push again. You should now see five jobs in the security stage. Unlike go-lint and go-test, these run on every merge request regardless of which files changed — see Explanation: security, always-on if you're curious why. If gitleaks or govulncheck finds something real, that's the gate doing its job — fix it before merging.

Step 5 — watch your merge request go green

At this point your merge request pipeline has seven jobs across three stages: one lint job, one (or two) test jobs, five security jobs. Once they're all green, you have a complete quality gate — this is the concrete milestone this tutorial set out to reach. Merge the MR.

Step 6 — wire the release pipeline

The last piece is goreleaser, which builds and publishes a release whenever you push a tag matching vX.Y.Z:

.gitlab-ci.yml
include:
  - component: gitlab.com/phpboyscout/cicd/[email protected]
  - component: gitlab.com/phpboyscout/cicd/[email protected]
  - component: gitlab.com/phpboyscout/cicd/[email protected]
  - component: gitlab.com/phpboyscout/cicd/[email protected]

goreleaser needs one more thing from you that the component can't provide: a .goreleaser.yaml describing what to build (see goreleaser's own documentation for that file's format — it's outside this component's scope). Once you have one, pushing a tag like v0.1.0 triggers the goreleaser job and publishes a release.

You don't need to push a real tag to finish this tutorial — the pipeline is correctly wired the moment the include: is in place and merged.

What's next