Microservicios con Docker: lo que aprendí armando mi primera arquitectura
Arrancé con un monolito .NET en un solo contenedor. Funcionaba bien, hasta que el equipo creció y todos tocábamos el mismo código. Desplegar un cambio en la pantalla de login requería redeployar toda la aplicación. Fue entonces cuando empecé a explorar microservicios con Docker.
Monolito vs microservicios: cuándo tiene sentido el cambio
| Aspecto | Monolito en Docker | Microservicios en Docker |
|---|---|---|
| Complejidad inicial | Baja | Alta |
| Deploy independiente | No — todo o nada | Sí — servicio por servicio |
| Escalabilidad selectiva | No | Sí — escalar solo lo que lo necesita |
| Fallo aislado | Un bug afecta todo | Un servicio caído no baja todo |
| Equipos independientes | Difícil | Cada equipo dueño de su servicio |
| Overhead operacional | Bajo | Alto — más servicios que monitorear |
Mi recomendación: empezá con el monolito. Cuando los puntos de dolor de la tabla de arriba se vuelvan reales en tu día a día, ahí es el momento de dividir.
Mi primera arquitectura de microservicios: auth + api + frontend
# docker-compose.yml — tres servicios independientes
version: '3.8'
services:
# Servicio de autenticación (JWT, usuarios)
auth-service:
build: ./services/auth
environment:
- DB_CONNECTION=Host=postgres;Database=auth;Username=auth;Password=${AUTH_DB_PASS}
- JWT_SECRET=${JWT_SECRET}
- JWT_EXPIRY=1h
depends_on:
postgres:
condition: service_healthy
restart: unless-stopped
# Sin puerto expuesto - solo accesible internamente
# API principal de negocio
api-service:
build: ./services/api
environment:
- DB_CONNECTION=Host=postgres;Database=apidb;Username=api;Password=${API_DB_PASS}
- AUTH_SERVICE_URL=http://auth-service:8080
- CACHE_URL=redis:6379
depends_on:
- auth-service
- redis
restart: unless-stopped
# Frontend React
frontend:
build: ./services/frontend
environment:
- REACT_APP_API_URL=http://api-service:8080
restart: unless-stopped
# Proxy - único punto de entrada externo
nginx:
image: nginx:1.25-alpine
ports:
- "80:80"
volumes:
- ./nginx/microservices.conf:/etc/nginx/nginx.conf:ro
depends_on:
- frontend
- api-service
# Infraestructura compartida
postgres:
image: postgres:16-alpine
environment:
POSTGRES_MULTIPLE_DATABASES: auth,apidb # extensión para múltiples DBs
POSTGRES_PASSWORD: ${POSTGRES_ROOT_PASS}
volumes:
- postgres-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready"]
interval: 10s
retries: 5
redis:
image: redis:7-alpine
volumes:
- redis-data:/data
volumes:
postgres-data:
redis-data:
Comunicación entre servicios
Dentro de la red Docker, los servicios se llaman por nombre. La API valida tokens llamando al servicio de auth en cada request:
# En el código de api-service (.NET):
// Validar token contra auth-service
var authResponse = await _httpClient.GetAsync(
$"{_authServiceUrl}/validate?token={token}"
);
// En el docker-compose, AUTH_SERVICE_URL = http://auth-service:8080
// Docker resuelve "auth-service" al contenedor correcto automáticamente
Deploy independiente: la ventaja real
# Actualizar solo el servicio de auth sin tocar nada más
docker compose up -d --build auth-service
# Escalar solo la API (recibe más carga)
docker compose up -d --scale api-service=3
# Rollback solo del frontend
docker compose stop frontend
docker compose rm -f frontend
TAG=anterior docker compose up -d frontend
Lo que aprendí en el proceso
La transición de monolito a microservicios no es solo técnica: es organizacional. Cada servicio necesita su propio repositorio (o al menos su propia carpeta), su propio pipeline de CI/CD y su propio dueño. La complejidad operacional sube. Por eso Docker Compose no es suficiente para microservicios en producción a escala — ese es el camino hacia Kubernetes, que vemos en el próximo artículo.
← Artículo anterior: Docker en CI/CD | Serie Docker Completo | Próximo: Seguridad en Docker →
← Artículo anterior: Docker en mi pipeline de CI/CD: builds reproducibles sin sorpresas | Serie Docker Completo | Próximo: Seguridad en Docker: errores que cometí y cómo los corregí →
