El Dockerfile es el corazón de la creación de imágenes en Docker: un fichero de texto donde describimos, paso a paso, cómo se construye una imagen. Dominar sus instrucciones es imprescindible para crear imágenes correctas, eficientes y mantenibles. En esta lección estudiaremos qué es un Dockerfile y cómo funciona, repasaremos en una tabla las instrucciones esenciales (FROM, RUN, COPY, ADD, WORKDIR, CMD, ENTRYPOINT, EXPOSE, ENV), aclararemos la confusión clásica entre CMD y ENTRYPOINT, y analizaremos un Dockerfile completo comentado línea a línea.
¿Qué es un Dockerfile?
Un Dockerfile es un fichero de texto plano (normalmente llamado exactamente Dockerfile, sin extensión) que contiene una secuencia de instrucciones. Cada instrucción describe un paso de la construcción, y Docker las ejecuta en orden, de arriba abajo.
Características fundamentales:
- Cada instrucción suele escribirse en MAYÚSCULAS por convención (
FROM,RUN, etc.), aunque Docker no distingue mayúsculas de minúsculas en el nombre de la instrucción. - La mayoría de las instrucciones generan una capa en la imagen resultante. Las capas se cachean, lo que acelera reconstrucciones posteriores.
- Las líneas que empiezan por
#son comentarios y se ignoran. - La primera instrucción significativa casi siempre es
FROM, que define la imagen base.
Instrucciones Esenciales del Dockerfile
La siguiente tabla resume las instrucciones que usarás en la inmensa mayoría de los Dockerfiles:
| Instrucción | Para qué sirve | Ejemplo |
|---|---|---|
FROM |
Define la imagen base de la que partimos | FROM node:18-alpine |
WORKDIR |
Establece el directorio de trabajo dentro de la imagen | WORKDIR /app |
COPY |
Copia ficheros del contexto a la imagen | COPY . . |
ADD |
Como COPY, pero también descomprime y descarga URLs |
ADD app.tar.gz /app |
RUN |
Ejecuta un comando durante la construcción | RUN npm ci |
ENV |
Define variables de entorno en la imagen | ENV NODE_ENV=production |
EXPOSE |
Documenta el puerto en que escucha el contenedor | EXPOSE 3000 |
CMD |
Comando por defecto al arrancar el contenedor | CMD ["node", "server.js"] |
ENTRYPOINT |
Comando fijo que siempre se ejecuta al arrancar | ENTRYPOINT ["node"] |
FROM
FROM debe ser la primera instrucción (salvo argumentos ARG previos). Establece la base sobre la que se construye todo lo demás.
python:3.12-slim: una imagen oficial de Python 3.12 en su variante reducida (slim).
WORKDIR
Define el directorio de trabajo para las instrucciones posteriores (RUN, COPY, CMD, etc.). Si no existe, lo crea.
- A partir de aquí, un
COPY . .copia al directorio/app, y los comandos se ejecutan desde ahí. - Es preferible a usar
RUN cd /app, porquecdno persiste entre instrucciones;WORKDIRsí.
COPY y ADD
Ambas copian ficheros del contexto de construcción a la imagen, pero tienen diferencias importantes:
| Aspecto | COPY |
ADD |
|---|---|---|
| Copiar ficheros locales | Sí | Sí |
Descomprimir .tar automáticamente |
No | Sí |
| Descargar desde una URL | No | Sí |
| Recomendación general | Preferida por su transparencia | Solo cuando necesitas sus extras |
- Usa
COPYpor defecto: es más predecible. ReservaADDsolo para los casos en que necesites descomprimir un.tarlocal o descargar una URL.
RUN
Ejecuta un comando durante la construcción de la imagen, generando una nueva capa con el resultado.
RUN apt-get update && apt-get install -y --no-install-recommends curl && rm -rf /var/lib/apt/lists/*- Se ejecuta al construir, no al arrancar el contenedor.
- Encadenar comandos con
&&y limpiar en la misma instrucción reduce el número de capas y el tamaño final.
ENV
Define variables de entorno que estarán disponibles tanto durante la construcción como en el contenedor en ejecución.
- Estas variables las pueden leer la aplicación y otras instrucciones del Dockerfile.
EXPOSE
Documenta qué puerto usa el contenedor. No publica el puerto por sí mismo: solo es informativo.
- Para que el puerto sea accesible desde el host, sigues necesitando
-p 8080:3000al ejecutardocker run.
CMD vs ENTRYPOINT: La Confusión Clásica
Esta es una de las dudas más frecuentes para quien empieza. Ambas definen qué se ejecuta al arrancar el contenedor, pero se comportan de forma distinta.
| Aspecto | CMD |
ENTRYPOINT |
|---|---|---|
| Propósito | Comando por defecto, fácil de sustituir | Comando fijo del contenedor |
¿Se sobrescribe con docker run imagen otracosa? |
Sí, se reemplaza por completo | No; los argumentos se le añaden |
| Uso típico | Aplicación cuyo comando puede variar | Un ejecutable concreto que siempre se usa |
Veamos la diferencia con ejemplos.
Con solo CMD:
docker run miimgimprimeHola.docker run miimg echo AdiosimprimeAdios: el argumento sustituye completamente alCMD.
Con solo ENTRYPOINT:
docker run miimg HolaimprimeHola: el argumento se añade alENTRYPOINT.- No puedes cambiar fácilmente el ejecutable; siempre será
echo.
La combinación recomendada usa ENTRYPOINT para fijar el ejecutable y CMD para los argumentos por defecto:
docker run miimgimprimeHola por defecto(elCMDpor defecto).docker run miimg AdiosimprimeAdios(el argumento sustituye alCMD, pero elENTRYPOINTechose mantiene).
Nota sobre formato: siempre que puedas, usa la forma exec (con corchetes y comillas, como
["echo", "Hola"]) en lugar de la forma shell (CMD echo Hola). La forma exec no envuelve el comando en una shell, lo que gestiona mejor las señales del sistema (por ejemplo, para detener el contenedor correctamente).
Ejemplo Completo: Dockerfile Comentado Línea a Línea
Veamos un Dockerfile realista para una aplicación Python con Flask, explicado instrucción por instrucción.
# 1. Imagen base oficial de Python, variante ligera
FROM python:3.12-slim
# 2. Variables de entorno de la aplicación
ENV PYTHONUNBUFFERED=1
ENV APP_PORT=5000
# 3. Directorio de trabajo dentro de la imagen
WORKDIR /app
# 4. Copiamos primero las dependencias para aprovechar la caché
COPY requirements.txt ./
# 5. Instalamos dependencias sin guardar la caché de pip
RUN pip install --no-cache-dir -r requirements.txt
# 6. Copiamos el resto del código de la aplicación
COPY . .
# 7. Documentamos el puerto en que escucha la app
EXPOSE 5000
# 8. Comando que arranca la aplicación
CMD ["python", "app.py"]Explicación detallada:
- Línea 2 (
FROM): partimos de Python 3.12 en su versiónslim, que reduce el tamaño respecto a la imagen completa. - Líneas 5-6 (
ENV):PYTHONUNBUFFERED=1hace que losprintaparezcan inmediatamente en los logs;APP_PORTqueda disponible para la aplicación. - Línea 9 (
WORKDIR): fija/appcomo directorio de trabajo; se crea si no existe. - Línea 12 (
COPY requirements.txt): copiamos solo el fichero de dependencias antes que el código, para que la costosa instalación se cachee y no se repita en cada cambio de código. - Línea 15 (
RUN pip install): instala las dependencias.--no-cache-direvita que pip deje su caché dentro de la imagen, reduciendo el tamaño. - Línea 18 (
COPY . .): copia el resto del proyecto. Al ir después de la instalación, cambiar el código no invalida la capa de dependencias. - Línea 21 (
EXPOSE 5000): documenta el puerto; recuerda que para acceder desde fuera hay que mapearlo con-pal ejecutar. - Línea 24 (
CMD): define el comando por defecto. En forma exec, gestiona correctamente las señales de parada.
Para construir y ejecutar esta imagen:
- La aplicación quedará accesible en
http://localhost:8080.
Errores Comunes y Consejos
- Usar
ADDcuando bastaCOPY:ADDtiene comportamientos implícitos (descompresión, descargas) que pueden sorprender. UsaCOPYsalvo que necesites lo extra. - Pensar que
EXPOSEpublica el puerto: no lo hace; solo documenta. Necesitas-pendocker run. - Confundir
CMDyENTRYPOINT:CMDse reemplaza entero al pasar argumentos;ENTRYPOINTlos recibe como añadidos. - Usar la forma shell por costumbre: prefiere la forma exec
["ejecutable", "arg"]para una correcta gestión de señales. - Copiar todo el código antes de instalar dependencias: rompe la caché en cada cambio. Copia primero los manifiestos.
- Usar
cddentro deRUN: no persiste entre instrucciones. UsaWORKDIR. - Consejo: comenta tus Dockerfiles con
#para explicar decisiones no obvias; facilita el mantenimiento por parte de otros.
Ejercicios
Ejercicio 1
Escribe un Dockerfile para una aplicación Node.js que use node:18-alpine, fije el directorio de trabajo en /app, instale dependencias aprovechando la caché, copie el código, exponga el puerto 3000 y arranque con node server.js.
Ejercicio 2
Dado este Dockerfile, indica qué imprime cada comando:
docker run miimgdocker run miimg personalizado
Ejercicio 3
Explica cuándo usarías ADD en lugar de COPY y por qué COPY es la opción preferida por defecto.
Soluciones
Solución 1
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
EXPOSE 3000
CMD ["node", "server.js"]Se copian primero los manifiestos package*.json y se ejecuta npm ci antes de copiar el resto del código, de modo que la instalación de dependencias se cachee y solo se repita cuando cambien las dependencias.
Solución 2
docker run miimgimprimeMensaje: por defecto. ElENTRYPOINTfijaecho "Mensaje:"y elCMDaporta el argumento por defectopor defecto.docker run miimg personalizadoimprimeMensaje: personalizado. El argumentopersonalizadosustituye alCMD, pero elENTRYPOINTse mantiene.
Solución 3
Usarías ADD cuando necesites descomprimir automáticamente un fichero .tar local o descargar un recurso desde una URL, ya que COPY no hace ninguna de esas cosas. COPY es la opción preferida por defecto porque su comportamiento es explícito y predecible: solo copia ficheros, sin acciones implícitas que puedan dar resultados inesperados.
Conclusión
En esta lección hemos profundizado en el Dockerfile: qué es y cómo se ejecuta, las instrucciones esenciales (FROM, WORKDIR, COPY, ADD, RUN, ENV, EXPOSE, CMD, ENTRYPOINT), la diferencia clave entre CMD y ENTRYPOINT, y un ejemplo completo comentado línea a línea. Con estos fundamentos ya puedes escribir Dockerfiles claros y eficientes para tus propias aplicaciones.
En la siguiente lección, Gestionando Imágenes Docker, aprenderemos a administrar las imágenes una vez construidas: listarlas, eliminarlas, inspeccionarlas, limpiar las que ya no usas con image prune, exportarlas e importarlas con save/load, y controlar el espacio que ocupan en disco.
Docker: De Principiante a Avanzado
Módulo 1: Introducción a Docker
- ¿Qué es Docker?
- Instalando Docker
- Arquitectura de Docker
- Comandos Básicos de Docker
- Entendiendo las Imágenes de Docker
- Creando tu Primer Contenedor Docker
Módulo 2: Trabajando con Imágenes Docker
- Docker Hub y Repositorios
- Construyendo Imágenes Docker
- Conceptos Básicos de Dockerfile
- Gestionando Imágenes Docker
- Etiquetado y Publicación de Imágenes
Módulo 3: Contenedores Docker
- Ejecutando Contenedores
- Ciclo de Vida del Contenedor
- Gestionando Contenedores
- Redes en Docker
- Persistencia de Datos con Volúmenes
Módulo 4: Docker Compose
- Introducción a Docker Compose
- Definiendo Servicios en Docker Compose
- Comandos de Docker Compose
- Aplicaciones Multi-Contenedor
- Variables de Entorno en Docker Compose
Módulo 5: Conceptos Avanzados de Docker
- Profundización en Redes Docker
- Opciones de Almacenamiento Docker
- Mejores Prácticas de Seguridad en Docker
- Optimizando Imágenes Docker
- Registro y Monitoreo en Docker
Módulo 6: Docker en Producción
- CI/CD con Docker
- Orquestando Contenedores con Docker Swarm
- Introducción a Kubernetes
- Desplegando Contenedores Docker en Kubernetes
- Escalado y Balanceo de Carga
