A modern, multi-module Go workspace template for monorepos with shared libraries and one or more deployable services.
This template uses a go.work workspace so that several Go modules — typically a handful of deployable services and a few shared libraries — live side by side and import each other through their canonical module paths, with no published versions or replace gymnastics in user-facing code.
It ships pre-wired with:
- Pinned Go toolchain and tooling via mise
- Task recipes that iterate every module in the workspace
- golangci-lint v2 (per-module) with integrated formatters (gofumpt + goimports)
- GoReleaser v2 with multi-platform binaries, SBOMs, and per-service builds
- govulncheck running per module on every CI build
- Distroless
nonrootDocker image that builds any service via--build-arg SERVICE=… - Conventional-commits driven CHANGELOG (git-cliff)
- License header enforcement (hawkeye) —
.gofiles use//line comments - Dependabot for every
go.mod, GitHub Actions, and Docker
Note: This template targets multi-module monorepos. For a small single-module project, use
simple-golang-templateinstead.
.
├── go.work # workspace declaration
├── services/ # deployable binaries; each is its own module
│ └── app/
│ ├── go.mod # module .../services/app
│ ├── cmd/app/main.go # entry point
│ └── internal/
│ └── version/ # build-time stamps via -ldflags
├── libs/ # shared libraries; each is its own module
│ └── shared/
│ ├── go.mod # module .../libs/shared
│ └── log/ # example: slog wrapper imported by services
├── deploy/
│ ├── Dockerfile # distroless, non-root, ARG SERVICE selects which one
│ └── .dockerignore
├── docs/
├── .github/
│ ├── dependabot.yml
│ └── workflows/{ci,cd,pr-check-ci}.yml
├── .mise.toml # pinned Go + tool versions
├── Taskfile.yml # workspace-aware recipes
├── .golangci.yml # shared lint config (run per module)
├── .goreleaser.yaml # release pipeline
├── cliff.toml # CHANGELOG generation
├── licenserc.toml # license header rules
├── .editorconfig
├── .gitignore
├── LICENSE # Apache-2.0
├── CHANGELOG.md
└── README.md
- Create the directory, e.g.
services/worker/cmd/worker/main.go. cd services/worker && go mod init github.com/itscheems/golang-template/services/worker.- Add it to the workspace:
go work use ./services/worker. - If it imports another local module, add a
require … v0.0.0-…line and a matchingreplacedirective in itsgo.mod(seeservices/app/go.modfor the pattern). The replace lets standalone tooling (go mod tidy, single-module builds) resolve cross-module imports the same waygo.workdoes. - Update
MODULESinTaskfile.ymland thegomodentries in.github/dependabot.yml. - For releases, add a new
builds:entry to.goreleaser.yamlpointing at the newdir:.
All tool versions are pinned in .mise.toml. The fast path:
# Install mise once: https://mise.jdx.dev/getting-started.html
mise install # installs Go, golangci-lint, goreleaser, task, gofumpt, ...
mise activate # or add `eval "$(mise activate zsh)"` to your shell rcManual installation needs at least: Go 1.26.x, Task 3.x, golangci-lint v2.x, and hawkeye.
# 1. Use this template on GitHub ("Use this template" → "Create a new repository")
# 2. Clone your new repo
git clone https://github.com/<you>/<your-repo>.git
cd <your-repo>
# 3. Rewrite the module path (one editor pass)
OLD=github.com/itscheems/golang-template
NEW=github.com/<you>/<your-repo>
git ls-files | xargs sed -i '' "s|$OLD|$NEW|g" # macOS; drop '' on Linux
go work sync && (cd services/app && go mod tidy)
# 4. Install dev tools
mise install
# 5. Run everything CI runs, locally
task citask # list available tasks
task run # go run services/app/cmd/app
task run SERVICE=worker # … any other service
task build # build dist/app
task fmt # gofumpt + goimports + license headers
task lint # golangci-lint (per module) + hawkeye + go vet
task test # race-detector + per-module coverage
task audit # govulncheck + gosec across every module
task ci # lint + test + build (everything CI runs)
task release-snapshot # dry-run a GoReleaser buildAliases: task l (lint), task t (test).
# Per-module
cd services/app
go run ./cmd/app
go build -o ../../dist/app ./cmd/app
go test -race ./...
golangci-lint run
# Workspace-wide
go work syncBuild any service by passing --build-arg SERVICE=…. Build context must be the repo root so the workspace and shared libs are visible.
docker build -f deploy/Dockerfile --build-arg SERVICE=app -t golang-template:dev .
docker run --rm golang-template:devThe image is gcr.io/distroless/static-debian12:nonroot — no shell, no package manager, runs as UID nonroot (65532).
Push a semver tag and the cd.yml workflow will:
- Generate
CHANGELOG.mdwith git-cliff and commit it back tomain. - Run GoReleaser to build binaries for
linux|darwin|windows×amd64|arm64for every service in.goreleaser.yaml. - Generate a CycloneDX SBOM per archive (Syft).
- Publish a GitHub Release with checksums, archives, and notes.
git tag v0.1.0
git push origin v0.1.0For per-service release tags (e.g. app/v0.1.0, worker/v0.2.0), enable GoReleaser's monorepo mode.
- Commits: Conventional Commits — drives changelog grouping.
- Go style:
gofumpt(superset ofgofmt) +goimportswith-local github.com/itscheems/golang-template. - Layout: Go's
internal/rule is honoured —services/app/internal/...is unimportable from outsideservices/app. - Cross-module imports: shared code goes under
libs/, requires both arequireline and areplacedirective in the consumer'sgo.mod(see "Adding a new module"). - License headers: every
.gofile carries the Apache-2.0 header; enforced byhawkeye checkin CI.
Licensed under the Apache License, Version 2.0. See LICENSE for details.
Part of a small family of project templates by @itscheems. Pick the one matching your project shape:
| Single-package / single-module | Multi-package / multi-module monorepo | |
|---|---|---|
| Rust | simple-rust-template |
rust-template — Cargo workspace |
| Go | simple-golang-template |
golang-template — go.work multi-module |