Because
go build
shouldn't turn into a puzzle.
When you're building Go projects, it usually starts simple:
go build ./cmd/myapp
But then things get more serious…
- You add tags, CGO settings, and cross-compilation targets
- You inject version info with
-X
flags - You enable static builds with linker flags
- You need to compile inside Docker to match production
- And before you know it, your build command is a 200-character monster
So you write a Makefile
:
build:
GOOS=linux GOARCH=arm64 \
CGO_ENABLED=1 CC=GCC \
go build -ldflags "-s -w -X main.version=$(VERSION) -linkmode external -extldflags '-static'" \
-tags "prod" -o builds/linux/arm64/myapp ./cmd/myapp
or even worse:
docker-build:
docker run --rm -v "$$(pwd)":/usr/src/myapp -w /usr/src/myapp \
-e GIT_TERMINAL_PROMPT=0 -e "GOPRIVATE=github.com/acme_inc/*" docker.io/golang:1.23-alpine sh -c "\
apk add --no-cache musl-dev build-base git autoconf automake libtool && \
git config --global url.https://$(GIT_TOKEN):[email protected]/.insteadOf https://github.com/ && \
cd /usr/src/myapp && \
export CGO_ENABLED=1 CC=gcc CGO_LDFLAGS='-L/usr/local/lib -lm' && \
go build --tags \"linux\" -ldflags='-linkmode external -extldflags \"-static\" -s -w -X main.runMode=production -X main.version=$${VERSION}' myapp"
Now it works… but no one knows why those flags are there. Your CI fails silently. Quoting breaks. Debugging becomes a guessing game.
Instead of spreading logic across bash, make, CI scripts, and tribal knowledge, go-builder
lets you define your build process in a clear, declarative, self-documented YAML file:
###############################################################################
# Build config for myapp — all in one place, documented and reproducible
###############################################################################
# Where to place the final binaries (default: "builds/")
build_dir: builds
# Entry point package and output name
source: ./cmd/myapp
output: myapp
# Global environment variables used by Go and CGO
env:
CGO_ENABLED: "1"
CC: gcc
# Compiler and linker settings
build:
ldflags:
- "-s -w" # Strip debug info and reduce binary size
- "linkmode external -extldflags '-static'" # Fully static binary
vars:
main.version: "${VERSION:-dev}" # Embed build-time metadata or default to "dev"
main.commit: "${GIT_COMMIT:-unknown}" # Default to "unknown" if not set
tags: ["prod"]
trimpath: true # Remove file paths from binary
debug: false
# Targets for cross-compilation
targets:
- os: linux
arch: amd64
- os: linux
arch: arm64
# Optional Docker container to perform the build
docker:
image: golang:1.23-alpine # Use a consistent Go + musl toolchain
setup:
- apk add --no-cache build-base musl-dev # Add build dependencies
- git config --global url."https://${GIT_TOKEN}:[email protected]/".insteadOf https://github.com/
env:
GIT_TERMINAL_PROMPT: "0"
GOPRIVATE: github.com/acme_inc/*
📄 Every line is documented. 🧩 Every option is explainable. 🛠️ Every build is reproducible.
go install github.com/pablolagos/go-builder@latest
Create a template config:
go-builder --init
Then run:
VERSION=1.3.0 go-builder
Or just preview the build without running it:
go-builder --dry-run
With go-builder
, your build process becomes:
- Transparent — no more guessing where flags come from
- Portable — Docker-based builds included
- Maintainable — easily edit or extend
- Documented — every line can carry its why
Whether you're building for Alpine, for arm64
, or just want to finally understand your build pipeline —
go-builder
brings clarity to your Go builds.
📦 Install it:
go install github.com/pablolagos/go-builder@latest
⚙️ Start a config file:
go-builder --init
🚀 Run your build:
go-builder
🧪 Preview without running:
go-builder --dry-run
With go-builder
, your builds become readable, repeatable, and robust.
Just like your Go code.
Need a different file? Use --config path/to/file.yml
.
✓ | What it does |
---|---|
Declarative YAML | (.gobuilder.yml ) with full inline docs. |
Build matrix | generate binaries for any GOOS/GOARCH set. |
Link-time vars | map → -X 'name=value' . |
Placeholder expansion | ${VAR} / ${VAR:-default} everywhere. |
Per-target & global env | CC , CGO_LDFLAGS , etc. |
Auto build directory | (default builds/ ) & automatic .gitignore handling. |
Dry-run | (--dry-run or build.debug:true ) to print commands only. |
Init template | (--init ) writes a richly commented sample YAML. |
No Git assumptions | use any branch, tag or detached HEAD. |
Docker builds | run builds in a Docker container with custom setup. |
Cross-compilation | compile for any GOOS/GOARCH target. |
Static linking verification | verify binaries are statically linked using file command. |
build_dir: builds
source: ./cmd/myapp
output: myapp-${VERSION:-dev} # binary compiled name: myapp-dev if $VERSION is defined it will replace "dev". Usage of ${VAR:-default} is optional
env:
CGO_ENABLED: "1"
build:
ldflags: ["-s -w"]
vars:
main.version: "${VERSION:-dev}"
main.commit: "${COMMIT_SHA:-local}"
tags: ["prod"]
trimpath: true
verify_static: false # Verify that the binary is statically linked (uses 'file' command)
targets:
- os: linux
arch: amd64
verify_static: true # Override global setting for this target only
- os: darwin
arch: arm64
- os: windows
arch: amd64
Run go-builder
and you’ll get:
builds/linux/amd64/myapp-dev
builds/darwin/arm64/myapp-dev
builds/windows/amd64/myapp-dev.exe
For example, to set a custom version:
VERSION=1.2.3 go-builder
This will produce binaries like:
builds/linux/amd64/myapp-1.2.3
builds/darwin/arm64/myapp-1.2.3
builds/windows/amd64/myapp-1.2.3.exe
All artifacts live under build_dir
, already ignored by Git.
Flag | Description |
---|---|
--config FILE |
Use FILE instead of .gobuilder.yml . |
--dry-run, -n |
Print go build … commands but don’t execute. |
--init, -i |
Create .gobuilder.yml from the embedded template. |
We are thrilled to know that go-builder
is being used across various projects and companies to simplify Go builds! Here's a list of some companies and teams leveraging the power of go-builder
:
- Pyxsoft - A software development company that builds cybersecurity and data protection solutions for cPanel servers
- PowerWAF - A web application firewall that protects against online threats
Add your company here!
We’d love to highlight more users of go-builder
in this section. If your organization or team uses go-builder
, let us know! Open an issue to share your experience and we'll happily feature you here!
Contributions are welcome!
MIT © 2025 Pablo Lagos