Introducción
\nLa contenerización de aplicaciones Go con Docker es el estándar del desarrollo moderno. Sin embargo, los Dockerfile básicos a menudo distan de ser ideales: las imágenes resultan pesadas, la compilación es lenta y en producción surgen problemas con el registro, las señales y los healthchecks. En este artículo, analizaremos técnicas avanzadas: desde compilaciones multi-stage hasta el ajuste fino de redes y volúmenes. Aprenderá cómo hacer que la imagen de una aplicación Go sea mínima, segura y lista para producción.
\n\n1. Compilaciones multi-stage: imagen mínima en segundos
\nGo se compila en un binario estático, lo que es ideal para la contenerización. Sin embargo, si utiliza la imagen completa golang:1.22 para ejecutar, arrastra el compilador, el gestor de paquetes y las utilidades del sistema. La solución es multi-stage build.
Dockerfile básico con multi-stage
\n# Stage 1: compilación\nFROM golang:1.22-alpine AS builder\nWORKDIR /app\nCOPY go.mod go.sum ./\nRUN go mod download\nCOPY . .\nRUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o /app/main ./cmd/\n\n# Stage 2: imagen mínima\nFROM alpine:3.19\nRUN apk add --no-cache ca-certificates tzdata\nCOPY --from=builder /app/main /usr/local/bin/main\nEXPOSE 8080\nENTRYPOINT ["/usr/local/bin/main"]\n\n¿Por qué funciona esto?
\n- \n
- Alpine — 5 MB, conjunto mínimo de utilidades. \n
CGO_ENABLED=0— desactiva las dependencias de C, el binario se vuelve estático. \n -ldflags="-s -w"— elimina la información de depuración, reduciendo el tamaño del binario en un 30-40%. \n ca-certificatesytzdata— para solicitudes HTTPS y la hora correcta. \n
Resultado: imagen de 10-15 MB en lugar de 800+ MB. Compilación: 15-20 segundos (con almacenamiento en caché de dependencias).
\n\n2. Optimización del almacenamiento en caché y las capas
\nDocker almacena en caché las capas. Si copia todo el código antes de go mod download, cualquier cambio en los archivos .go invalida la caché de dependencias. El orden correcto:
COPY go.mod go.sum ./\nRUN go mod download\nCOPY . .\n\nTambién utilice .dockerignore:
.git\n*.md\ntest/\ncoverage/\n.env\nDockerfile\n\nEsto excluye archivos innecesarios del contexto de compilación, acelerando la transferencia al demonio de Docker.
\n\nTruco avanzado: almacenamiento en caché de módulos
\nSi utiliza Go modules, puede montar la caché:
\nFROM golang:1.22-alpine AS builder\nWORKDIR /app\nRUN --mount=type=cache,target=/go/pkg/mod \\\n --mount=type=cache,target=/root/.cache/go-build \\\n go build -ldflags="-s -w" -o /app/main ./cmd/\n\nEsto conserva los módulos descargados y los artefactos de compilación entre ejecuciones de docker build.
3. Healthchecks, señales y graceful shutdown
\nDocker puede verificar el estado del contenedor. Añada en el Dockerfile:
\n\nHEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \\\n CMD ["/usr/local/bin/main", "health"]\n\nEn el código Go, implemente el endpoint /healthz:
package main\n\nimport (\n "net/http"\n "os"\n "os/signal"\n "syscall"\n)\n\nfunc main() {\n http.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) {\n w.WriteHeader(http.StatusOK)\n w.Write([]byte("ok"))\n })\n\n srv := &http.Server{Addr: ":8080"}\n\n // Graceful shutdown\n go func() {\n sigint := make(chan os.Signal, 1)\n signal.Notify(sigint, syscall.SIGTERM, syscall.SIGINT)\n <-sigint\n srv.Shutdown(nil)\n }()\n\n srv.ListenAndServe()\n}\n\nImportante: la aplicación Go debe manejar correctamente SIGTERM (que Docker envía al detener el contenedor). Sin un graceful shutdown, corre el riesgo de perder datos o dejar conexiones abiertas.