Docker Best Practices: 16 Consejos para Contenedores Listos para Produccion
dockerintermediate

Docker Best Practices: 16 Consejos para Contenedores Listos para Produccion

Guia completa de mejores practicas Docker para optimizar tus Dockerfiles, asegurar tus contenedores y reducir el tamano de las imagenes. Ejemplos practicos y consejos de produccion.

Antoine C
10 min read
#docker#best-practices#dockerfile#security#performance#devops

Docker Best Practices: 16 Consejos para Contenedores Listos para Produccion

La mayoria de los Dockerfiles que reviso comparten los mismos problemas: imagenes de 2 GB cuando 200 MB serian suficientes, builds de 15 minutos que podrian tomar 30 segundos, contenedores ejecutandose como root sin ninguna razon valida.

El contenedor funciona localmente, las pruebas pasan, pero una vez en produccion, los problemas se acumulan: tiempos de despliegue excesivos, vulnerabilidades de seguridad, consumo de memoria explosivo.

La diferencia entre un Dockerfile que "funciona" y un Dockerfile listo para produccion a menudo se reduce a una docena de mejores practicas. Estas practicas no son complicadas, pero requieren entender como funciona Docker internamente: sistema de capas, cache de build, aislamiento de procesos.

En este articulo, descubrira las 16 mejores practicas esenciales de Docker para pasar de imagenes amateurs a contenedores profesionales. Cubriremos optimizacion de Dockerfiles, seguridad, rendimiento y patrones de produccion.

Este articulo presenta cada practica de forma concisa. Para profundizar y practicar en entornos reales, Train With Docker ofrece escenarios "Docker Best Practices" que lo guian paso a paso en la implementacion de cada concepto.

Optimizacion de Dockerfiles#

1. Use multi-stage builds#

Los multi-stage builds son probablemente la tecnica mas impactante para reducir el tamano de sus imagenes. La idea: separar el entorno de build del entorno de ejecucion.

dockerfile
# Etapa 1: Build
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

# Etapa 2: Produccion
FROM node:20-alpine AS runner
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
CMD ["node", "dist/index.js"]

La primera etapa contiene todo lo necesario para compilar (TypeScript, devDependencies, fuentes). La segunda etapa conserva solo lo estrictamente necesario: codigo compilado y dependencias de produccion.

Ganancia tipica

Un proyecto Node.js TypeScript a menudo pasa de 1.2 GB a 150 MB con un multi-stage build bien configurado.

2. Ordene sus instrucciones para maximizar el cache#

Docker cachea cada capa. Cuando una instruccion cambia, todas las instrucciones siguientes se reconstruyen. El orden de sus instrucciones impacta directamente la velocidad de sus builds.

dockerfile
# Malo: el cache se invalida con cada cambio de codigo
COPY . .
RUN npm ci
RUN npm run build

# Bueno: las dependencias se cachean por separado
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

La regla: coloque los elementos que raramente cambian primero (dependencias del sistema, luego paquetes, luego codigo fuente).

3. Minimice el numero de capas#

Cada instruccion RUN, COPY y ADD crea una nueva capa. Combine los comandos relacionados para reducir el numero de capas.

dockerfile
# Malo: 3 capas
RUN apt-get update
RUN apt-get install -y curl
RUN apt-get clean

# Bueno: 1 capa
RUN apt-get update && \
    apt-get install -y --no-install-recommends curl && \
    apt-get clean && \
    rm -rf /var/lib/apt/lists/*
¿Listo para Probarlo por Ti Mismo?
Practica estos conceptos Docker en un entorno real con escenarios prácticos.

4. Use un archivo .dockerignore#

El archivo .dockerignore funciona como .gitignore: excluye archivos del contexto de build. Sin el, Docker copia todo, incluyendo node_modules, .git y sus archivos de prueba.

text
node_modules
.git
.gitignore
*.md
.env*
coverage
.nyc_output
dist
Atencion

Un contexto de build voluminoso ralentiza cada build, incluso si no copia estos archivos explicitamente. Docker aun debe enviarlos al daemon.

5. Elija la imagen base correcta#

La eleccion de la imagen base impacta el tamano final, la seguridad y las dependencias disponibles.

ImagenTamanoCaso de uso
node:20~400 MBNunca en produccion
node:20-slim~75 MBCuando necesita paquetes del sistema
node:20-alpine~50 MBEleccion por defecto
gcr.io/distroless/nodejs20~188 MBSeguridad maxima

Alpine usa musl libc en lugar de glibc. En el 95% de los casos, funciona perfectamente. Para el 5% restante (ciertos binarios nativos), use -slim.

Las imagenes distroless de Google son mas pesadas que Alpine, pero no contienen shell, gestor de paquetes ni herramientas del sistema. Solo el runtime (aqui Node.js) y sus dependencias estan presentes. Resultado: menos superficie de ataque y ninguna forma para que un atacante ejecute comandos en el contenedor.

Seguridad de Contenedores#

6. Nunca ejecute como root#

Por defecto, los contenedores Docker se ejecutan como root. Este es un problema de seguridad importante: si un atacante compromete su aplicacion, tiene privilegios de root en el contenedor.

dockerfile
FROM node:20-alpine

# Crear un usuario no-root
RUN addgroup -g 1001 -S nodejs && \
    adduser -S nextjs -u 1001

WORKDIR /app
COPY --chown=nextjs:nodejs . .

# Cambiar de usuario antes de CMD
USER nextjs

CMD ["node", "index.js"]
Verificacion

Puede verificar el usuario actual con docker exec <container> whoami. Si devuelve root, tiene un problema.

7. Limite las capabilities de Linux#

Por defecto, Docker otorga a sus contenedores un conjunto de capabilities de Linux (permisos del sistema). La mayoria son innecesarias para una aplicacion estandar y representan un riesgo de seguridad.

bash
# Elimine todas las capabilities y agregue solo las necesarias
docker run --cap-drop=ALL --cap-add=NET_BIND_SERVICE myapp

Capabilities mas comunes:

CapabilityUso
NET_BIND_SERVICEEscuchar en un puerto < 1024
CHOWNCambiar propietario de archivos
SETUID / SETGIDCambiar usuario/grupo
SYS_PTRACEDepurar procesos (evitar en prod)
Principio de minimo privilegio

Siempre comience con --cap-drop=ALL, luego agregue solo las capabilities que su aplicacion realmente necesita. Si su app falla, los logs le indicaran que capability falta.

Para Docker Compose:

yaml
services:
  app:
    image: myapp
    cap_drop:
      - ALL
    cap_add:
      - NET_BIND_SERVICE
Pon la Teoría en Práctica
Aplica lo que has aprendido con escenarios Docker interactivos y entornos reales.

8. Use imagenes base minimas#

Cuantos mas paquetes contiene una imagen, mayor es su superficie de ataque. Las imagenes distroless de Google contienen solo su aplicacion y sus dependencias de runtime, sin shell ni herramientas del sistema.

dockerfile
FROM node:20-alpine AS builder
WORKDIR /app
COPY . .
RUN npm ci && npm run build

FROM gcr.io/distroless/nodejs20-debian12
COPY --from=builder /app/dist /app
WORKDIR /app
CMD ["index.js"]

La desventaja: imposible hacer docker exec para depurar. Esto es intencional. En produccion, no deberia necesitar un shell en sus contenedores.

9. Escanee sus imagenes en busca de vulnerabilidades#

Las imagenes Docker pueden contener CVEs (vulnerabilidades conocidas) en sus paquetes del sistema o dependencias.

bash
# Con Docker Scout (integrado en Docker Desktop)
docker scout cves <image>

# Con Trivy (open source)
trivy image <image>

# Con Snyk
snyk container test <image>

Integre estos escaneos en su CI/CD. Un escaneo que descubre una vulnerabilidad critica deberia bloquear el despliegue.

10. Maneje sus secretos correctamente#

Nunca ponga secretos en su Dockerfile o imagen. Permaneceran en el historial de capas.

dockerfile
# NUNCA: el secreto permanece en la imagen
ENV DATABASE_URL=postgres://user:password@host/db

# MEJOR: use secretos de Docker (build-time)
# El secreto se monta temporalmente en /run/secrets/ durante el build
# Nunca se escribe en una capa de la imagen
RUN --mount=type=secret,id=db_url \
    export DATABASE_URL=$(cat /run/secrets/db_url) && \
    npm run migrate

# Para construir con este secreto:
# docker build --secret id=db_url,src=.env .

# EN PRODUCCION: pase los secretos en runtime
docker run -e DATABASE_URL=$DATABASE_URL myapp

Para Docker Swarm y Kubernetes, use sus mecanismos nativos de secretos.

Rendimiento y Tamano de Imagenes#

11. Limpie los caches en la misma capa#

Los gestores de paquetes dejan caches. Si los limpia en una capa separada, el cache permanece en las capas anteriores.

dockerfile
# Python
RUN pip install --no-cache-dir -r requirements.txt

# Node.js
RUN npm ci --only=production && npm cache clean --force

# Debian/Ubuntu
RUN apt-get update && \
    apt-get install -y --no-install-recommends package && \
    apt-get clean && \
    rm -rf /var/lib/apt/lists/*
Domina Docker de Forma Práctica
Ve más allá de la teoría - practica con contenedores reales y escenarios de orquestación.

12. Use COPY en lugar de ADD#

COPY hace exactamente lo que su nombre indica: copiar archivos. ADD hace lo mismo, pero extrae automaticamente archivos (.tar, .gz) y puede descargar archivos desde una URL. Estos comportamientos implicitos hacen el Dockerfile menos legible y pueden introducir vulnerabilidades de seguridad si una URL esta comprometida o si un archivo contiene archivos inesperados. Prefiera COPY por su previsibilidad.

dockerfile
# Prefiera
COPY ./src /app/src

# Evite (a menos que necesite extraer un archivo)
ADD ./src /app/src

Mejores Practicas de Build#

13. Agregue labels para metadatos#

Los labels ayudan a identificar y documentar sus imagenes.

dockerfile
LABEL org.opencontainers.image.title="Mi Aplicacion"
LABEL org.opencontainers.image.description="API Backend"
LABEL org.opencontainers.image.version="1.2.3"
LABEL org.opencontainers.image.authors="[email protected]"
LABEL org.opencontainers.image.source="https://github.com/org/repo"

Estos labels siguen la especificacion OCI y son reconocidos por Docker Hub, GitHub Container Registry y otros registros.

14. Implemente health checks#

Un health check permite a Docker (y a los orquestadores) verificar que su aplicacion esta realmente funcionando.

dockerfile
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
  CMD curl -f http://localhost:3000/health || exit 1

Sin un health check, Docker considera un contenedor "healthy" tan pronto como inicia, incluso si la aplicacion falla despues de 5 segundos.

15. Entienda la diferencia entre ENTRYPOINT y CMD#

  • ENTRYPOINT: define el comando principal (raramente sobrescrito)
  • CMD: define los argumentos por defecto (facilmente sobrescritos)
dockerfile
# Patron recomendado
ENTRYPOINT ["node"]
CMD ["index.js"]

# Permite hacer:
docker run myapp                    # Ejecuta node index.js
docker run myapp other-script.js    # Ejecuta node other-script.js

Use la forma exec ["cmd", "arg"] en lugar de la forma shell cmd arg. La forma exec permite un mejor manejo de senales.

Logging y Observabilidad#

16. Escriba sus logs en stdout/stderr#

Docker captura automaticamente todo lo que sale por stdout y stderr. No escriba en archivos de log.

javascript
// Bueno: stdout
console.log(JSON.stringify({ level: 'info', message: 'User logged in', userId: 123 }));

// Malo: archivo
fs.appendFileSync('/var/log/app.log', 'User logged in\n');

El formato JSON estructura sus logs para herramientas de agregacion (ELK, Datadog, CloudWatch).

dockerfile
# No redirija logs a archivos
CMD ["node", "index.js"]

# No esto:
CMD ["node", "index.js", ">", "/var/log/app.log"]

Orquestacion y Produccion#

Una vez que sus imagenes estan optimizadas, algunas practicas adicionales se aplican en runtime.

Limite los recursos para evitar que un contenedor monopolice el servidor:

bash
docker run --memory="512m" --cpus="0.5" myapp

Configure las politicas de reinicio para resiliencia:

bash
docker run --restart=unless-stopped myapp

Use rolling updates con Docker Swarm o Kubernetes para desplegar sin interrupcion del servicio.

Docker Swarm

Si esta preparando la certificacion DCA o quiere profundizar en Docker Swarm, Train With Docker ofrece escenarios practicos con clusters preconfigurados.

Conclusion#

Estas 16 mejores practicas de Docker cubren los aspectos esenciales: optimizacion de builds, seguridad, rendimiento y preparacion para produccion. Aplicadas sistematicamente, transforman Dockerfiles amateurs en configuraciones profesionales.

Lo mas importante: entender por que existe cada practica. Los multi-stage builds reducen el tamano porque separan build y runtime. El cache funciona por capa, por lo que el orden importa. Los contenedores no-root limitan el impacto de un compromiso.

Este articulo pasa por cada practica sin entrar en detalles de implementacion. Para ensuciarse las manos, Train With Docker ofrece escenarios "Docker Best Practices" que le permiten practicar cada concepto en entornos preconfigurados, sin instalar nada localmente.

Docker Best Practices: 16 Consejos para Contenedores Listos para Produccion