Frontend Labs
⋯

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.


Generated from real-life-golang-guide.md · 8 Oct 2026 16:36