Tema
Docker y contenedores para desarrollo local
La idea: que todo el equipo trabaje con el mismo entorno (SO, dependencias,
servicios, configuración) para evitar el clásico “en mi máquina sí funciona”
y hacer los despliegues más predecibles.
Reproducible
Rápido de onboardear
Aísla dependencias
Requiere disciplina
Curva inicial
Agenda
Qué veremos
Ruta corta para entender y aplicar Docker en dev local.
Contenido
- Qué es un contenedor (vs VM)
- Problema: “en mi máquina sí funciona”
- Docker en dev: Dockerfile + docker compose
- Ejemplo práctico (app + base de datos)
- Buenas prácticas y anti-patrones
- Checklist para equipos
Objetivo
- Que cualquiera del equipo pueda ejecutar el proyecto con 1 comando.
- Que el entorno de dev se parezca al de QA/Prod.
- Reducir “works on my machine” a casi cero.
Conceptos
Contenedor ≠ máquina virtual
Un contenedor es un proceso aislado que comparte el kernel del host, con su propio
filesystem, red y variables. Una VM emula hardware y corre un SO completo.
Contenedor
- Arranque rápido (segundos)
- Menos consumo (no hay SO completo)
- Empaqueta dependencias y runtime
- Ideal para servicios y entornos reproducibles
VM
- Más pesado (SO por VM)
- Arranque más lento
- Aislamiento más “fuerte” a nivel SO
- Útil cuando necesitas un SO distinto o drivers específicos
Idea clave
En dev, Docker te permite normalizar versiones de runtime (Node/Java/Python),
librerías del sistema (OpenSSL, libc, etc.) y servicios (Postgres, Redis).
Dolor real
“En mi máquina sí funciona”
La mayoría de fallos “fantasma” vienen de diferencias invisibles entre equipos.
Causas típicas
- Versiones distintas de runtime: Node 18 vs 20, Java 17 vs 21
- Dependencias del SO: libpq, imagemagick, locales, tzdata
- Servicios locales: DB con datos distintos, extensiones faltantes
- Variables de entorno y secretos en archivos personales
- Permisos/paths: Windows vs Linux vs macOS
Efecto en el equipo
- Onboarding lento (“instala X, ahora Y, ahora arregla Z…”)
- Debugging de infraestructura en vez de producto
- Más fricción en QA/CI/CD
- Hotfixes y despliegues inciertos
Tiempo
Riesgo
Estrés
Solución
Entorno idéntico para todo el equipo
Definimos el entorno en código: Dockerfile para la app y docker compose
para orquestar servicios (DB, cache, colas, etc.).
Qué se “versiona”
- Runtime y dependencias del sistema
- Comandos de build y ejecución
- Servicios (Postgres/Redis/etc.) con volúmenes y puertos
- Variables base (no secretos) y archivos ejemplo
Resultado
- Onboarding: git clone + docker compose up
- Menos “snowflakes”: el entorno deja de ser artesanal
- CI y despliegue se parecen más a dev
Flujo
Cómo se organiza un proyecto en dev
Separar responsabilidades: imagen de app, servicios y datos.
Artefactos
- Dockerfile: cómo construir la imagen de tu app
- .dockerignore: qué NO copiar al build
- compose.yaml: servicios, redes, volúmenes
- .env.example: variables de entorno de referencia
Reglas de oro
- La app se reconstruye; los datos se persisten con volúmenes
- Los secretos NO van al repositorio
- Logs al stdout/stderr (no archivos locales)
- Paridad de versiones (dev ≈ staging ≈ prod)
Mantra
“Si no está declarado en Dockerfile/compose, no existe.”
Ejemplo
Dockerfile para una app (Node como referencia)
Ejemplo genérico (ajústalo a tu stack). La meta: builds reproducibles y rápidos.
Dockerfile (multi-stage)
# syntax=docker/dockerfile:1
FROM node:20-alpine AS deps
WORKDIR /app
COPY package*.json ./
RUN npm ci
FROM node:20-alpine AS dev
WORKDIR /app
ENV NODE_ENV=development
COPY --from=deps /app/node_modules ./node_modules
COPY . .
EXPOSE 3000
CMD ["npm", "run", "dev"]
Nota: en algunos stacks conviene separar build prod vs dev (hot reload).
Qué resuelve
- Versión de Node fija (no depende del host)
- Dependencias instaladas de forma consistente
- Comando estándar para arrancar el servicio
- Se puede usar igual en CI para pruebas
Ejemplo
docker compose: app + Postgres
Orquestación local: una red, un volumen de datos, y servicios con healthchecks.
compose.yaml
services:
app:
build:
context: .
target: dev
ports:
- "3000:3000"
environment:
DATABASE_URL: postgres://postgres:postgres@db:5432/app
depends_on:
db:
condition: service_healthy
volumes:
- .:/app
- /app/node_modules
db:
image: postgres:16-alpine
environment:
POSTGRES_PASSWORD: postgres
POSTGRES_DB: app
ports:
- "5432:5432"
volumes:
- pgdata:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres -d app"]
interval: 5s
timeout: 3s
retries: 20
volumes:
pgdata:
Detalles importantes
- depends_on + healthcheck: evita carreras al iniciar
- volumen pgdata: persiste datos entre reinicios
- volúmenes para código: permite hot reload
- URL usa hostname db (red interna)
Día a día
Comandos típicos en el equipo
La idea es estandarizar: todos usan lo mismo.
Cheatsheet
# levantar todo
docker compose up -d --build
# ver logs
docker compose logs -f --tail=200 app
# entrar al contenedor (debug)
docker compose exec app sh
# correr tests
docker compose exec app npm test
# parar y limpiar (sin borrar datos)
docker compose down
# borrar datos (cuidado)
docker compose down -v
Tema: velocidad en dev con Docker
- Cache de dependencias: copia primero archivos de lock (ej. package-lock.json) para que el build no reinstale todo en cada cambio de código.
- Volúmenes bien puestos: monta el código para hot reload, pero evita pisar dependencias del contenedor (ej. /app/node_modules).
- Rebuilds controlados: reconstruye solo cuando cambie el Dockerfile/deps; en cambios normales usa docker compose up + watcher.
Buenas prácticas
Lo que suele marcar la diferencia
Docker en dev funciona excelente si cuidamos consistencia, performance y seguridad.
Recomendado
- Versionar imágenes base (ej. Postgres 16)
- Incluir healthchecks y dependencias explícitas
- Usar .env.example y validar variables al iniciar
- Separar perfiles: dev vs test (compose profiles)
- Optimizar builds con cache (COPY package*.json antes del código)
Evitar
- “Montar todo” sin entender: volúmenes mal puestos rompen builds
- Hacer docker exec para tareas manuales no documentadas
- Usar “latest” en imágenes y romper el equipo sin querer
- Guardar secretos en el repo o en la imagen
- Ignorar performance (builds lentos = nadie lo usa)
Predictibilidad
Despliegues más predecibles
Si dev, CI y prod comparten el mismo empaquetado, disminuyen las sorpresas.
Cómo ayuda
- La imagen de la app se construye siempre igual
- Las dependencias del SO quedan “congeladas”
- CI puede correr tests dentro del contenedor
- Menos diferencias entre entornos (paridad)
Menos incidentes
Releases más rápidos
Puente a producción
- En prod quizá uses Kubernetes/ECS/Nomad, pero el artefacto puede ser el mismo: una imagen.
- Compose te da una “mini-orquestación” local, ideal para dev.
Checklist
Checklist para equipos
Si cumples esto, el “en mi máquina sí funciona” baja drásticamente.
Proyecto
- docker compose levanta todo con 1 comando
- Servicios con healthchecks
- Datos persistidos en volúmenes
- Variables documentadas en .env.example
- README con comandos del día a día
Equipo
- Versión mínima de Docker/Compose definida
- Convención de puertos (evitar colisiones)
- Política de secretos (vault, .env local, etc.)
- CI ejecuta tests dentro de contenedores
- Se revisan cambios a Dockerfile/compose como código crítico
Cierre
Resumen en 3 ideas
Si te llevas solo esto, ya valió la pena.
Las 3 ideas
- Entorno como código: lo declaras y se replica.
- Dev ≈ CI ≈ Prod: menos sorpresas al desplegar.
- Onboarding rápido: el equipo produce antes.
Consistencia
Velocidad
Calidad
Datos sobre el enfoque
- Reproducibilidad: el entorno queda definido en archivos versionados (Dockerfile/compose) y no en configuraciones personales.
- Onboarding: reduces variabilidad; la instalación se vuelve “levantar stack” en vez de “configurar máquina”.
- Paridad: al correr la app en contenedor, CI puede ejecutar tests en condiciones más cercanas a dev/prod.
- Predecibilidad: menos fallos por diferencias de versiones de runtime/servicios y dependencias del sistema operativo.
Atajo: presiona Home para volver a la portada