Guía de Despliegue en Producción¶
IMPORTANTE: Esta guía es un punto de partida. Consulte con especialistas en DevOps y seguridad de su institución antes de desplegar en producción.
Tabla de contenidos¶
- Requisitos previos
- Configuración de infraestructura
- Secretos y variables de entorno
- Despliegue con Docker Compose
- Clustering y escalabilidad
- Respaldos y recuperación
- Monitoreo y logs
- Seguridad
- Troubleshooting
Requisitos previos¶
Hardware mínimo¶
Para una institución pequeña a mediana (< 5000 usuarios):
- CPU: 4 cores (x86-64 o ARM64)
- RAM: 16 GB
- Almacenamiento: 500 GB SSD para PostgreSQL + 1-5 TB para MinIO (según volumen de documentos)
- Ancho de banda: 100 Mbps
Software¶
- Docker 24.0+
- Docker Compose 2.20+
- Linux (RHEL, Ubuntu, Debian) o Kubernetes
- Certificado TLS válido (Let's Encrypt gratuito o CA corporativa)
- Dominio DNS resuelto
Conocimientos¶
- Administración de Linux/Docker
- Seguridad de redes (firewall, VPN)
- Bases de datos PostgreSQL
- Backup y recovery
Configuración de infraestructura¶
1. Preparar el servidor¶
# Actualizar sistema operativo
sudo apt update && sudo apt upgrade -y
# Instalar Docker
curl -fsSL https://get.docker.com | sh
sudo usermod -aG docker $USER
# Instalar Docker Compose
sudo curl -L "https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose
# Crear directorio de datos (fuera del árbol del código)
sudo mkdir -p /srv/orpyca/{postgres,minio,redis,certs}
sudo chown $USER:$USER /srv/orpyca -R
2. Obtener certificado TLS¶
Con Let's Encrypt (gratuito, requiere puerto 80 accesible):
sudo apt install certbot
sudo certbot certonly --standalone -d keycloak.yourdomain.com
# Certificados en /etc/letsencrypt/live/keycloak.yourdomain.com/
# Copiar a la carpeta del proyecto (renovación automática)
sudo cp /etc/letsencrypt/live/keycloak.yourdomain.com/fullchain.pem /srv/orpyca/certs/tls.crt
sudo cp /etc/letsencrypt/live/keycloak.yourdomain.com/privkey.pem /srv/orpyca/certs/tls.key
sudo chown $USER:$USER /srv/orpyca/certs/tls.*
Con CA corporativa: proporcione tls.crt y tls.key en la carpeta de certs.
3. Configurar firewall¶
# Asumir UFW en Ubuntu
sudo ufw default deny incoming
sudo ufw default allow outgoing
# Permitir SSH (CRÍTICO: no bloquee tu acceso)
sudo ufw allow ssh
# Permitir Nginx (proxy reverso)
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
# Denegar acceso directo a servicios internos
sudo ufw deny 5432 # PostgreSQL
sudo ufw deny 6379 # Redis
sudo ufw deny 8080 # API Gateway sin proxy
sudo ufw deny 9000 # MinIO
sudo ufw enable
4. Instalar Nginx como proxy reverso (recomendado)¶
sudo apt install nginx
# Ver plantilla en infra/nginx.conf.example
sudo cp infra/nginx.conf.example /etc/nginx/sites-available/orpyca
sudo ln -s /etc/nginx/sites-available/orpyca /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl restart nginx
Secretos y variables de entorno¶
¡CRÍTICO! Manejo seguro de secretos¶
NUNCA commitee .env a Git. Cada entorno debe tener secretos diferentes.
# 1. Crear archivo .env en el servidor (local, no trackeado)
cd /srv/orpyca
cp infra/.env.example .env
# 2. Generar contraseñas fuertes
openssl rand -base64 32 # PostgreSQL password
openssl rand -base64 32 # Redis password
openssl rand -base64 32 # MinIO password
openssl rand -base64 32 # Keycloak admin password
# 3. Editar .env con valores reales
nano .env
Variables requeridas¶
| Variable | Descripción | Ejemplo |
|---|---|---|
DB_USER |
Usuario PostgreSQL | orpycamcp_prod |
DB_PASSWORD |
Contraseña PostgreSQL (32+ caracteres) | <openssl rand -base64 32> |
REDIS_PASSWORD |
Contraseña Redis (32+ caracteres) | <openssl rand -base64 32> |
MINIO_ROOT_PASSWORD |
Contraseña MinIO | <openssl rand -base64 32> |
KEYCLOAK_HOSTNAME |
Dominio FQDN de Keycloak | keycloak.yourdomain.com |
KEYCLOAK_ADMIN_PASSWORD |
Contraseña admin Keycloak | <secure password> |
KEYCLOAK_*_SECRET |
Secrets de clientes OAuth2 | <32 char hex> |
FRONTEND_HOSTNAME |
FQDN público del frontend SSR (define ORIGIN y el redirect_uri del login) |
app.yourdomain.com |
API_PUBLIC_HOSTNAME |
FQDN público del api-gateway que ve el navegador | api.yourdomain.com |
SIGNATURE_SECRET |
Clave HMAC del sellado de firma (E06). Aleatoria, rotar | <openssl rand -base64 32> |
KNOWLEDGE_INTERNAL_TOKEN |
Token de ingesta interna (E21). Vacío ⇒ ingesta fail-closed | <openssl rand -base64 32> |
SMTP_HOST |
Host SMTP | smtp.gmail.com |
SMTP_PASSWORD |
Contraseña o token de aplicación SMTP | app-specific-password |
Contrato de issuer (iss) — crítico¶
El iss de los tokens es siempre la URL pública por la que el navegador hace
login. Cualquier servicio que valide JWT (auth-service, signature-service) debe
esperar exactamente ese iss o rechazará todos los tokens (fail-closed).
- Producción (single-host): Keycloak corre con
--hostname-strict=true --hostname=${KEYCLOAK_HOSTNAME}, por lo que navegador y servicios usan el mismo host.expected_issuerse deriva deKEYCLOAK_URLy no hace falta override. - Desarrollo (
docker-compose.yml): hay split (navegadorlocalhost:19180↔ red internakeycloak:8080); por eso allí se fijaKEYCLOAK_ISSUERcon la URL pública del realm.
Al arrancar, auth-service y signature-service registran en el log el iss
esperado y emiten un WARNING fail-fast si KEYCLOAK_URL apunta a un host
interno de Docker sin KEYCLOAK_ISSUER definido (configuración que rechazaría todo
token). Verifica este log tras el despliegue:
Frontend SSR¶
El frontend (SvelteKit + adapter-node) corre en su propio contenedor detrás del
proxy reverso. Necesita resolver correctamente el origin para construir el
redirect_uri del login PKCE:
ORIGIN=https://${FRONTEND_HOSTNAME}(oPROTOCOL_HEADER=x-forwarded-proto+HOST_HEADER=x-forwarded-hostsi el proxy los inyecta).- URLs públicas (navegador):
PUBLIC_API_URL,PUBLIC_KEYCLOAK_URL. - URLs internas (SSR → red Docker):
INTERNAL_API_URL,INTERNAL_KEYCLOAK_URL.
El redirect_uri resultante (https://${FRONTEND_HOSTNAME}/login/callback) debe
estar en redirectUris/webOrigins del cliente orpycamcp-frontend en Keycloak.
Rotación de secretos¶
Cada 90 días (o según política corporativa):
# 1. Generar nueva contraseña
NEW_PASSWORD=$(openssl rand -base64 32)
# 2. Actualizar en PostgreSQL
docker compose exec postgres psql -U orpycamcp -c "ALTER USER orpycamcp PASSWORD '$NEW_PASSWORD';"
# 3. Actualizar .env y reiniciar servicios
sed -i "s/DB_PASSWORD=.*/DB_PASSWORD=$NEW_PASSWORD/" .env
docker compose restart document-service tenant-service auth-service
Despliegue con Docker Compose¶
Usando docker-compose.prod.yml¶
# 1. Clonar repositorio
git clone https://gitlab.com/orpyca/orpyca-mcp.git
cd orfeoMcp
# 2. Crear estructura de directorios
mkdir -p infra/certs infra/logs
# 3. Configurar variables de entorno
cp infra/.env.example infra/.env
nano infra/.env # Editar con valores reales
# 4. Levantar stack
cd infra
docker compose -f docker-compose.prod.yml up -d
# 5. Verificar estado
docker compose ps
docker compose logs -f api-gateway
# 6. Crear primer tenant (después de que Keycloak esté listo)
docker compose exec document-service /scripts/init-tenant.sh \
--name "Mi Institución" \
--slug "mi-institucion" \
--code "MIST"
Verificación post-despliegue¶
# Revisar logs de cada servicio
docker compose logs -f postgres # Errores de conexión
docker compose logs -f keycloak # OIDC/realm issues
docker compose logs -f api-gateway # Errores de ruteo
# Probar health check (a través de Nginx si está configurado)
curl -k https://keycloak.yourdomain.com/health
# Verificar que MinIO está funcionando
docker compose exec minio mc ls minio
# Verificar que Redis está funcionando
docker compose exec redis redis-cli -a $REDIS_PASSWORD ping
Clustering y escalabilidad¶
Para instituciones grandes (> 10,000 usuarios):
Opción 1: Docker Swarm (simple)¶
# Inicializar cluster
docker swarm init
docker swarm join --token <TOKEN> manager-node-ip
# Desplegar stack
docker stack deploy -c docker-compose.prod.yml orpyca-mcp
Opción 2: Kubernetes (recomendado)¶
Convertir a Helm charts:
# Convertir docker-compose a Kubernetes
docker-compose -f docker-compose.prod.yml config | docker-to-k8s > orpycamcp-k8s.yaml
# Desplegar en cluster K8s
kubectl apply -f orpycamcp-k8s.yaml
Escalabilidad de servicios¶
Servicios sin estado (stateless) pueden escalarse horizontalmente:
# En docker-compose.prod.yml
api-gateway:
deploy:
replicas: 3 # 3 instancias balanceadas por carga
storage-service:
deploy:
replicas: 2 # Para throughput de uploads
document-service:
deploy:
replicas: 2 # Para búsquedas de radicados
Respaldos y recuperación¶
Respaldo automático de PostgreSQL¶
#!/bin/bash
# infra/backup.sh
DATE=$(date +%Y%m%d_%H%M%S)
BACKUP_DIR="/srv/orpyca/backups"
mkdir -p $BACKUP_DIR
# Respaldar base de datos
docker compose exec -T postgres pg_dump -U orpycamcp orpycamcp_db | \
gzip > $BACKUP_DIR/orpycamcp_$DATE.sql.gz
# Respaldar datos MinIO
docker compose exec -T minio mc mirror \
minio/ /backup/minio-$DATE/
# Limpiar respaldos antiguos (> 30 días)
find $BACKUP_DIR -name "*.gz" -mtime +30 -delete
echo "Backup completado: $BACKUP_DIR/orpycamcp_$DATE.sql.gz"
Programar con cron:
Recuperación desde respaldo¶
# 1. Detener servicios
docker compose down
# 2. Restaurar PostgreSQL
gunzip < /srv/orpyca/backups/orpycamcp_20260601_020000.sql.gz | \
docker compose exec -T postgres psql -U orpycamcp orpycamcp_db
# 3. Restaurar MinIO
docker compose exec -T minio mc mirror \
/backup/minio-20260601/ minio/
# 4. Reiniciar servicios
docker compose up -d
Monitoreo y logs¶
Centralizando logs con ELK Stack¶
# docker-compose.prod.yml — agregar servicios ELK
elasticsearch:
image: docker.elastic.co/elasticsearch/elasticsearch:8.0.0
environment:
- discovery.type=single-node
volumes:
- elasticsearch-data:/usr/share/elasticsearch/data
logstash:
image: docker.elastic.co/logstash/logstash:8.0.0
volumes:
- ./logstash.conf:/usr/share/logstash/pipeline/logstash.conf:ro
kibana:
image: docker.elastic.co/kibana/kibana:8.0.0
ports:
- "5601:5601" # Acceso a través de Nginx
Métricas con Prometheus¶
prometheus:
image: prom/prometheus:latest
volumes:
- ./infra/prometheus.yml:/etc/prometheus/prometheus.yml:ro
- prometheus-data:/prometheus
grafana:
image: grafana/grafana:latest
ports:
- "3000:3000" # A través de Nginx
Alertas¶
Configurar alertas para:
- CPU/memoria > 80%
- Espacio en disco < 20%
- PostgreSQL retrasos de replicación
- SMTP delivery failures
- Tasa de errores HTTP > 5%
Seguridad¶
Checklist de seguridad previa al despliegue¶
- [ ] Todos los servicios tienen TLS habilitado
- [ ] PostgreSQL no es accesible externamente
- [ ] MinIO requiere autenticación (sin acceso público)
- [ ] Redis requiere contraseña
- [ ] Keycloak usa HTTPS con certificado válido
- [ ] Firewall bloquea puertos internos (5432, 6379, 9000)
- [ ] CORS configurado restringidamente
- [ ] Audit logging habilitado en PostgreSQL y Keycloak
- [ ] Backup diario con pruebas de recuperación
- [ ] Rate limiting en api-gateway activado
- [ ] Headers de seguridad (HSTS, X-Content-Type-Options, etc.)
Headers de seguridad en Nginx¶
# infra/nginx.conf
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
Auditoría de seguridad¶
# Escanear imágenes Docker en busca de vulnerabilidades
docker scan api-gateway:latest
docker scan postgres:15-alpine
# Usar Trivy para análisis de vulnerabilidades
trivy image api-gateway:latest
Troubleshooting¶
Los servicios no inician¶
# 1. Verificar logs
docker compose logs postgres
docker compose logs keycloak
# 2. Comprobar variables de entorno
docker compose config | grep -A 5 "environment"
# 3. Verificar conectividad de red
docker network ls
docker network inspect orpycamcp-net
PostgreSQL rechaza conexiones¶
# Verificar autenticación
docker compose exec postgres psql -U orpycamcp -d orpycamcp_db -c "SELECT version();"
# Resetear contraseña si es necesario
docker compose exec postgres psql -U postgres -c \
"ALTER USER orpycamcp PASSWORD 'new-password';"
MinIO no se sincroniza con buckets¶
# Verificar estado de MinIO
docker compose exec minio mc status minio
# Listar buckets
docker compose exec minio mc ls minio/
# Recrear bucket si está corrupto
docker compose exec minio mc rb minio/orpycamcp-tenant-documents
docker compose exec minio mc mb minio/orpycamcp-tenant-documents
Alto uso de memoria¶
# Reducir buffer_pool_size en PostgreSQL
docker compose down
# Editar docker-compose.prod.yml
sed -i 's/-c shared_buffers=256MB/-c shared_buffers=128MB/' docker-compose.prod.yml
docker compose up -d postgres
Siguientes pasos¶
- Integrar con tu PKI corporativa — usar certificados firmados internamente
- Implementar SSO corporativo — federación SAML/OIDC con Entra ID, Okta, etc.
- Configurar respaldo en nube — AWS S3, GCS, o Azure Blob Storage
- Implementar recuperación ante desastres — replicación a un segundo sitio
- Entrenar al equipo — cómo operacionalizar OrpycaMCP
Preguntas? Contacte a: aurigadl@gmail.com