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-lintandgo test -raceon every merge request. - Runs five security scanners on every merge request.
- Builds and publishes a release with
goreleaserwhenever 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:
Step 2 — add the lint gate¶
go-lint runs golangci-lint over your
packages. Add it:
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:
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:
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:
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¶
- Automate the release-tagging itself with
releaser-pleaser— it opens a Release MR from your Conventional Commits and cuts the tag when you merge it, so you never typegit tagby hand. See How-to: publish a release. - Keep your component pins current automatically with
renovate-self— see How-to: pin and upgrade a component. - Building a Rust, Terraform, or Svelte-embedding project instead? The Reference section covers every track; the How-To Guides cover the same kinds of tasks this tutorial walked through, for those tracks.