Docker para Desarrolladores: Contenedores, Imágenes y Compose
· 12 min de lectura
Tabla de Contenidos
- Entendiendo Docker: Por Qué Importa
- Conceptos Fundamentales Explicados
- Anatomía y Estructura del Dockerfile
- Mejores Prácticas de Dockerfile
- Comandos Esenciales de Docker
- Docker Compose para Aplicaciones Multi-Contenedor
- Flujo de Trabajo de Desarrollo y Consejos
- Depuración y Solución de Problemas
- Consideraciones de Seguridad
- Optimización del Rendimiento
- Preguntas Frecuentes
- Artículos Relacionados
Entendiendo Docker: Por Qué Importa
Docker ha cambiado fundamentalmente cómo los desarrolladores construyen, envían y ejecutan aplicaciones. Antes de Docker, configurar entornos de desarrollo era una pesadilla de conflictos de dependencias, desajustes de versiones y el infame problema de "funciona en mi máquina".
Docker resuelve esto empaquetando tu aplicación con todo lo que necesita para ejecutarse—código, runtime, herramientas del sistema, bibliotecas y configuraciones—en una unidad estandarizada llamada contenedor. Este contenedor se ejecuta de manera idéntica en tu laptop, la máquina de tu colega y los servidores de producción.
Los beneficios son inmediatos y tangibles:
- Consistencia: Los entornos de desarrollo, staging y producción son idénticos
- Aislamiento: Cada aplicación se ejecuta en su propio contenedor sin conflictos
- Portabilidad: Los contenedores se ejecutan en cualquier lugar donde Docker esté instalado
- Eficiencia: Los contenedores comparten el kernel del sistema operativo host, usando muchos menos recursos que las máquinas virtuales
- Velocidad: Los contenedores inician en segundos, no en minutos
Para los desarrolladores, Docker significa que puedes iniciar un stack completo de aplicación—servidor web, base de datos, caché, cola de mensajes—con un solo comando. No más pasar horas instalando PostgreSQL o depurando problemas de configuración de Redis.
Conceptos Fundamentales Explicados
Entender los conceptos fundamentales de Docker es esencial antes de sumergirse en el uso práctico. Estos bloques de construcción trabajan juntos para crear el ecosistema de Docker.
| Concepto | Qué Es | Analogía | Características Clave |
|---|---|---|---|
| Imagen | Plantilla de solo lectura con app + dependencias | Una definición de clase en POO | Inmutable, en capas, compartible |
| Contenedor | Instancia en ejecución de una imagen | Un objeto (instancia de clase) | Aislado, efímero, sin estado |
| Dockerfile | Instrucciones para construir una imagen | Una receta o plano | Archivo de texto, controlado por versiones |
| Volumen | Almacenamiento persistente fuera del contenedor | Un disco duro externo | Sobrevive a la eliminación del contenedor |
| Red | Comunicación entre contenedores | Una red de área local (LAN) | Aislada, configurable, segura |
| Registro | Almacenamiento para imágenes (Docker Hub) | npm/PyPI para contenedores | Público o privado, versionado |
Imágenes vs Contenedores: La Distinción Crítica
Aquí es donde muchos principiantes se confunden. Una imagen es una instantánea estática—piensa en ella como una plantilla congelada. Un contenedor es lo que obtienes cuando ejecutas esa imagen—es un proceso vivo y en ejecución.
Puedes crear contenedores ilimitados desde una sola imagen, al igual que puedes crear múltiples objetos desde una clase. Cada contenedor está aislado de los demás, incluso si todos están basados en la misma imagen.
Consejo profesional: Las imágenes se construyen en capas. Cada instrucción en tu Dockerfile crea una nueva capa. Docker almacena en caché estas capas, por lo que reconstruir una imagen solo reconstruye las capas que cambiaron. Esto hace que las construcciones sean increíblemente rápidas.
Volúmenes: Resolviendo el Problema de Persistencia
Los contenedores son efímeros por diseño—cuando eliminas un contenedor, todo dentro de él desaparece. Esto es genial para aplicaciones sin estado pero problemático para bases de datos o cualquier dato que necesites conservar.
Los volúmenes resuelven esto almacenando datos fuera del sistema de archivos del contenedor. Los datos persisten incluso cuando los contenedores se eliminan y recrean. Hay tres tipos de montajes:
- Volúmenes: Administrados por Docker, almacenados en el área de almacenamiento de Docker (recomendado)
- Montajes de enlace: Mapean un directorio del host directamente en el contenedor (útil para desarrollo)
- Montajes tmpfs: Almacenados solo en la memoria del host, nunca escritos en disco (para datos sensibles)
Anatomía y Estructura del Dockerfile
Un Dockerfile es un documento de texto que contiene instrucciones para construir una imagen de Docker. Cada instrucción crea una capa en la imagen, y Docker almacena en caché estas capas para eficiencia.
Aquí hay un desglose de las instrucciones de Dockerfile más comunes:
| Instrucción | Propósito | Ejemplo | Mejor Práctica |
|---|---|---|---|
FROM |
Imagen base desde la cual construir | FROM node:22-alpine |
Usa versiones específicas, prefiere alpine |
WORKDIR |
Establecer directorio de trabajo | WORKDIR /app |
Usa rutas absolutas |
COPY |
Copiar archivos del host a la imagen | COPY package.json . |
Copia dependencias primero para caché |
RUN |
Ejecutar comandos durante la construcción | RUN npm install |
Encadena comandos con && para reducir capas |
EXPOSE |
Documentar en qué puerto escucha la app | EXPOSE 3000 |
Solo documentación, no publica el puerto |
ENV |
Establecer variables de entorno | ENV NODE_ENV=production |
Usa para configuración |
USER |
Establecer usuario para comandos subsiguientes | USER node |
Nunca ejecutes como root en producción |
CMD |
Comando predeterminado cuando el contenedor inicia | CMD ["node", "server.js"] |
Usa formato de array JSON |
Entendiendo el Almacenamiento en Caché de Capas
Docker construye imágenes capa por capa, de arriba hacia abajo. Cada instrucción crea una nueva capa. Si una capa no ha cambiado, Docker reutiliza la versión en caché en lugar de reconstruirla.
Por eso debes estructurar tu Dockerfile para poner las instrucciones que rara vez cambian en la parte superior y las que cambian frecuentemente en la parte inferior. Por ejemplo, tus dependencias cambian menos a menudo que tu código fuente, así que instala las dependencias antes de copiar los archivos fuente.
# Malo: Copia todo primero, luego instala
COPY . .
RUN npm install
# Bueno: Instala dependencias primero, aprovecha caché
COPY package*.json ./
RUN npm install
COPY . .
Mejores Prácticas de Dockerfile
Escribir Dockerfiles eficientes es un arte. Aquí hay un ejemplo listo para producción que demuestra múltiples mejores prácticas:
# Construcción multi-etapa: etapa de construcción
FROM node:22-alpine AS builder
WORKDIR /app
# Copia archivos de dependencias primero para mejor caché
COPY package*.json ./
RUN npm ci --production
# Copia código fuente y construye
COPY . .
RUN npm run build
# Construcción multi-etapa: etapa de producción
FROM node:22-alpine
WORKDIR /app
# Copia solo lo necesario del constructor
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY package*.json ./
# Seguridad: ejecuta como usuario no root
USER node
# Documenta el puerto (no lo publica realmente)
EXPOSE 3000
# Verificación de salud para orquestación de contenedores
HEALTHCHECK --interval=30s --timeout=3s \
CMD node healthcheck.js || exit 1
# Inicia la aplicación
CMD ["node", "dist/server.js"]
Construcciones Multi-Etapa: Reducción Dramática de Tamaño
Las construcciones multi-etapa te permiten usar múltiples declaraciones FROM en un Dockerfile. Cada FROM inicia una nueva etapa, y puedes copiar artefactos de etapas anteriores.
La magia ocurre porque solo la etapa final se convierte en tu imagen. Las herramientas de construcción, compiladores y archivos intermedios permanecen en etapas anteriores y nunca llegan a producción. Esto puede reducir los tamaños de imagen en 10x o más.
Consejo rápido: Nombra tus etapas con AS builder para que puedas referenciarlas más tarde con COPY --from=builder. Esto hace que tu Dockerfile sea más legible y mantenible.
Imágenes Alpine: Pequeñas pero Poderosas
Alpine Linux es una distribución Linux mínima que tiene solo 5MB de tamaño. Compara eso con imágenes basadas en Ubuntu de más de 900MB. Para la mayoría de las aplicaciones, Alpine proporciona todo lo que necesitas.
El compromiso es que Alpine usa musl libc en lugar de glibc, lo que ocasionalmente puede causar problemas de compatibilidad con binarios precompilados. Para el 95% de los casos de uso, Alpine funciona perfectamente y reduce dramáticamente el tamaño de la imagen, el tiempo de descarga y la superficie de ataque.
El Archivo .dockerignore
Al igual que .gitignore, un archivo .dockerignore le dice a Docker qué archivos excluir al construir imágenes. Esto acelera las construcciones y reduce el tamaño de la imagen.
# Ejemplo de .dockerignore
node_modules
npm-debug.log
.git
.gitignore
README.md
.env
.env.local
dist
coverage
.vscode
.idea
*.log
.DS_Store
Mejores Prácticas de Seguridad
La seguridad debe estar integrada en tus Dockerfiles desde el principio:
- Nunca ejecutes como root: Usa
USER nodeo crea un usuario dedicado - Fija versiones específicas: Usa
node:22.1.0-alpinenonode:latest - Escanea en busca de vulnerabilidades: Usa
docker scan myimagepara verificar CVEs conocidos - Minimiza paquetes instalados: Menos software significa menos vulnerabilidades
- Usa imágenes base oficiales: Están mantenidas y actualizadas regularmente
- No almacenes secretos en imágenes: Usa variables de entorno o herramientas de gestión de secretos
¿Necesitas ayuda generando Dockerfiles optimizados? Prueba nuestra herramienta Generador de Dockerfile.
Comandos Esenciales de Docker
Dominar estos comandos cubrirá el 90% de tu uso diario de Docker. Cada comando incluye ejemplos prácticos y flags comunes.
Construcción y Ejecución
# Construir una imagen desde Dockerfile en el directorio actual
docker build -t myapp:1.0 .
# Construir con argumentos de construcción
docker build --build-arg NODE_ENV=production -t myapp:1.0 .
# Construir sin caché (forzar reconstrucción)
docker build --no-cache -t myapp:1.0 .
# Ejecutar un contenedor en modo separado
docker run -d -p 3000:3000 --name myapp myapp:1.0
# Ejecutar con variables de entorno
docker run -d -p 3000:3000 -e NODE_ENV=production --name myapp myapp:1.0
# Ejecutar con montaje de volumen
docker run -d -p 3000:3000 -v $(pwd)/data:/app/data --name myapp myapp:1.0
# Ejecutar interactivamente con acceso a shell
docker run -it --rm myapp:1.0 sh
Gestión de Contenedores
# Listar contenedores en ejecución
docker ps
# Listar todos los contenedores (incluyendo detenidos)
docker ps -a
# Ver logs del contenedor
docker logs myapp
# Seguir logs en tiempo real
docker logs -f myapp
# Ver últimas 100 líneas de logs
docker logs --tail 100 myapp
# Ejecutar comando en contenedor en ejecución
docker exec -it myapp sh
# Ejecutar un comando único
docker exec myapp npm test
# Detener un contenedor graciosamente
docker stop myapp
# Matar un contenedor inmediatamente
docker kill myapp
# Eliminar un contenedor detenido
docker rm myapp
# Detener y eliminar en un comando
docker stop myapp && docker rm myapp
# Eliminar todos los contenedores detenidos
docker container prune
Trabajando con Imágenes
# Listar todas las imágenes
docker images
# Listar imágenes con formato personalizado
docker images --format "table {{.Repository}}\t{{.Tag}}\t{{.Size}}"
# Eliminar una imagen
docker rmi myapp:1.0
# Eliminar todas las imágenes no utilizadas
docker image prune -a
# Etiquetar una imagen
docker tag myapp:1.0 myapp:latest
# Subir al registro
docker push myregistry.com/myapp:1.0
# Descargar del registro
docker pull myregistry.com/myapp:1.0
# Guardar imagen en archivo tar
docker save myapp:1.0 > myapp.tar
# Cargar imagen desde archivo tar
docker load < myapp.tar
Gestión del Sistema
# Ver uso de disco
docker system df
# Eliminar todos los datos no utilizados (contenedores, redes, imágenes, caché)
docker system prune -a
# Ver estadísticas de contenedores en tiempo real
docker stats
# Inspeccionar detalles del contenedor (salida JSON)
docker inspect myapp
# Ver procesos del contenedor
docker top myapp
Consejo profesional: Agrega el flag --rm al ejecutar contenedores para pruebas. Esto elimina automáticamente el contenedor cuando se detiene, manteniendo tu sistema limpio: docker run --rm -it myapp:1.0 sh
Docker Compose para Aplicaciones Multi-Contenedor
Docker Compose es una herramienta para definir y ejecutar aplicaciones multi-contenedor. En lugar de ejecutar múltiples comandos docker run, defines todo en un solo archivo YAML.
Esto es esencial para aplicaciones modernas que típicamente consisten en múltiples servicios: un servidor web, base de datos, caché, cola de mensajes y más.
Ejemplo Completo de Docker Compose
# docker-compose.yml
version: '3.8'
services:
# W