Building and Releasing Software
Overview
Go compiles to static binaries, making deployment simple. This chapter covers building, cross-compilation, and release workflows.
Basic Build
go build -o myapp ./cmd/serverProduction Build
go build \
-ldflags="-s -w" \
-o bin/myapp \
./cmd/serverFlags: - -s - Strip symbol table - -w - Strip DWARF debugging info
Version Injection
var (
version = "dev"
commit = "none"
date = "unknown"
)go build -ldflags="-X main.version=1.0.0 -X main.commit=$(git rev-parse HEAD)"Cross-Compilation
# Linux
GOOS=linux GOARCH=amd64 go build -o app-linux
# Windows
GOOS=windows GOARCH=amd64 go build -o app.exe
# macOS ARM
GOOS=darwin GOARCH=arm64 go build -o app-darwin
# All platforms
for os in linux darwin windows; do
for arch in amd64 arm64; do
GOOS=$os GOARCH=$arch go build -o bin/app-$os-$arch
done
doneDocker
# Multi-stage build
FROM golang:1.26-alpine AS builder
WORKDIR /app
COPY . .
RUN go build -o /app/server ./cmd/server
FROM alpine:latest
COPY --from=builder /app/server /server
ENTRYPOINT ["/server"]GitHub Actions
name: Release
on:
push:
tags: ['v*']
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: '1.26'
- run: go build -o app
- uses: softprops/action-gh-release@v1
with:
files: appGoReleaser
# .goreleaser.yaml
builds:
- main: ./cmd/server
goos: [linux, darwin, windows]
goarch: [amd64, arm64]
archives:
- format: tar.gzgoreleaser releaseSummary
| Task | Command |
|---|---|
| Build | go build -o app |
| Optimize | -ldflags="-s -w" |
| Cross-compile | GOOS=linux GOARCH=amd64 |
| Version inject | -ldflags="-X main.version=x" |
More examples
Version and commit via ldflags
mkdir -p /tmp/go-ldflags && cd /tmp/go-ldflags
go mod init example.com/ldflagsSave as main.go:
package main
import "fmt"
// Overridden at link time:
// go run -ldflags="-X main.version=1.2.3 -X main.commit=abc1234" .
var (
version = "dev"
commit = "none"
)
func main() {
fmt.Printf("version=%s commit=%s\n", version, commit)
}go run .
go run -ldflags="-X main.version=1.2.3 -X main.commit=abc1234" .Expected output:
version=dev commit=none
version=1.2.3 commit=abc1234
Build info from the binary itself
mkdir -p /tmp/go-buildinfo && cd /tmp/go-buildinfo
go mod init example.com/buildinfoSave as main.go:
package main
import (
"fmt"
"runtime/debug"
)
func main() {
bi, ok := debug.ReadBuildInfo()
if !ok {
fmt.Println("no build info")
return
}
fmt.Println("path:", bi.Path)
fmt.Println("go:", bi.GoVersion)
for _, s := range bi.Settings {
switch s.Key {
case "GOOS", "GOARCH", "-compiler", "CGO_ENABLED", "vcs.revision", "vcs.modified":
fmt.Printf("%s=%s\n", s.Key, s.Value)
}
}
}go run .Expected output (keys vary by toolchain/VCS):
path: example.com/buildinfo
go: go1.22.x
GOOS=...
GOARCH=...
-compiler=gc
CGO_ENABLED=...
Runnable example
Version injection and strip flags are the core of Go release builds. This program prints embedded metadata and reports binary-oriented build info.
mkdir -p /tmp/go-release-demo && cd /tmp/go-release-demo
go mod init example.com/release-demoSave as main.go:
package main
import (
"fmt"
"runtime"
"runtime/debug"
)
// Populated via: -ldflags="-X main.version=... -X main.commit=... -X main.date=..."
var (
version = "dev"
commit = "none"
date = "unknown"
)
func main() {
fmt.Printf("version=%s commit=%s date=%s\n", version, commit, date)
fmt.Printf("runtime=%s %s/%s\n", runtime.Version(), runtime.GOOS, runtime.GOARCH)
if info, ok := debug.ReadBuildInfo(); ok {
fmt.Println("module:", info.Path)
for _, s := range info.Settings {
switch s.Key {
case "-tags", "CGO_ENABLED", "GOOS", "GOARCH", "vcs.revision", "vcs.modified":
fmt.Printf("build %s=%s\n", s.Key, s.Value)
}
}
}
}go run -ldflags="-X main.version=1.0.0 -X main.commit=abc1234 -X main.date=2026-07-28" .
CGO_ENABLED=0 go build -trimpath -ldflags="-s -w \
-X main.version=1.0.0 \
-X main.commit=abc1234 \
-X main.date=2026-07-28" -o app .
./app
# Cross-compile matrix sample:
for pair in linux/amd64 linux/arm64 darwin/arm64 windows/amd64; do
GOOS=${pair%/*} GOARCH=${pair#*/}
ext=""; [ "$GOOS" = windows ] && ext=".exe"
CGO_ENABLED=0 go build -trimpath -ldflags="-s -w -X main.version=1.0.0" \
-o "app-${GOOS}-${GOARCH}${ext}" .
echo "built app-${GOOS}-${GOARCH}${ext}"
doneExpected output (illustrative):
version=1.0.0 commit=abc1234 date=2026-07-28
runtime=go1.22.x darwin/arm64
module: example.com/release-demo
build GOOS=darwin
build GOARCH=arm64
build CGO_ENABLED=0
What to notice
-X main.version=...rewrites string vars at link time—no runtime git dependency in production.-s -wstrips symbol/DWARF data; pair with-trimpathfor more reproducible artifacts.debug.ReadBuildInfoexposes module and VCS metadata embedded by modern Go toolchains.
Try next
- Compare
ls -lasizes of stripped vs unstripped builds. - Add a
-X main.commit=$(git rev-parse --short HEAD)one-liner to a release script.