Si tu Dockerfile de Go empieza con FROM golang y termina con CMD ["./app"], tu imagen probablemente pesa más de 800 MB: incluye el compilador, el código fuente y la caché de módulos. Nada de eso hace falta para ejecutar un binario. Con un build multi-stage compilamos en una imagen y copiamos solo el binario a otra mínima.
El Dockerfile
# syntax=docker/dockerfile:1
ARG GO_VERSION=1.24
FROM --platform=$BUILDPLATFORM golang:${GO_VERSION}-alpine AS build
WORKDIR /src
COPY go.mod go.sum ./
RUN --mount=type=cache,target=/go/pkg/mod \
go mod download
COPY . .
ARG TARGETOS
ARG TARGETARCH
RUN --mount=type=cache,target=/go/pkg/mod \
--mount=type=cache,target=/root/.cache/go-build \
CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} \
go build -trimpath -ldflags="-s -w" -o /out/app .
FROM gcr.io/distroless/static-debian12:nonroot
COPY --from=build /out/app /app
USER nonroot:nonroot
EXPOSE 8080
ENTRYPOINT ["/app"]
Qué hace cada parte
Dos FROM = dos etapas. La primera (build) tiene Go completo. La segunda parte de una imagen casi vacía y solo recibe /out/app con COPY --from=build. Todo lo demás se descarta.
go.mod y go.sum antes que el código. Docker cachea cada instrucción. Si copias todo de una vez, cualquier cambio en un .go invalida la descarga de módulos. Separándolos, go mod download solo se repite cuando cambian tus dependencias.
RUN --mount=type=cache. Es una función de BuildKit (activado por defecto desde Docker 23). Monta una caché persistente durante el build sin guardarla en la imagen. La caché de compilación de Go (/root/.cache/go-build) hace que recompilar tras un cambio pequeño tarde segundos.
CGO_ENABLED=0. Genera un binario estático que no depende de la libc del sistema. Es lo que permite usar una imagen final sin libc.
-trimpath -ldflags="-s -w". -trimpath quita las rutas de tu máquina del binario y -s -w elimina la tabla de símbolos y la información de depuración. El binario queda notablemente más chico.
--platform=$BUILDPLATFORM + TARGETOS/TARGETARCH. Go compila de forma cruzada sin esfuerzo. Con esto, el compilador corre nativo en tu máquina y genera el binario para la arquitectura que pidas, sin emular con QEMU:
docker buildx build --platform linux/amd64,linux/arm64 -t mi-servicio .
distroless/static-debian12:nonroot. Una imagen de unos 2 MB que contiene certificados CA (para llamar APIs HTTPS), datos de zona horaria y un usuario sin privilegios. No tiene shell ni gestor de paquetes: si alguien compromete tu app, no tiene herramientas para moverse.
Un detalle importante
Como distroless no tiene shell, no puedes usar HEALTHCHECK CMD curl .... Define el healthcheck en Kubernetes o compose apuntando a tu endpoint, o haz que tu binario acepte un flag como -healthcheck y usa HEALTHCHECK CMD ["/app", "-healthcheck"].
Compruébalo
docker build -t mi-servicio .
docker images mi-servicio
Para un servicio HTTP típico verás una imagen de entre 10 y 25 MB. Menos tamaño significa despliegues más rápidos, menos costo de almacenamiento y menos superficie de ataque.
¿Te sirvió? Este ejemplo es parte del Pack Dockerfiles Optimizados: paga lo que quieras, desde 2 USD. Más recursos gratis en GitHub.
Top comments (0)