Docker para desarrolladores: imágenes, contenedores y Compose
Has creado una aplicación web que funciona perfectamente en tu máquina, pero cuando la implementas en un servidor o la compartes con un compañero de equipo, falla. Faltan dependencias, hay conflictos de versiones o las variables de entorno son incorrectas. Este es el clásico problema de "funciona en mi máquina". Docker lo resuelve empaquetando tu aplicación y sus dependencias en una unidad estandarizada llamada contenedor. En esta guía, aprenderás los conceptos básicos de Docker—imágenes, contenedores y Compose—y cómo usarlos para optimizar tu flujo de trabajo de desarrollo.
¿Qué es Docker?
Docker es una plataforma para construir, ejecutar y enviar aplicaciones en contenedores. Un contenedor es un paquete ejecutable ligero y autónomo que incluye todo lo necesario para ejecutar un software: código, tiempo de ejecución, herramientas del sistema, bibliotecas y configuraciones. Los contenedores aíslan las aplicaciones entre sí y de la infraestructura subyacente, garantizando la consistencia entre entornos.
Imágenes vs. Contenedores
Una imagen es una plantilla de solo lectura con instrucciones para crear un contenedor. Es como una instantánea de tu aplicación y su entorno. Un contenedor es una instancia ejecutable de una imagen. Puedes crear, iniciar, detener, mover o eliminar contenedores usando la API de Docker o la CLI.
Piensa en una imagen como una clase y en un contenedor como un objeto (instancia) de esa clase. Puedes ejecutar varios contenedores a partir de la misma imagen, cada uno aislado de los demás.
Construir una imagen con un Dockerfile
Un Dockerfile es un archivo de texto con instrucciones para construir una imagen. Aquí tienes un ejemplo simple para una aplicación Node.js:
# Use an official Node.js runtime as a parent image
FROM node:18-alpine
# Set the working directory
WORKDIR /app
# Copy package.json and package-lock.json
COPY package*.json ./
# Install dependencies
RUN npm install
# Copy the rest of the application code
COPY . .
# Expose the port the app runs on
EXPOSE 3000
# Define the command to run the app
CMD ["node", "server.js"]
Para construir la imagen, ejecuta:
docker build -t my-node-app .
La bandera -t etiqueta la imagen con un nombre (my-node-app). El . al final especifica el contexto de construcción (directorio actual).
Ejecutar contenedores
Una vez que tengas una imagen, puedes ejecutar un contenedor:
docker run -p 3000:3000 -d my-node-app
Esto asigna el puerto 3000 de tu host al puerto 3000 del contenedor y lo ejecuta en modo detached (-d).
Comandos comunes:
docker ps– lista los contenedores en ejecucióndocker ps -a– lista todos los contenedores (incluidos los detenidos)docker stop <container_id>– detiene un contenedordocker rm <container_id>– elimina un contenedordocker images– lista las imágenesdocker rmi <image_id>– elimina una imagen
Gestionar datos con volúmenes
Los contenedores son efímeros; cuando se eliminan, sus datos se pierden. Para persistir datos, usa volúmenes. Un volumen es un directorio fuera del sistema de archivos del contenedor, gestionado por Docker.
docker run -v my_volume:/app/data my-node-app
También puedes montar un directorio del host (bind mount) para desarrollo:
docker run -v $(pwd):/app my-node-app
Docker Compose: aplicaciones multi-contenedor
Las aplicaciones del mundo real a menudo requieren múltiples servicios: un servidor web, una base de datos, una caché, etc. Docker Compose te permite definir y ejecutar aplicaciones Docker multi-contenedor usando un archivo YAML. Aquí tienes un ejemplo de docker-compose.yml para una aplicación Node.js con una base de datos PostgreSQL:
version: '3.8'
services:
web:
build: .
ports:
- "3000:3000"
environment:
- DATABASE_URL=postgres://user:password@db:5432/mydb
depends_on:
- db
db:
image: postgres:15-alpine
environment:
- POSTGRES_USER=user
- POSTGRES_PASSWORD=password
- POSTGRES_DB=mydb
volumes:
- postgres_data:/var/lib/postgresql/data
volumes:
postgres_data:
Ejecuta todo el stack con:
docker compose up -d
Esto construye el servicio web, descarga la imagen de Postgres, crea una red e inicia ambos contenedores. El depends_on asegura que la base de datos se inicie antes que el servicio web.
Para detener y eliminar contenedores, redes y volúmenes:
docker compose down -v
Mejores prácticas para Docker en desarrollo
- Usa .dockerignore para excluir archivos como
node_modulesy.gitdel contexto de construcción. - Aprovecha las compilaciones multi-etapa para mantener las imágenes pequeñas. Por ejemplo, construye tu aplicación en una etapa y copia solo los artefactos de producción a una imagen de tiempo de ejecución más pequeña.
- Fija versiones en tu Dockerfile (por ejemplo,
node:18-alpineen lugar denode:latest) para reproducibilidad. - Usa volúmenes para datos persistentes y bind mounts para recarga de código en vivo durante el desarrollo.
- Mantén los contenedores sin estado; almacena el estado en volúmenes o servicios externos.
Comparación: Docker vs. Máquinas virtuales
| Aspecto | Contenedores Docker | Máquinas virtuales |
|---|---|---|
| Tiempo de inicio | Segundos | Minutos |
| Uso de recursos | Ligero (comparte el kernel del SO) | Pesado (SO completo por VM) |
| Aislamiento | A nivel de proceso | A nivel de hardware |
| Portabilidad | Alta (se ejecuta donde sea que Docker se ejecute) | Limitada por la compatibilidad del hipervisor |
Preguntas frecuentes
¿Cuál es la diferencia entre una imagen y un contenedor?
Una imagen es una plantilla de solo lectura que define la aplicación y su entorno. Un contenedor es una instancia en ejecución de una imagen. Puedes crear múltiples contenedores a partir de la misma imagen.
¿Cuándo debo usar Docker Compose?
Usa Docker Compose cuando tu aplicación consta de múltiples servicios (por ejemplo, servidor web, base de datos, caché) que necesitan trabajar juntos. Simplifica la orquestación para desarrollo y pruebas.
¿Cómo persisto datos en Docker?
Usa volúmenes para persistir datos fuera del sistema de archivos del contenedor. Los volúmenes son gestionados por Docker y se pueden compartir entre contenedores. Los bind mounts también se pueden usar para montar directorios del host.
¿Listo para contenerizar tu próximo proyecto? Comienza escribiendo un Dockerfile para una aplicación simple, luego agrega servicios gradualmente con Docker Compose. Para más herramientas de desarrollo, revisa nuestro JSON Formatter para validar y embellecer tus archivos de configuración.