
Buenas prácticas para escribir Dockerfiles: guía para desarrolladores
Consejos para desarrolladores que escriben Dockerfiles: WORKDIR, multi-stage, imágenes base, ARG y ENV, ENTRYPOINT y CMD, descargas y .dockerignore.
Por Víctor Piles TemporalSRE y técnico de sistemasPublicado el 5 min de lectura
Un Dockerfile es un archivo de texto con las instrucciones para construir una imagen nueva. Docker trata cada instrucción que lee del archivo como una capa distinta, que se puede compartir por completo con otras recetas.
En resumen: trabajad siempre sobre un WORKDIR, separad la compilación con multi-stage builds para que la imagen final lleve solo lo necesario, cread imágenes base con vuestras dependencias, usad ARG y ENV para que las recetas sean reutilizables, entended la diferencia entre ENTRYPOINT y CMD, descargad los paquetes externos con curl en lugar de ADD y excluid con .dockerignore todo lo que no haga falta.
Desarrolladores y Dockerfiles
Si sois desarrolladores, conocer bien Docker es imprescindible. Hoy repaso varios consejos útiles para escribir Dockerfiles.
¿Quién debe generar las imágenes, los desarrolladores o el equipo de DevOps?
En este debate lo tengo claro: en mi opinión, los desarrolladores. Son quienes conocen las dependencias y las necesidades de su código.
Como DevOps/SRE me gusta echar una mano, y espero que con estos consejos podáis generar imágenes más estables y de más calidad.
Buenas prácticas para escribir Dockerfiles
Definid el directorio de trabajo y trabajad sobre él
Docker trabaja sobre el WORKDIR, así que no hace falta indicar rutas absolutas al copiar si no son estrictamente necesarias.
FROM php:8.3-apache
WORKDIR /app
COPY . .
En este caso se copia todo el directorio actual (PWD) a la ruta /app de la imagen.
Usad multi-stage builds para copiar solo las capas necesarias entre imágenes
- Con este método podéis trabajar solo con Docker: no necesitáis ninguna dependencia instalada en vuestro equipo. El multi-stage lo hace por vosotros, y así es mucho más fácil probar distintas versiones de las herramientas que usáis.
- Declarad en pasos pequeños todo el ciclo de compilación de vuestro código: queda mucho más declarativo y funcional.
- En el ejemplo siguiente se generan imágenes intermedias para construir una última imagen con los estáticos que se van a servir.
FROM node:latest AS install
WORKDIR /app
COPY . .
RUN npm install
#-----------#
FROM install AS build
RUN npm run build
#-----------#
FROM nginx:alpine AS standalone
WORKDIR /usr/share/nginx/html
COPY --from=build --chown=nginx:nginx /app/build .
En este caso se generan dos imágenes temporales donde se instala todo lo necesario y se compila el código, y la última imagen sirve solo los estáticos.
Compilad una imagen con todas sus dependencias y usadla como base para el resto de aplicaciones
Dockerfile-base (construidla con una etiqueta como base-php):
FROM php:8.3-apache
COPY php.ini-production $PHP_INI_DIR/php.ini
RUN a2enmod alias
Dockerfile:
FROM base-php
WORKDIR /var/www/html
COPY . .
En este caso se genera una imagen base con todas las dependencias.
Usad argumentos para generar recetas reutilizables
ARG BASE_IMAGE
FROM $BASE_IMAGE
ARG FILE
WORKDIR /app
COPY --chmod=600 $FILE .
Compiladla con el siguiente comando:
docker build --build-arg BASE_IMAGE=alpine \
--build-arg FILE=deploy.yml .
En este caso los argumentos sirven para generar recetas que se pueden compartir.
Instalación de paquetes
Esta es una instrucción bien formada para instalar todo lo necesario:
FROM ubuntu:24.04
RUN apt-get update && apt-get install -y \
nodejs \
python3 \
python3-pip \
curl \
&& rm -rf /var/lib/apt/lists/*
En este caso se instalan todas las dependencias de forma bien estructurada.
Definir variables dentro de la receta no es una mala práctica
Usad las variables que vuestra aplicación necesita conocer para compilar correctamente.
FROM node:latest
ARG PUBLIC_URL
ENV URL=$PUBLIC_URL
RUN npm run build
Para compilar esta receta, ejecutad el siguiente comando:
docker build --build-arg PUBLIC_URL=test-dev .
En este caso se usan variables de entorno para compilar el código.
Cómo usar ENTRYPOINT y CMD
Las dos instrucciones sirven para ejecutar comandos cuando arranca el contenedor:
- ENTRYPOINT: lo que ejecuta el contenedor al arrancar.
- CMD: argumentos que se añaden al
ENTRYPOINT, o un comando puntual que se ejecuta en el contenedor.
FROM alpine
ENTRYPOINT ["sleep"]
CMD ["10"]
FROM alpine
WORKDIR /usr/local/bin/
COPY docker-entrypoint.sh .
RUN chmod +x docker-entrypoint.sh
ENTRYPOINT [ "sh", "/usr/local/bin/docker-entrypoint.sh" ]
En estos casos se usan ENTRYPOINT y CMD para ejecutar comandos al arrancar los contenedores.
Dejad que Docker descargue los paquetes externos necesarios
Quizá hayáis usado antes la instrucción ADD para esto. Para obtener paquetes desde URL remotas está muy desaconsejada: descargándolos vosotros en un RUN podéis borrar en el mismo paso los archivos que ya no necesitáis.
FROM alpine
RUN apk add --no-cache curl \
&& curl -SL https://example.com/test.tar.gz | tar -xz
En este caso se usa curl para descargar los paquetes externos, sin la instrucción ADD que usa todo el mundo.
Usad .dockerignore
Subid solo lo que necesitáis y excluid todos los archivos que no haga falta enviar. La lógica es la misma que la de .gitignore.
commands
node_modules
public
scripts
src*
.yml*
.yaml
.env*
.git*
.json
yarn.lock
...
Segunda parte
Estos son los básicos. En la segunda parte hablo de comprobaciones de salud (HEALTHCHECK), de la sintaxis heredoc y de los montajes de BuildKit para secretos y claves SSH.
Si queréis que estas prácticas se apliquen de forma automática en vuestros despliegues, en infraestructura como código, GitOps y CI/CD explico cómo monto los pipelines que construyen las imágenes, y en gestión de vulnerabilidades cómo las escaneo. Si necesitáis algo, escribidme desde contacto.
Publicado originalmente en inglés en Medium.


