Adopción de Docker en producción: lecciones de los primeros despliegues

Qué se hace mal cuando un equipo pasa de máquinas virtuales a contenedores: imágenes obesas, secretos en variables de entorno, falta de límites de recursos y dependencia del host.

Desarrollador revisando contenedores Docker en doble monitor
Desarrollador revisando contenedores Docker en doble monitor

La imagen no es un tar de tu home

Las primeras imágenes que un equipo construye suelen pesar 1-2 GB porque parten de ubuntu:latest, instalan build-essential y dejan dentro caches de apt, archivos de compilación y herramientas de desarrollo. Una imagen de producción debería ser multi-stage: una etapa para compilar, otra mínima (alpine o distroless) que sólo contenga el binario, sus librerías y los certificados.

FROM golang:1.19 AS build
WORKDIR /src
COPY . .
RUN CGO_ENABLED=0 go build -o /out/app ./cmd/app

FROM gcr.io/distroless/static:nonroot
COPY --from=build /out/app /app
USER nonroot:nonroot
ENTRYPOINT ["/app"]

Secretos, recursos y persistencia

Tres errores recurrentes: pasar contraseñas por ENV (quedan en el manifiesto, en docker inspect y en los logs del orquestador), no fijar memory/cpu limits (un contenedor mata al host en producción) y escribir datos en el filesystem del contenedor (se pierden en cada redeploy). Las soluciones son conocidas: secrets gestionados, requests/limits explícitos y volúmenes para todo dato persistente.

Observabilidad en un mundo efímero

Un contenedor reiniciado pierde sus logs locales. Stdout/stderr a un colector central (Fluentd, Loki, journald-remote), métricas expuestas en /metrics y trazas con un identificador de correlación que sobreviva al ciclo de vida del contenedor. Sin esto, debugar un incidente en producción se convierte en arqueología.