Docker para Desarrolladores: Contenedores, Imágenes y Compose

· 12 min de lectura

Tabla de Contenidos

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:

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:

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:

¿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