Si alguna vez escuchaste “en mi máquina sí funciona”, este artículo es para ti. Docker resuelve exactamente ese problema: empaqueta tu aplicación junto con todo lo que necesita para correr, garantizando que funcione igual en cualquier entorno.
¿Qué es Docker y por qué importa?
Docker es una plataforma de contenedorización. Un contenedor es como una caja sellada que incluye tu código, sus dependencias, las variables de entorno y la configuración del sistema operativo necesaria. No importa si el servidor destino usa Ubuntu, CentOS o Windows — el contenedor corre igual.
Antes de Docker, desplegar una aplicación implicaba:
- Documentar manualmente las dependencias
- Rezar para que las versiones coincidieran
- Pasar horas depurando errores de entorno
Con Docker, el proceso se reduce a docker build y docker run.
Conceptos clave antes de empezar
Imagen vs Contenedor
Una imagen es la plantilla: define el sistema base, las dependencias y los comandos de inicio. Un contenedor es una instancia en ejecución de esa imagen. Puedes tener 10 contenedores corriendo a partir de la misma imagen.
Dockerfile
El Dockerfile es el recetario que Docker sigue para construir tu imagen:
# Imagen base
FROM node:20-alpine
# Directorio de trabajo dentro del contenedor
WORKDIR /app
# Copiar dependencias primero (mejor caché)
COPY package*.json ./
RUN npm ci --only=production
# Copiar el resto del código
COPY . .
# Puerto que expone el contenedor
EXPOSE 3000
# Comando de inicio
CMD ["node", "server.js"]
Construir y correr
# Construir la imagen con el tag "mi-app:latest"
docker build -t mi-app:latest .
# Correr un contenedor en segundo plano, mapeando el puerto 3000
docker run -d -p 3000:3000 --name mi-app mi-app:latest
# Ver contenedores activos
docker ps
# Ver logs en tiempo real
docker logs -f mi-app
Docker Compose: múltiples servicios, una sola configuración
La mayoría de las aplicaciones reales necesitan más de un servicio: un servidor web, una base de datos, un caché. Docker Compose orquesta todos en un solo archivo docker-compose.yml:
version: "3.9"
services:
web:
build: .
ports:
- "3000:3000"
environment:
- DATABASE_URL=postgres://user:pass@db:5432/mydb
depends_on:
- db
db:
image: postgres:16-alpine
environment:
POSTGRES_USER: user
POSTGRES_PASSWORD: pass
POSTGRES_DB: mydb
volumes:
- postgres_data:/var/lib/postgresql/data
volumes:
postgres_data:
Levantar todo el stack:
docker compose up -d
Apagar y limpiar:
docker compose down
Volúmenes: persistencia de datos
Por defecto, cuando un contenedor muere, sus datos desaparecen. Los volúmenes resuelven eso montando una carpeta del host (o un volumen gestionado por Docker) dentro del contenedor:
# Montar la carpeta local ./data en /app/data del contenedor
docker run -v ./data:/app/data mi-app:latest
Buenas prácticas que marcan la diferencia
1. Usa imágenes Alpine siempre que puedas. node:20-alpine pesa ~50 MB vs ~900 MB de node:20. Menos peso = builds más rápidos y menos superficie de ataque.
2. Ordena las capas del Dockerfile estratégicamente. Docker cachea capa por capa. Pon las instrucciones que cambian menos (instalar dependencias) antes de las que cambian más (copiar tu código).
3. Usa .dockerignore. Igual que .gitignore, evita copiar node_modules, .git y archivos innecesarios a la imagen:
node_modules
.git
dist
*.log
.env
4. No corras contenedores como root. Agrega un usuario sin privilegios en tu Dockerfile:
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
USER appuser
5. Separa la imagen de build de la de producción con multi-stage builds:
# Etapa 1: build
FROM node:20-alpine AS builder
WORKDIR /app
COPY . .
RUN npm ci && npm run build
# Etapa 2: producción (solo el resultado)
FROM node:20-alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/package*.json ./
RUN npm ci --only=production
CMD ["node", "dist/server.js"]
Comandos que usarás todos los días
| Comando | Qué hace |
|---|---|
docker ps | Lista contenedores activos |
docker ps -a | Lista todos los contenedores |
docker images | Lista las imágenes locales |
docker exec -it <nombre> sh | Abre una shell dentro del contenedor |
docker logs -f <nombre> | Logs en tiempo real |
docker stop <nombre> | Detiene un contenedor |
docker rm <nombre> | Elimina un contenedor detenido |
docker rmi <imagen> | Elimina una imagen |
docker system prune | Limpia todo lo que no se usa |
¿Y ahora qué?
Con esto ya puedes contenerizar cualquier aplicación web. El siguiente paso natural es aprender sobre registros de imágenes (Docker Hub, GitLab Registry) y orquestación con Kubernetes o Docker Swarm para ambientes de producción a escala.
Docker no es solo una herramienta de moda — es el estándar de la industria para entregar software de forma consistente y predecible.