Every Go service I start tends to need the same plumbing: a cmd/ entry point, a Dockerfile, a Taskfile, golangci-lint config, CI workflows, a release pipeline. Copy-pasting from the previous project works, but it gets stale fast. gonew — the official Go scaffolding tool — turns any module into a reusable starting point.
What gonew Is
gonew is an experimental command from the Go team. It clones a module, rewrites its module path to whatever you choose, and drops the result in a fresh directory — no Git history, no upstream link, just code you own from the first commit.
Install it once:
go install golang.org/x/tools/cmd/gonew@latestThe official example uses the helloserver sample from golang.org/x/example:
gonew golang.org/x/example/helloserver example.com/myserver
cd ./myserverThat’s it — you have a working module rooted at example.com/myserver, with all imports rewritten and ready to build.
Why Not Just git clone?
A clone gives you the source, but it also gives you:
- The original commit history (irrelevant to your project)
- The upstream remote (easy to push back to by accident)
- The original module path scattered across imports
gonew skips all of that. It downloads the template through the Go module proxy, rewrites go.mod and every import that referenced the old path, and writes the files out clean. You then git init yourself and the new repo starts at commit zero.
A Real-World Template
The official helloserver is fine for demos but only contains a handful of files. To get a feel for what gonew is actually good for, point it at something more substantial. I keep template-api around for this exact purpose — a REST API skeleton with everything I’d otherwise rebuild every time:
gonew github.com/sgaunet/template-api gitlab.com/myuser/awesome-api
cd awesome-api
git init
git add .
git commit -m "initial commit"
git remote add origin git@gitlab.com:myuser/awesome-api.git
git push -u origin masterWhat you inherit:
cmd/server/main.go— minimal entry pointinternal/— domain, database, middleware, app errorspkg/— handlers, services, config, webserver wiring (hexagonal-style separation)sqlc.yaml+queries/+internal/database/db/migrations/— SQL pipeline ready to goTaskfile.yaml+Taskfile_dev.yml— build, lint, test, release tasks.golangci.yml+.pre-commit-config.yaml— quality gates wired up.goreleaser.yml+Dockerfile— multi-arch binary and image releases.github/workflows/— linter, coverage, snapshot, release, vulnerability scan
Every reference to github.com/sgaunet/template-api becomes gitlab.com/myuser/awesome-api. Internal packages keep working without manual sed surgery.
Building Your Own Template
Anything gonew can fetch through go install is a valid template — public modules and modules in your GOPRIVATE set both work. A few rules I follow when building one:
Keep it opinionated. A template that tries to support every database, every router, and every config library ends up being a worse version of all of them. Pick one stack, document it in the README, and let users delete what they don’t need.
Don’t hide secrets in go.mod. gonew rewrites the module path but leaves dependency versions alone. If your template pins a private fork, downstream users will hit a 404 the first time they run go mod tidy.
Use internal/ for things you don’t want users to reach for. Once gonew rewrites the module path, internal/ packages are no longer importable from the original template-api namespace — they’re now private to the new project. That’s exactly what you want.
Tag a release. gonew resolves through the module proxy, so untagged templates work but you’ll get the latest commit on default branch. Tagging gives users a way to pin: gonew github.com/you/template@v1.2.0 ....
Caveats
gonewis still flagged experimental. The CLI surface is small and stable in practice, but don’t be surprised if flags change.- It does not preserve the
.gitdirectory. That’s by design — but it also means hooks, CI permissions, and branch protections need to be set up fresh on the new repo. - It will not run
go mod tidyfor you. Always do that next.
Wrap-Up
For one-off prototypes, plain go mod init is still the right call. But the moment you find yourself copying the same Taskfile, the same lint config, and the same release pipeline into every new service, freeze that scaffolding into a template repo and reach for gonew instead. The first project you bootstrap with it pays for the time spent building the template.