Bandera: Русский Русский Bandera: English English

El Dockerfile perfecto: transformamos una compilación casera en una herramienta profesional

Publicado el 12.01.2026

Escribir un Dockerfile simple: FROM node, COPY ., CMD run. Esto funciona, y para pruebas locales a menudo es suficiente. Pero cuando tal imagen llega al CI/CD o, Dios no lo quiera, a producción, empiezan los problemas: la construcción dura una eternidad, la imagen pesa gigabytes y los de seguridad se llevan las manos a la cabeza.

La diferencia entre “funciona” y “funciona correctamente” es colosal. Vamos a analizar cuatro niveles de optimización que separan una chapuza aficionada de una solución de ingeniería fiable.


1. Fundamento: Elección de la imagen y determinismo

Todo comienza con la instrucción FROM. Mucha gente por costumbre toma imágenes completas (por ejemplo, la estándar ubuntu o python:3.9) sin pensar en las consecuencias.

Problema: Las imágenes completas del SO arrastran cientos de megabytes de “basura”: curl, vim, systemd. Estas utilidades no son necesarias para tu microservicio, pero aumentan el tiempo de descarga y, lo que es más importante, crean una enorme superficie de ataque.

Qué elegir?

  • Alpine Linux: El rey de los ligeros (unos 5 MB). Ideal para Go o binarios estáticos.

    Importante: Alpine usa la biblioteca musl en lugar de la estándar glibc. Si programas en Python o C++, esto puede causar problemas de compatibilidad o rendimiento. ¡Prueba!

  • Versiones slim: (por ejemplo, debian:bullseye-slim). El mismo Debian, pero limpio de manuales y paquetes innecesarios. Tiene glibc, lo que lo convierte en la “tierra de nadie” ideal para la mayoría de aplicaciones.

  • Distroless: La máxima expresión de Google. En estas imágenes ni siquiera hay shell (sh).

    • Ventaja: Un atacante no podrá ejecutar ningún comando dentro del contenedor.
    • Desventaja: Tú tampoco podrás entrar para depurar (docker exec no funcionará).

Tabú con :latest Nunca uses el tag latest en producción.

  • Riesgo: Mañana saldrá una nueva versión de Node.js o Python con cambios incompatibles. Tu CI la descargará automáticamente y la producción fallará.
  • Solución: Fija las versiones (Pinning). Usa node:18.16.0-alpine para asegurar determinismo: la compilación debe dar el mismo resultado hoy y dentro de un año.

2. Optimización de la build: Caché y contexto

La imagen Docker es una tarta por capas. La regla principal del caché: si cambia una capa, todas las siguientes se reconstruyen desde cero.

.dockerignore — no es solo un capricho

Al igual que con .gitignore, este archivo evita enviar “basura” (carpetas .git, node_modules, logs temporales) al demonio de Docker.

  • Para qué: Acelera el inicio de la build (menos contexto que transferir) y protege tus secretos de ser incluidos por accidente en la imagen.

El orden de los comandos lo decide todo

Un error común de novatos es copiar el código antes de instalar dependencias.

❌ Mal (la caché se invalida con cualquier cambio en el código):

COPY . .
RUN npm install  # Esta operación pesada se ejecutará cada vez!

✅ Bien (caché inteligente):

COPY package.json package-lock.json ./
RUN npm install  # Se ejecuta solo si cambiaron las dependencias
COPY . .         # Copiamos el código. Si cambiaste una coma en el código, npm install no se volverá a ejecutar.

Capas atómicas

Cada instrucción RUN crea una nueva capa.

Consejo: Combina comandos de actualización, instalación y limpieza de caché con &&. Esto evita llevar archivos eliminados a la imagen final.

RUN apt-get update && apt-get install -y \
    python3-dev \
    && rm -rf /var/lib/apt/lists/*

3. Seguridad y gestión de secretos

No al usuario root

Por defecto Docker ejecuta procesos como root. Si un atacante encuentra una vulnerabilidad en tu aplicación y consigue escapar del contenedor (container breakout), obtendrá privilegios root en la máquina host.

Solución: Siempre crea un usuario y cámbiate a él.

RUN addgroup -S appgroup && adduser -S appuser -G appgroup
USER appuser

Los secretos no en ENV

Nunca pases contraseñas o claves API a través de ARG o ENV. Las variables de entorno quedan “horneadas” en el historial de capas de la imagen (docker history las mostrará a cualquiera interesado).

Solución: Usa BuildKit Secrets. Funcionan como una “memoria USB” temporal conectada solo durante la build.

# Ejemplo de uso de secreto durante la compilación
RUN --mount=type=secret,id=my_token \
    cat /run/secrets/my_token | pip install -r private-requirements.txt

4. Técnicas avanzadas: Subir de nivel

Multi-stage Builds (Builds multi-etapa)

Esta es la principal Best Practice para lenguajes compilados (Go, Java, Rust, C++), y también para frontend. La idea: en la primera (pesada) imagen compilas el código, y en la segunda (limpia) copias solo el binario.

  • Resultado: La imagen pesa 15 MB en lugar de 1 GB. Todo el código fuente y los compiladores quedan fuera de la imagen final.

PID 1 y apagado correcto (Graceful Shutdown)

Los orquestadores (Kubernetes) se comunican con los contenedores mediante señales (por ejemplo, SIGTERM para detenerlos). Si tu aplicación se arranca a través de un shell (por ejemplo, npm start), puede que no reciba esa señal porque sh no reenvía señales a los procesos hijos. Al final Kubernetes matará el pod de forma brutal (SIGKILL), lo que puede provocar pérdida de datos o transacciones interrumpidas.

Solución:

  1. Usa el formato exec en CMD: CMD ["node", "server.js"].
  2. Usa tini — un pequeño init que maneja correctamente las señales.

Conclusión

La imagen Docker ideal se sostiene sobre tres pilares:

  1. Velocidad (caché óptimo y tamaño reducido).
  2. Seguridad (usuario no-root, ausencia de utilidades innecesarias, manejo correcto de secretos).
  3. Fiabilidad (tags de versiones deterministas).

Para no tener todas estas reglas en la cabeza, integra en tu pipeline CI hadolint. Es un analizador estático para Dockerfile que te “cantará las cuarenta” por errores de sintaxis y violaciones de buenas prácticas antes de que la imagen empiece a construirse.

Reseñas relacionadas

Hubo varios problemas, tanto en la parte técnica como en la comprensión general. Mijaíl respondió rápido a la solicitud, ayudó a aclarar las cosas y resolvió los problemas técnicos; por ello, muchas gracias. Estoy satisfecho con el resultado.

abazawolf · Configuración de VPS, configuración del servidor

18.02.2026 · ⭐ 5/5

Hubo varios problemas relacionados tanto con la parte técnica como con la comprensión en general. Mijaíl respondió rápidamente a la solicitud, ayudó a aclarar las cosas y resolvió los problemas técnicos, por lo que le doy las gracias por ello. Estoy satisfecho con el resultado.

¿Necesitas ayuda?

Escríbeme y te ayudaré a resolver el problema

Publicaciones relacionadas