57. Shipping it: cross-compilation, Docker, systemd, graceful shutdown
57.1 A minimal, secure Docker image
# Dockerfile
FROM golang:1.22 AS build
WORKDIR /src
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -o /app ./cmd/server
FROM scratch
COPY --from=build /app /app
COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
EXPOSE 8080
ENTRYPOINT ["/app"]
CGO_ENABLED=0 makes pure-Go builds avoid libc/cgo dependencies, which is
what makes FROM scratch — a base image with nothing in it, not even a
shell — practical. The result is often small and has far less OS/userland
attack surface: no shell to pop, no package manager, and fewer unrelated
libraries with their own CVEs.
57.2 Graceful shutdown
func main() {
srv := &http.Server{Addr: ":8080", Handler: mux}
go func() {
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatal(err)
}
}()
ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
defer stop()
<-ctx.Done()
log.Println("shutting down...")
shutdownCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
if err := srv.Shutdown(shutdownCtx); err != nil {
log.Println("forced shutdown:", err)
}
}
srv.Shutdown stops accepting new connections immediately, but lets
in-flight requests finish (up to the passed context's deadline) before
returning — the difference between a rolling deploy that drops active
requests and one that doesn't.
57.3 systemd unit for a bare-metal/VM deployment
# /etc/systemd/system/myapp.service
[Unit]
Description=My Go Service
After=network.target
[Service]
ExecStart=/opt/myapp/myapp
Restart=on-failure
User=myapp
AmbientCapabilities=CAP_NET_BIND_SERVICE
Environment=APP_PORT=8080
[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable --now myapp
journalctl -u myapp -f
57.4 Real-world example
The complete "boring but correct" production checklist for a typical Go
service built across this guide: small self-contained binary/container (57.1), explicit
http.Server timeouts (24.1), graceful shutdown on SIGTERM (57.2),
structured logs with a request ID (36.1), /debug/pprof mounted
internally (20.3), and runtime.NumGoroutine() exposed as a metric
(15.3). None of these are exotic — together, they're the difference
between a service that degrades observably and recovers cleanly, and one
that's a mystery every time it misbehaves.
Applied in the series: Linux guide, Chapter 82: a tested 14.8 MB distroless image, a hardened systemd unit (exposure score 3.1 vs 9.6 unhardened), and a Kubernetes manifest, each setting traced to the chapter that explains it.