2.1Escribir tu primer Dockerfile
Lo esencial en 30 segundos
- 5 instrucciones = 1 imagen ejecutable : FROM, WORKDIR, COPY, RUN, CMD — cada una tiene un rol distinto.
- El build cocina, el run ejecuta : el build fija las capas, el run arranca el contenedor — lee la última etapa en caso de fallo.
- RUN construye, CMD arranca : instalar en el build, lanzar en el run — nunca el servidor en RUN.
Mini-caso hilado — Idir y la app que no viajaba
Idir, devops junior en Argel, debe entregar la pequeña app Node de su equipo en otra máquina : « funciona en mi equipo » ya no basta. Escribe su primer Dockerfile en la raíz del proyecto : base oficial, directorio de trabajo, depósito del código, instalación en el build, comando de arranque. Compila en mon-app:1.0 y luego lanza con el puerto publicado : la app responde en localhost:3000, en cualquier máquina. Esta app « empaquetada por Idir » vuelve en 2.2 (build más rápido e imagen más ligera), 2.3 (etiqueta y publicación), 2.4 (puerto configurable) y 2.5 (versión reforzada non-root).
Hilo conductor del nivel 2 : la app de Idir vuelve en el curso, el esquema, el script y el desafío 48h — y continúa en 2.2, 2.3, 2.4 y 2.5.
1. Las 5 instrucciones vitales
FROM elige la base (por ejemplo node:20-slim, una imagen oficial ligera), WORKDIR fija el directorio de trabajo interno (creado si falta), COPY deposita el código desde el contexto, RUN instala durante el build (cada RUN fija una capa), CMD define el comando de arranque del contenedor. Cinco líneas bastan para un primer servicio funcional. Idir las escribe en este orden : base, directorio, código, instalación, lanzamiento.
2. Build y luego run : los dos tiempos
docker build -t mon-app:1.0 . cocina la imagen : el punto final designa el contexto, es decir la carpeta enviada al daemon para el build. docker run la ejecuta : el contenedor arranca y publica sus puertos. Si el build falla, se lee la última etapa mostrada : esa es la culpable (dependencia faltante, errata, red cortada). Idir nombra su imagen nombre:versión desde el build para orientarse.
3. CMD frente a RUN : no confundirlos más
RUN se ejecuta durante el build (instalar, compilar) y fija una capa en la imagen. CMD se ejecuta al arrancar el contenedor (lanzar el servidor), en cada run. Poner el lanzamiento en RUN fija un build que nunca arranca : el error clásico del principiante. Se escribe el CMD en forma JSON (["node", "server.js"]), un único comando de arranque. Idir instala con RUN y arranca con CMD : construir y luego lanzar, nunca al revés.
🔎 Ejemplo concreto — 🔎 Ejemplo concreto — Una app Node empaquetada
La app de Idir, del código a la imagen ejecutable :
FROM node:20-slim
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY . .
EXPOSE 3000
CMD ["node", "server.js"]
docker build -t mon-app:1.0 .
docker run -d --name app -p 3000:3000 mon-app:1.0
Lectura : las dependencias se instalan en el build (RUN), el servidor arranca en el run (CMD), el puerto se declara (EXPOSE) y se publica (-p).
Lección : copiar package*.json primero ya prepara la optimización de la caché (2.2).
Vocabulario de la sección
- FROM
- La imagen base del Dockerfile, punto de partida de todas las capas (preferiblemente una imagen oficial ligera).
Ejemplo sencillo: Idir parte de node:20-slim para su app Node.
Lo que no es: el comando que lanza el contenedor.
Reflejo: base oficial ligera primero.
- WORKDIR
- El directorio de trabajo interno del contenedor, creado automáticamente si falta ; todas las instrucciones siguientes se refieren a él.
Ejemplo sencillo: Idir fija WORKDIR /app antes de copiar su código.
Lo que no es: un volumen que persiste los datos.
Reflejo: fijar el directorio antes de copiar.
- RUN
- Ejecutar un comando DURANTE el build (instalar, compilar) ; cada RUN fija una capa en la imagen.
Ejemplo sencillo: Idir instala sus dependencias con RUN npm ci.
Lo que no es: el comando de arranque del contenedor.
Reflejo: preparar la imagen = RUN.
- CMD
- El comando de ARRANQUE del contenedor, ejecutado en cada docker run ; uno solo, preferiblemente en forma JSON.
Ejemplo sencillo: Idir arranca con CMD ["node", "server.js"].
Lo que no es: una instalación fijada en el build.
Reflejo: dar vida al contenedor = CMD.
En la práctica — En la práctica — Empaquetar una mini-app
- Crea una carpeta con un server.js que responda « OK » en el puerto 3000.
- Escribe el Dockerfile (FROM, WORKDIR, COPY, RUN, EXPOSE, CMD) en la raíz del proyecto.
- Compila en mon-app:1.0 y lee cada etapa del build.
- Lanza con -p 3000:3000 y abre localhost:3000.
Script listo para decir — explicar el Dockerfile a un colega (30 s)
« Este Dockerfile hace cinco cosas : parte de una base oficial, fija el directorio de trabajo, copia el código, instala en el build con RUN y arranca con CMD. Se cocina con docker build, se lanza con docker run : la app funciona igual en todas las máquinas. RUN construye, CMD arranca — es la única distinción que hay que recordar. »
Para decir mostrando el Dockerfile de Idir y luego el build etapa por etapa.
Modelo copiar-pegar — primer Dockerfile Node
FROM node:20-slim
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY . .
EXPOSE 3000
CMD ["node", "server.js"]
App Python
Idir empaqueta un script Python : FROM python:3.12-slim, COPY de requirements y luego RUN pip install, CMD en forma JSON.
Página estática
Un sitio escaparate : Idir parte de una base de servidor ligera, copia los archivos, expone el puerto, CMD lanza el servidor.
Contexto enorme
Build lento : Idir añade un .dockerignore (node_modules, .git) y el contexto vuelve a ser ligero.
RUN con trampa
Un colega lanza el servidor en RUN : el build se fija y nunca arranca — Idir mueve el lanzamiento a CMD.
✓ Buenas prácticas
- Un único comando de arranque en CMD (forma JSON).
- Nombrar la imagen nombre:versión desde el build.
- Leer el build etapa por etapa en caso de fallo.
✗ Errores frecuentes
- Lanzar el servidor en RUN en lugar de CMD.
- Olvidar el .dockerignore (contexto enorme).
- Copiar node_modules local en la imagen.
Puntos clave
- FROM, WORKDIR, COPY, RUN, CMD : la base de todo Dockerfile.
- El build cocina la imagen, el run la ejecuta (el « . » es el contexto enviado al daemon).
- RUN = build (fija una capa), CMD = arranque (forma JSON).
- Nombrar nombre:versión desde el build ; leer la última etapa en caso de fallo.
Desafío 48h — aplica mañana
Empaqueta una mini-app real (la tuya o la de Idir) : Dockerfile en 7 líneas (FROM, WORKDIR, COPY, RUN, EXPOSE, CMD), build etiquetado 1.0 leído etapa por etapa, y luego run con puerto publicado verificado en el navegador.
- Puedo escribir un Dockerfile mínimo (FROM, WORKDIR, COPY, RUN, EXPOSE, CMD).
- Sé compilar (contexto incluido) y leer un fallo en la última etapa.
- Distingo RUN (build) y CMD (arranque) sin dudar.
Preguntas frecuentes
¿Dónde escribir el Dockerfile ?
En la raíz del proyecto, llamado exactamente « Dockerfile ». El contexto es la carpeta enviada al daemon : quédate en la raíz para COPY . . y aligéralo con un .dockerignore (node_modules, .git) para no enviar cientos de MB en cada build.
¿Por qué el build es tan lento ?
A menudo por un contexto enorme (node_modules, .git) enviado cada vez. El .dockerignore y el orden de las capas (dependencias primero, código después — ver 2.2) resuelven el 80 % de los casos antes que cualquier otra optimización.
En un Dockerfile, ¿qué hace RUN frente a CMD ?