Docker para desarrolladores: imágenes, contenedores y Compose
Has escrito código que funciona en tu máquina, pero cuando un compañero intenta ejecutarlo, se encuentra con dependencias faltantes, conflictos de versiones o errores oscuros. Docker resuelve esto empaquetando tu aplicación y su entorno en una única unidad portátil. En esta guía, aprenderás los fundamentos de Docker: imágenes, contenedores y Compose, con pasos prácticos para contenerizar una aplicación web sencilla.
¿Qué es Docker?
Docker es una plataforma para construir, ejecutar y compartir aplicaciones en contenedores. Un contenedor es un paquete ejecutable ligero y autónomo que incluye todo lo necesario para ejecutar software: código, runtime, herramientas del sistema, librerías y configuraciones. Los contenedores aíslan las aplicaciones entre sí y del sistema anfitrión, garantizando la consistencia entre desarrollo, pruebas y producción.
Imágenes vs contenedores: el plano y la instancia en ejecución
Una imagen es una plantilla de solo lectura que define un contenedor. Es como una clase en programación orientada a objetos. Un contenedor es una instancia ejecutable de una imagen, como un objeto. Puedes crear muchos contenedores a partir de la misma imagen.
Las imágenes se construyen a partir de un Dockerfile, un archivo de texto con instrucciones. Los contenedores se inician con docker run.
Diferencias clave
| Imagen | Contenedor |
|---|---|
| Plantilla de solo lectura | Instancia ejecutable |
Se construye con docker build | Se inicia con docker run |
| Se almacena en un registro (p. ej., Docker Hub) | Se ejecuta en el host |
| Sistema de archivos por capas | Capa de escritura encima |
Construyendo tu primera imagen
Vamos a contenerizar una aplicación sencilla de Node.js. Crea un archivo llamado Dockerfile:
# Use an official Node.js runtime as a parent image
FROM node:18-alpine
# Set the working directory
WORKDIR /app
# Copy package.json and install dependencies
COPY package*.json ./
RUN npm install
# Copy the rest of the application
COPY . .
# Expose the port the app runs on
EXPOSE 3000
# Define the command to run the app
CMD ["node", "server.js"]
Construye la imagen con una etiqueta:
docker build -t my-node-app .
La opción -t nombra y etiqueta la imagen. El . al final especifica el contexto de construcción (el directorio actual).
Ejecutando contenedores
Inicia un contenedor a partir de tu imagen:
docker run -p 3000:3000 -d --name my-app my-node-app
-p 3000:3000: Mapea el puerto 3000 del host al puerto 3000 del contenedor.-d: Ejecuta el contenedor en modo detached (en segundo plano).--name my-app: Asigna un nombre al contenedor para referenciarlo fácilmente.
Consulta los contenedores en ejecución con docker ps. Detén el contenedor con docker stop my-app.
Gestionando datos con volúmenes
Los contenedores son efímeros; los datos dentro de ellos se pierden cuando se elimina el contenedor. Para persistir datos, usa volúmenes. Por ejemplo, para persistir los datos de una base de datos:
docker run -v my-db-data:/var/lib/mysql -d mysql:8
Esto crea un volumen con nombre my-db-data que sobrevive a los reinicios y eliminaciones del contenedor.
Simplificando aplicaciones multicontenedor con Docker Compose
Las aplicaciones reales suelen necesitar múltiples servicios: un servidor web, una base de datos, una caché, etc. Docker Compose te permite definir y ejecutar aplicaciones multicontenedor con un único archivo YAML.
Crea un docker-compose.yml:
version: '3.8'
services:
web:
build: .
ports:
- "3000:3000"
depends_on:
- db
environment:
- DATABASE_URL=postgres://user:pass@db:5432/mydb
db:
image: postgres:15
volumes:
- postgres_data:/var/lib/postgresql/data
environment:
- POSTGRES_USER=user
- POSTGRES_PASSWORD=pass
- POSTGRES_DB=mydb
volumes:
postgres_data:
Ejecuta todo con:
docker compose up -d
Compose construye la imagen web, inicia la base de datos y las conecta en una red compartida. El depends_on asegura que la base de datos se inicie antes que el servicio web.
Comandos esenciales de Docker
docker ps -a: Lista todos los contenedores (incluidos los detenidos).docker images: Lista las imágenes.docker logs my-app: Ver los logs del contenedor.docker exec -it my-app sh: Abre una shell dentro de un contenedor en ejecución.docker compose down: Detiene y elimina los contenedores, redes y volúmenes definidos en el archivo de Compose.
Buenas prácticas para desarrollo
- Usa .dockerignore para excluir archivos (p. ej., node_modules) del contexto de construcción, acelerando las builds.
- Aprovecha las builds multi-stage para mantener pequeñas las imágenes de producción. Por ejemplo, construye tu aplicación en una etapa y copia solo los artefactos necesarios a una imagen de runtime ligera.
- Fija las versiones en tu Dockerfile (p. ej.,
node:18-alpineen lugar denode:latest) para reproducibilidad. - Ejecuta como usuario no root por seguridad. Añade
USER nodedespués de copiar los archivos.
FAQ
¿Cuál es la diferencia entre una imagen de Docker 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 esa imagen. Puedes crear múltiples contenedores a partir de la misma imagen.
¿Cómo persisto datos en Docker?
Usa volúmenes. Los volúmenes con nombre (p. ej., docker run -v my-vol:/data) se almacenan fuera del sistema de archivos del contenedor y persisten entre reinicios y eliminaciones del contenedor.
¿Cuándo debería usar Docker Compose?
Usa Compose cuando tu aplicación consta de múltiples servicios (p. ej., web + base de datos) que necesitan ejecutarse juntos. Simplifica la orquestación para desarrollo y pruebas.
¿Listo para contenerizar tu próximo proyecto? Empieza escribiendo un Dockerfile sencillo y ejecutando tu primer contenedor. Para más herramientas de desarrollo, echa un vistazo al JSON Formatter de TryQuickToolBox para validar respuestas de API mientras pruebas tus servicios contenerizados.