Un Dockerfile es el archivo que convierte código fuente en una imagen Docker reproducible. Contiene todas las instrucciones que Docker ejecuta en orden para construir la imagen: qué sistema operativo base usar, qué dependencias instalar, qué archivos copiar y qué comando ejecutar cuando el contenedor arranque.
Sin un Dockerfile bien escrito, las imágenes Docker son lentas de construir, innecesariamente grandes y difíciles de mantener. Con un Dockerfile bien escrito, el proceso es reproducible, eficiente y automatizable en cualquier pipeline CI/CD.
Para entender el ecosistema completo donde encaja el Dockerfile, el artículo sobre qué es Docker explica la arquitectura de contenedores e imágenes antes de entrar en la construcción.
Cómo funciona un Dockerfile: capas y caché
Entender el sistema de capas de Docker es la base para escribir Dockerfiles eficientes. Cada instrucción en el Dockerfile genera una nueva capa en la imagen. Esas capas son inmutables y se apilan unas sobre otras para formar la imagen final.
La consecuencia más importante de este sistema es la caché. Docker cachea el resultado de cada capa. En builds posteriores, si una instrucción no ha cambiado desde el último build, Docker usa la capa cacheada en lugar de ejecutarla de nuevo. Eso hace que los builds repetidos sean mucho más rápidos.
El problema es que cuando una capa cambia, Docker invalida esa capa y todas las que vienen después. Si el código de la aplicación (que cambia frecuentemente) se copia antes de instalar las dependencias (que cambian poco), cada cambio en el código obliga a reinstalar todas las dependencias. Invirtiendo el orden se evita ese problema.
Instrucciones principales del Dockerfile

FROM: imagen base
🔴 ¿Quieres entrar de lleno al mundo DevOps & Cloud Computing? 🔴
Descubre el DevOps & Cloud Computing Full Stack Bootcamp de KeepCoding. La formación más completa del mercado y con empleabilidad garantizada
👉 Prueba gratis el Bootcamp en DevOps & Cloud Computing por una semanaEs siempre la primera instrucción del Dockerfile. Define la imagen base sobre la que se construye la nueva imagen.
FROM node:20-alpine
FROM python:3.12-slim
FROM ubuntu:24.04
La elección de la imagen base tiene un impacto directo en el tamaño final de la imagen. Las variantes alpine (basadas en Alpine Linux, ~5MB) y slim (Debian mínimo) son significativamente más pequeñas que las imágenes completas. Para producción, siempre se recomienda usar variantes ligeras y especificar la versión exacta en lugar de latest.
WORKDIR: directorio de trabajo
Establece el directorio de trabajo dentro del contenedor para las instrucciones siguientes: RUN, COPY, CMD y ENTRYPOINT.
WORKDIR /app
Si el directorio no existe, Docker lo crea automáticamente. Es mejor práctica que usar RUN mkdir seguido de cd.
COPY y ADD: copiar archivos
Ambas instrucciones copian archivos del host al sistema de archivos del contenedor.
COPY package*.json ./
COPY src/ ./src/
COPY . .
COPY es la instrucción recomendada para la mayoría de casos. ADD tiene dos funcionalidades adicionales: puede descargar archivos desde URLs y extraer automáticamente archivos comprimidos (.tar.gz, .zip). La documentación oficial de Docker recomienda usar COPY siempre que no se necesiten esas funcionalidades extra.
RUN: ejecutar comandos
Ejecuta comandos durante el proceso de build. Cada instrucción RUN crea una nueva capa. Para minimizar el número de capas, se encadenan múltiples comandos con &&:
# ❌ Tres capas innecesarias
RUN apt-get update
RUN apt-get install -y curl
RUN rm -rf /var/lib/apt/lists/*
# ✅ Una sola capa
RUN apt-get update && \
apt-get install -y curl && \
rm -rf /var/lib/apt/lists/*
Eliminar la caché del gestor de paquetes en el mismo RUN que lo instala es una práctica importante para reducir el tamaño de la imagen.
ENV y ARG: variables
ENV define variables de entorno que estarán disponibles tanto durante el build como en el contenedor en ejecución:
ENV NODE_ENV=production
ENV PORT=3000
ARG define variables solo disponibles durante el proceso de build, no en el contenedor en ejecución. Se usan para pasar valores al build sin incluirlos en la imagen final:
ARG BUILD_VERSION
RUN echo "Building version $BUILD_VERSION"
EXPOSE: puertos
Documenta en qué puerto escucha la aplicación dentro del contenedor. No publica el puerto automáticamente: eso se hace en docker run -p o en el docker-compose.yml.
EXPOSE 3000
CMD y ENTRYPOINT: comando de arranque
Es una de las confusiones más frecuentes en Dockerfiles y una de las preguntas más buscadas sobre el tema.
CMD define el comando por defecto que ejecuta el contenedor al arrancar. Se puede sobreescribir completamente al ejecutar docker run imagen otro-comando:
CMD ["node", "server.js"]
ENTRYPOINT define el ejecutable principal del contenedor. No se puede sobreescribir fácilmente con docker run. Lo que se pase a docker run se interpreta como argumentos para el ENTRYPOINT:
ENTRYPOINT ["node"]
CMD ["server.js"]
# docker run miapp script.js → ejecuta: node script.js
La combinación más robusta en producción es usar ENTRYPOINT para el ejecutable y CMD para los argumentos por defecto. Así el contenedor siempre ejecuta el mismo programa pero con flexibilidad en los argumentos.
HEALTHCHECK: comprobación de salud
Define el comando que Docker ejecuta periódicamente para verificar que la aplicación dentro del contenedor funciona correctamente. Es especialmente importante cuando se usa con depends_on: condition: service_healthy en Docker Compose:
HEALTHCHECK --interval=30s --timeout=10s --retries=3 \
CMD curl -f http://localhost:3000/health || exit 1
Un Dockerfile completo de producción
Este ejemplo muestra un Dockerfile completo para una aplicación Node.js con todas las buenas prácticas aplicadas:
# Etapa de build
FROM node:20-alpine AS builder
WORKDIR /app
# Instalar dependencias primero (mejor aprovechamiento de caché)
COPY package*.json ./
RUN npm ci --only=production
# Copiar código fuente
COPY src/ ./src/
# Imagen final de producción
FROM node:20-alpine AS production
# Crear usuario no-root por seguridad
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
WORKDIR /app
# Copiar solo lo necesario desde la etapa builder
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/src ./src
# Variables de entorno
ENV NODE_ENV=production
ENV PORT=3000
# Puerto de la aplicación
EXPOSE 3000
# Cambiar al usuario no-root
USER appuser
# Healthcheck
HEALTHCHECK --interval=30s --timeout=10s --retries=3 \
CMD wget -qO- http://localhost:3000/health || exit 1
# Comando de arranque
CMD ["node", "src/server.js"]
El archivo .dockerignore
El .dockerignore es al Dockerfile lo que el .gitignore es a Git. Excluye archivos y directorios del contexto de build que no necesita la imagen. Sin él, Docker envía al daemon todo el directorio del proyecto, incluyendo node_modules, carpetas .git, archivos de test y documentación.
node_modules
.git
.gitignore
*.md
tests/
coverage/
.env
.env.*
Dockerfile*
docker-compose*
Un .dockerignore bien configurado reduce el tamaño del contexto de build, acelera el envío al daemon y evita que archivos sensibles como el .env acaben involuntariamente dentro de la imagen.
Multi-stage builds: imágenes más pequeñas y seguras
Los multi-stage builds usan múltiples etapas FROM en un solo Dockerfile. La etapa final solo contiene lo necesario para ejecutar la aplicación, sin herramientas de build ni dependencias de desarrollo.
Para una aplicación Python con FastAPI la diferencia entre una imagen sin multi-stage (600MB+) y una con multi-stage (150MB) puede ser de cientos de megabytes.
Para distribuir bien esas imágenes y publicarlas en registros, el artículo sobre distribución de aplicaciones con Docker cubre el flujo completo desde el build hasta el despliegue.
Buenas prácticas para escribir Dockerfiles

- Especifica versiones exactas en FROM.
node:20-alpineen lugar denode:latest. Las imágeneslatestcambian sin aviso y pueden romper el build. - Ordena las instrucciones por frecuencia de cambio. Las instrucciones que cambian menos (instalar dependencias del sistema) van primero. Las que cambian más (copiar el código) van al final.
- Encadena los comandos RUN. Múltiples comandos en un solo
RUNcon&¶ crear menos capas y reducir el tamaño. - Usa COPY en lugar de ADD. Excepto cuando necesites descargar URLs o extraer comprimidos.
- Ejecuta con usuario no-root. Crear un usuario sin privilegios y cambiarlo con
USERantes delCMDreduce la superficie de ataque. - Mantén el .dockerignore actualizado. Excluye todo lo que no necesita la imagen: tests, documentación, archivos de configuración local.
- Usa multi-stage builds para producción. La imagen final solo debe contener lo necesario para ejecutar la aplicación.
Para ver cómo el Dockerfile encaja en el contexto de un entorno de desarrollo completo con hot reload y debugging, el artículo sobre cómo usar Docker para entornos de desarrollo muestra la configuración completa.
Y para gestionar aplicaciones multi-contenedor con varios servicios, el artículo sobre qué es Docker Compose explica cómo orquestarlos.
Rubén trabajaba como ingeniero de aplicaciones cuando decidió dar el salto al ecosistema DevOps. Cada oferta interesante que encontraba mencionaba Docker, Kubernetes y Terraform. Hizo el Bootcamp DevOps de KeepCoding para entender ese ecosistema desde dentro.
Hoy trabaja como SRE en Red Hat. El Dockerfile fue una de las primeras herramientas que dominó en el programa, y el conocimiento de cómo construir imágenes eficientes para producción le resultó directamente aplicable desde el primer proyecto real.
Cómo aprender a escribir Dockerfiles profesionales
La sintaxis de un Dockerfile se aprende en pocas horas. Lo que tarda más en desarrollarse es el criterio para escribir Dockerfiles que sean eficientes en build, seguros en producción y fáciles de mantener.
Saber por qué el orden de las instrucciones importa para la caché, cuándo usar multi-stage builds, cómo configurar healthchecks correctamente o cómo reducir el tamaño de una imagen de 800MB a 80MB son habilidades que se construyen trabajando en proyectos reales con consecuencias reales.
Para aprender Docker con proyectos reales y profesores en activo, el DevOps y Cloud Computing Full Stack Bootcamp de KeepCoding cubre el recorrido completo en 6 meses.
Conclusión

Un Dockerfile bien escrito es la diferencia entre imágenes Docker que funcionan y se mantienen bien y imágenes que acumulan deuda técnica desde el primer build.
El sistema de capas y su caché, el orden correcto de las instrucciones, los multi-stage builds y el .dockerignore son los elementos que más impactan en la calidad real de una imagen.
Docker ejecuta las instrucciones del Dockerfile en orden, cachea cada capa y construye imágenes reproducibles que funcionan igual en cualquier entorno. Entender eso es entender el corazón de cómo funciona Docker.
La referencia oficial más completa sobre todas las instrucciones del Dockerfile está en docs.docker.com/reference/dockerfile/.



