📖 GUNEI ERP - RUNBOOK COMPLETO
Deployment & Operations Guide - Enterprise ArchitectureVersión 2.1 - Enero 2026
📑 Tabla de Contenidos
- Overview del Sistema
- Infraestructura
- Componentes Individuales
- CI/CD Pipeline
- Operaciones Diarias
- Gestión de Ambientes
- Monitoreo y Alertas
- Troubleshooting
- Apéndices
1. Overview del Sistema
1.1 Arquitectura General
┌────────────────────────────────────────────────────────────┐
│ Internet │
└──────────────────────┬─────────────────────────────────────┘
│
▼
┌────────────────────────────────────────────────────────────┐
│ Caddy Shared (Reverse Proxy + SSL) │
│ Ports: 80 (HTTP), 443 (HTTPS) │
└──────────┬────────────────────┬────────────────────────────┘
│ │
▼ ▼
┌─────────────────┐ ┌─────────────────┐
│ gunei.xyz │ │ api.gunei.xyz │
│ [STAGING] │ │ [STAGING] │
│ Frontend:3001 │ │ Backend:3000 │
└─────────────────┘ └────────┬────────┘
│
┌─────────────────────┴────────────┐
│ │
▼ ▼
┌─────────────────┐ ┌─────────────────┐
│ erp-dev DB │ │ erp DB │
│ [STAGING] │ │ [PRODUCTION] │
└─────────────────┘ └─────────────────┘
│ │
└──────────────┬───────────────────┘
│
┌───────▼────────┐
│ cloud-sql-proxy │
│ (Port 5450) │
└───────┬─────────┘
│
▼
┌───────────────────────┐
│ GCP Cloud SQL │
│ banco-gal:southamerica│
│ -east1:gunei-erp │
└───────────────────────┘
VPS containers conectados vía: gunei-network
Cloud SQL Proxy conecta a GCP vía credentials.json
1.2 Arquitectura de Ambientes
El sistema ahora implementa una arquitectura enterprise por ambientes:
/opt/
├── infrastructure/ # Servicios compartidos
│ ├── caddy/ # Reverse proxy compartido
│ ├── cloud-sql-proxy/ # Conexión a GCP Cloud SQL
│ └── monitoring/ # Prometheus/Grafana (futuro)
│
└── apps/
└── gunei-erp/
├── backend/
│ ├── staging/ # Ambiente de desarrollo
│ └── production/ # Ambiente productivo
└── frontend/
├── staging/ # Ambiente de desarrollo
└── production/ # Ambiente productivo
Ventajas de esta arquitectura:
- ✅ Separación clara entre staging y production
- ✅ Base de datos en GCP Cloud SQL (backups automáticos, alta disponibilidad)
- ✅ Un solo Caddy para todo el routing (simplicidad)
- ✅ Escalabilidad: fácil agregar nuevos ambientes
- ✅ Mejor organización y mantenimiento
1.3 Stack Tecnológico
| Componente | Tecnología | Versión | Puertos |
|---|---|---|---|
| Runtime | Bun | Latest | - |
| Backend Framework | Hono | Latest | 3000 (staging), 3100 (prod) |
| Frontend Framework | SvelteKit | 5.x | 3001 (staging), 3101 (prod) |
| Database | GCP Cloud SQL (PostgreSQL) | 17 | 5450 (via proxy) |
| DB Proxy | Cloud SQL Proxy | 2.15.0 | 5450 |
| Reverse Proxy | Caddy | Latest | 80/443 |
| Container Runtime | Docker | 28.x | - |
| Orchestration | Docker Compose | 2.x | - |
1.4 Ambientes y URLs
Staging (Ambiente actual operativo)
- Frontend: https://gunei.xyz →
gunei-frontend-staging:3001 - Backend: https://api.gunei.xyz →
gunei-backend-staging:3000 - Database:
erp-deven GCP Cloud SQL (viacloud-sql-proxy:5450)
Production (Preparado, no deployado aún)
- Frontend: (Pendiente configurar subdominio) →
gunei-frontend-production:3101 - Backend: (Pendiente configurar subdominio) →
gunei-backend-production:3100 - Database:
erpen GCP Cloud SQL (viacloud-sql-proxy:5450)
1.5 Health Endpoints
| Ambiente | Servicio | URL | Endpoint |
|---|---|---|---|
| Staging | Frontend | https://gunei.xyz/health | /health |
| Staging | Backend | https://api.gunei.xyz/status | /status |
| Production | Frontend | (Pendiente) | /health |
| Production | Backend | (Pendiente) | /status |
1.6 Flujo de Requests (Staging)
Usuario → gunei.xyz:443 (HTTPS)
↓
Caddy Shared (SSL termination + routing)
↓
gunei-frontend-staging:3001 (SvelteKit)
↓
api.gunei.xyz:443 (API calls)
↓
Caddy Shared (CORS + routing)
↓
gunei-backend-staging:3000 (Hono)
↓
cloud-sql-proxy:5450 → GCP Cloud SQL → erp-dev (PostgreSQL)
2. Infraestructura
2.1 VPS Contabo
Especificaciones:
- Provider: Contabo
- IP: 154.53.36.180
- OS: Ubuntu 24.04 LTS
- Hostname: vmi2991275
- Acceso: SSH con ED25519 key
Conexión SSH:
ssh root@gunei.xyz
# o
ssh root@154.53.36.180
2.2 Estructura de Directorios Actualizada
/opt/
├── infrastructure/
│ ├── caddy/
│ │ ├── Caddyfile # Configuración del reverse proxy
│ │ ├── docker-compose.yml # Compose de Caddy
│ │ └── caddy_data/ # SSL certificates y data
│ │
│ ├── cloud-sql-proxy/
│ │ ├── docker-compose.yml # Compose de Cloud SQL Proxy
│ │ └── credentials.json # GCP service account credentials
│ │
│ └── monitoring/ # Preparado para Grafana/Prometheus
│ └── docker-compose.yml
│
└── apps/
└── gunei-erp/
├── backend/
│ ├── staging/
│ │ ├── docker-compose.yml
│ │ └── .env # Variables de entorno staging
│ └── production/
│ ├── docker-compose.yml
│ └── .env # Variables de entorno production
│
└── frontend/
├── staging/
│ └── docker-compose.yml
└── production/
└── docker-compose.yml
/root/
├── scripts/
│ ├── monitor-logs.sh # Script de monitoreo
│ ├── health-check.sh # Health checks multi-ambiente
│ └── alert-check.sh # Sistema de alertas
│
└── legacy/ # Archivos viejos (preservados por backup)
└── gunei-erp-back/
2.3 Docker Networking
Red Principal: gunei-network
Todos los servicios comparten esta red para comunicación interna:
# Ver la red
docker network inspect gunei-network
# Servicios en la red:
# - caddy-shared (reverse proxy)
# - cloud-sql-proxy (conexión a GCP Cloud SQL)
# - gunei-frontend-staging (frontend staging)
# - gunei-backend-staging (backend staging)
# - gunei-frontend-production (frontend production - futuro)
# - gunei-backend-production (backend production - futuro)
Ventajas:
- Comunicación entre contenedores por nombre
- Aislamiento de red del host
- DNS interno automático
- Único punto de configuración
Migración de red:
- Red anterior:
red-npm(deprecada) - Red actual:
gunei-network(enterprise)
2.4 DNS Configuration
Registros en Porkbun:
| Tipo | Host | Apunta a | TTL |
|---|---|---|---|
| A | @ (gunei.xyz) | 154.53.36.180 | 600 |
| A | api | 154.53.36.180 | 600 |
| A | prod (futuro) | 154.53.36.180 | 600 |
| A | api-prod (futuro) | 154.53.36.180 | 600 |
Verificación:
dig gunei.xyz +short
dig api.gunei.xyz +short
# Ambos deben devolver: 154.53.36.180
3. Componentes Individuales
3.1 Caddy Shared (Reverse Proxy + SSL)
3.1.1 Descripción
Caddy es el punto de entrada único para todo el tráfico HTTP/HTTPS de todos los ambientes. Se encarga de:
- SSL automático con Let's Encrypt
- Reverse proxy a múltiples ambientes
- CORS headers por ambiente
- Compresión gzip/zstd
- Logs separados por dominio/ambiente
- Routing inteligente
3.1.2 Ubicación y Archivos
# Directorio principal
cd /opt/infrastructure/caddy
# Archivos clave
/opt/infrastructure/caddy/Caddyfile # Configuración principal
/opt/infrastructure/caddy/docker-compose.yml # Definición del servicio
/opt/infrastructure/caddy/caddy_data/ # Certificados SSL
3.1.3 Caddyfile Actualizado
{
email miguel.culaciati@gunei.com.ar # Email para Let's Encrypt
log {
output stdout
format console
}
}
# Snippet reutilizable para headers de seguridad
(security_headers) {
encode zstd gzip
header -Via
header -Server
header Server "Gunei Platform"
header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
header X-Content-Type-Options "nosniff"
header X-Frame-Options "SAMEORIGIN"
header Referrer-Policy "strict-origin-when-cross-origin"
}
# Snippet reutilizable para CORS
(cors_headers) {
header {
Access-Control-Allow-Methods "GET, POST, PUT, DELETE, PATCH, OPTIONS"
Access-Control-Allow-Headers "Content-Type, Authorization, X-Requested-With"
Access-Control-Allow-Credentials "true"
}
@options {
method OPTIONS
}
respond @options 204
}
# ============================================
# STAGING ENVIRONMENT
# ============================================
# Frontend Staging
gunei.xyz {
import security_headers
reverse_proxy gunei-frontend-staging:3001
log {
output file /var/log/caddy/staging-frontend.log
}
}
# Backend Staging
api.gunei.xyz {
import security_headers
import cors_headers
header Access-Control-Allow-Origin "https://gunei.xyz"
reverse_proxy gunei-backend-staging:3000
log {
output file /var/log/caddy/staging-backend.log
}
}
# ============================================
# PRODUCTION ENVIRONMENT (Preparado)
# ============================================
# Frontend Production (cuando se configure)
# prod.gunei.xyz {
# import security_headers
# reverse_proxy gunei-frontend-production:3101
# log {
# output file /var/log/caddy/production-frontend.log
# }
# }
# Backend Production (cuando se configure)
# api-prod.gunei.xyz {
# import security_headers
# import cors_headers
#
# header Access-Control-Allow-Origin "https://prod.gunei.xyz"
#
# reverse_proxy gunei-backend-production:3100
#
# log {
# output file /var/log/caddy/production-backend.log
# }
# }
3.1.4 docker-compose.yml
services:
caddy:
image: caddy:latest
container_name: caddy-shared
restart: unless-stopped
cap_add:
- NET_ADMIN
ports:
- "80:80" # HTTP
- "443:443/tcp" # HTTPS
- "443:443/udp" # HTTP/3
environment:
- TZ=America/Argentina/Buenos_Aires
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- ./caddy_data:/data
networks:
- gunei-network
networks:
gunei-network:
external: true
3.1.5 Comandos Comunes
# Ver logs de Caddy
docker logs caddy-shared -f
# Recargar configuración (sin downtime)
docker exec caddy-shared caddy reload --config /etc/caddy/Caddyfile
# Reiniciar Caddy
docker restart caddy-shared
# Ver certificados SSL
docker exec caddy-shared ls -la /data/caddy/certificates/
# Verificar configuración
docker exec caddy-shared caddy validate --config /etc/caddy/Caddyfile
# Ver logs de acceso por ambiente
docker exec caddy-shared cat /var/log/caddy/staging-frontend.log
docker exec caddy-shared cat /var/log/caddy/staging-backend.log
# Tail logs en tiempo real
docker exec caddy-shared tail -f /var/log/caddy/staging-backend.log
3.1.6 SSL Automático
Caddy obtiene y renueva certificados SSL automáticamente:
- Primera vez: Al iniciar, solicita certificados a Let's Encrypt
- Renovación: Automática ~30 días antes del vencimiento
- Almacenamiento: En
/data/caddy/certificates/
Verificar SSL:
# Ver info del certificado
curl -vI https://gunei.xyz 2>&1 | grep -i 'expire\|issuer'
# Ver desde el VPS
docker exec caddy-shared ls -la /data/caddy/certificates/acme-v02.api.letsencrypt.org-directory/gunei.xyz/
3.1.7 Troubleshooting Caddy
Problema: SSL no se genera
# Ver logs detallados
docker logs caddy-shared --tail 100 | grep -i "acme\|tls"
# Forzar renovación
docker restart caddy-shared
# Verificar que el puerto 443 está abierto
netstat -tlnp | grep :443
Problema: "502 Bad Gateway"
# Verificar que el backend/frontend está corriendo
docker ps | grep "gunei-"
# Probar conexión interna
docker exec caddy-shared wget -O- http://gunei-frontend-staging:3001/health
docker exec caddy-shared wget -O- http://gunei-backend-staging:3000/status
Problema: Cambios en Caddyfile no se aplican
# El volumen está montado como read-only
# Editar en el HOST, luego reload
nano /opt/infrastructure/caddy/Caddyfile
docker exec caddy-shared caddy reload --config /etc/caddy/Caddyfile
3.2 Cloud SQL Proxy (Conexión a GCP)
3.2.1 Descripción
Cloud SQL Proxy conecta los containers del VPS a la base de datos PostgreSQL en GCP Cloud SQL:
- Seguridad: Conexión encriptada vía IAM, no expone puertos públicos
- Backups: GCP maneja backups automáticos
- Alta disponibilidad: Failover automático gestionado por GCP
- Databases:
erp-dev(staging),erp(production)
3.2.2 Ubicación y Archivos
# Directorio principal
cd /opt/infrastructure/cloud-sql-proxy
# Archivos clave
/opt/infrastructure/cloud-sql-proxy/docker-compose.yml # Definición del servicio
/opt/infrastructure/cloud-sql-proxy/credentials.json # GCP service account key
3.2.3 Instancia GCP
Proyecto GCP: banco-gal
Región: southamerica-east1
Instancia: gunei-erp
Connection name: banco-gal:southamerica-east1:gunei-erp
Databases:
- erp-dev → Staging
- erp → Production
Usuario: dba_dev
Puerto proxy: 5450
3.2.4 docker-compose.yml
services:
cloud-sql-proxy:
image: gcr.io/cloud-sql-connectors/cloud-sql-proxy:2.15.0
container_name: cloud-sql-proxy
restart: unless-stopped
command: banco-gal:southamerica-east1:gunei-erp --address 0.0.0.0 --port 5450 --credentials-file /credentials.json
volumes:
- ./credentials.json:/credentials.json:ro
networks:
- gunei-network
networks:
gunei-network:
external: true
Nota sobre el puerto:
- Puerto: 5450 (expuesto a gunei-network)
- Los containers acceden via:
cloud-sql-proxy:5450
3.2.5 Comandos Comunes
# Ver logs del proxy
docker logs cloud-sql-proxy -f
# Ver últimas líneas
docker logs cloud-sql-proxy --tail 50
# Verificar que el proceso está corriendo
docker exec cloud-sql-proxy ps aux | grep cloud-sql-proxy
# Restart del proxy
docker restart cloud-sql-proxy
# Ver estado del container
docker ps | grep cloud-sql-proxy
# Test de conexión desde backend staging
docker exec gunei-backend-staging psql -h cloud-sql-proxy -p 5450 -U dba_dev -d erp-dev -c "SELECT 1;"
# Test de conexión desde backend production
docker exec gunei-backend-production psql -h cloud-sql-proxy -p 5450 -U dba_dev -d erp -c "SELECT 1;"
# Listar databases
docker exec gunei-backend-staging psql -h cloud-sql-proxy -p 5450 -U dba_dev -d erp-dev -c "\l"
# Ver tablas de staging
docker exec gunei-backend-staging psql -h cloud-sql-proxy -p 5450 -U dba_dev -d erp-dev -c "\dt"
3.2.6 Backups
Gestionados por GCP Cloud SQL:
- Backups automáticos diarios
- Retención configurable en GCP Console
- Point-in-time recovery disponible
Backup manual (export):
# Desde GCP Console o gcloud CLI
gcloud sql export sql gunei-erp gs://bucket-name/backup.sql --database=erp-dev
# Dump local via proxy (para archivos locales)
docker exec gunei-backend-staging pg_dump -h cloud-sql-proxy -p 5450 -U dba_dev erp-dev > backup_staging_$(date +%Y%m%d).sql
3.2.7 Migrations
Workflow por ambiente:
# Para staging (desde máquina local o CI)
# Conectar via Cloud SQL Proxy o Auth Proxy local
export DATABASE_URL="postgresql://dba_dev:PASSWORD@cloud-sql-proxy:5450/erp-dev"
dbmate up
# Para production
export DATABASE_URL="postgresql://dba_dev:PASSWORD@cloud-sql-proxy:5450/erp"
dbmate up
3.2.8 Troubleshooting Cloud SQL Proxy
Problema: "Connection refused" al proxy
# Verificar que el proxy está corriendo
docker ps | grep cloud-sql-proxy
# Ver logs del proxy
docker logs cloud-sql-proxy --tail 50
# Verificar que está escuchando
docker exec cloud-sql-proxy netstat -tlnp | grep 5450
Problema: "Authentication failed" o "Permission denied"
# Verificar que credentials.json existe y es válido
ls -la /opt/infrastructure/cloud-sql-proxy/credentials.json
# Verificar logs por errores de IAM
docker logs cloud-sql-proxy | grep -i "error\|permission\|denied"
# Verificar que el service account tiene los roles correctos en GCP:
# - Cloud SQL Client
# - Cloud SQL Instance User
Problema: "Unable to connect to Cloud SQL instance"
# Verificar conexión a internet desde el proxy
docker exec cloud-sql-proxy ping -c 3 google.com
# Verificar el connection string
docker logs cloud-sql-proxy | head -20
# Verificar que la instancia existe y está running en GCP Console
# https://console.cloud.google.com/sql/instances
Problema: Proxy se reinicia constantemente
# Ver logs completos
docker logs cloud-sql-proxy
# Causas comunes:
# - credentials.json inválido o expirado
# - Instancia Cloud SQL no existe
# - Service account sin permisos
# - Errores de red/firewall
# Recrear el container
cd /opt/infrastructure/cloud-sql-proxy
docker compose down
docker compose up -d
3.3 Backend (Bun + Hono)
3.3.1 Descripción
API REST construida con:
- Runtime: Bun (JavaScript/TypeScript runtime ultra-rápido)
- Framework: Hono (framework web minimalista)
- Features: AFIP integration, facturación electrónica
- Ambientes: Staging (operativo) y Production (preparado)
3.3.2 Backend Staging
Ubicación:
cd /opt/apps/gunei-erp/backend/staging
docker-compose.yml:
services:
backend:
image: ghcr.io/gunei-dev/gunei-erp-back:staging
pull_policy: always
container_name: gunei-backend-staging
ports:
- "3000:3000"
networks:
- gunei-network
env_file:
- .env
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
tag: "{{.Name}}/{{.ID}}"
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/status"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
networks:
gunei-network:
external: true
Variables de entorno (.env):
# Database - GCP Cloud SQL via Proxy
DATABASE_URL=postgresql://dba_dev:PASSWORD@cloud-sql-proxy:5450/erp-dev
# Application
NODE_ENV=staging
PORT=3000
# AFIP (ejemplos)
AFIP_KEY=...
AFIP_SECRET=...
# JWT
JWT_SECRET=...
3.3.3 Backend Production (Preparado)
Ubicación:
cd /opt/apps/gunei-erp/backend/production
docker-compose.yml:
services:
backend:
image: ghcr.io/gunei-dev/gunei-erp-back:production
pull_policy: always
container_name: gunei-backend-production
ports:
- "3100:3000" # Puerto diferente
networks:
- gunei-network
env_file:
- .env
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
tag: "{{.Name}}/{{.ID}}"
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/status"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
networks:
gunei-network:
external: true
Variables de entorno (.env):
# Database - GCP Cloud SQL via Proxy
DATABASE_URL=postgresql://dba_dev:PASSWORD@cloud-sql-proxy:5450/erp
# Application
NODE_ENV=production
PORT=3000 # Puerto interno (mapeado a 3100)
# AFIP
AFIP_KEY=...
AFIP_SECRET=...
# JWT
JWT_SECRET=...
3.3.4 Comandos Comunes
Staging:
# Ver logs del backend staging
docker logs gunei-backend-staging -f
# Ver últimas 50 líneas
docker logs gunei-backend-staging --tail 50
# Reiniciar backend staging
docker restart gunei-backend-staging
# Ver health status
curl http://localhost:3000/status
curl https://api.gunei.xyz/status
# Entrar al contenedor
docker exec -it gunei-backend-staging sh
# Ver variables de entorno
docker exec gunei-backend-staging env | grep DATABASE
# Deploy manual (pull + restart)
cd /opt/apps/gunei-erp/backend/staging
docker compose pull backend
docker compose up -d backend
Production:
# Ver logs del backend production (cuando esté deployado)
docker logs gunei-backend-production -f
# Reiniciar backend production
docker restart gunei-backend-production
# Ver health status (puerto diferente)
curl http://localhost:3100/status
# Deploy manual
cd /opt/apps/gunei-erp/backend/production
docker compose pull backend
docker compose up -d backend
3.3.5 Healthcheck
El backend expone dos endpoints de health:
/status - Estado básico
{
"status": "ok",
"timestamp": "2026-01-12T23:00:00Z",
"environment": "staging"
}
/ping - Ping simple
pong
3.3.6 Troubleshooting Backend
Problema: Backend no inicia
# Ver logs de error (staging)
docker logs gunei-backend-staging --tail 100
# Verificar variables de entorno
docker exec gunei-backend-staging env
# Verificar conexión a Cloud SQL via proxy
docker exec gunei-backend-staging psql -h cloud-sql-proxy -p 5450 -U dba_dev -d erp-dev -c "SELECT 1;"
Problema: "Database connection failed"
# Verificar cloud-sql-proxy está corriendo
docker ps | grep cloud-sql-proxy
# Ver logs del proxy
docker logs cloud-sql-proxy --tail 50
# Probar conexión desde backend
docker exec gunei-backend-staging psql -h cloud-sql-proxy -p 5450 -U dba_dev -d erp-dev -c "SELECT 1;"
# Verificar credenciales en .env
cat /opt/apps/gunei-erp/backend/staging/.env | grep DATABASE
Problema: Backend staging y production en conflicto
# Los puertos deben ser diferentes:
# - Staging: 3000 (host) → 3000 (container)
# - Production: 3100 (host) → 3000 (container)
# Verificar puertos en uso
netstat -tlnp | grep -E "3000|3100"
# Ver puertos de containers
docker ps --format "table {{.Names}}\t{{.Ports}}"
3.4 Frontend (SvelteKit + Bun)
3.4.1 Descripción
Aplicación web construida con:
- Framework: SvelteKit 5
- Runtime: Bun
- Adapter: svelte-adapter-bun (SSR con Bun)
- Features: ERP UI, AFIP integration
- Ambientes: Staging (operativo) y Production (preparado)
3.4.2 Frontend Staging
Ubicación:
cd /opt/apps/gunei-erp/frontend/staging
docker-compose.yml:
services:
frontend:
image: ghcr.io/gunei-dev/gunei-erp-front:develop
pull_policy: always
container_name: gunei-frontend-staging
ports:
- "3001:3001"
networks:
- gunei-network
environment:
- NODE_ENV=staging
- API_BASE_URL=https://api.gunei.xyz
- BASE_URL=https://api.gunei.xyz
- PUBLIC_BASE_URL=https://api.gunei.xyz
- PORT=3001
- ORIGIN=https://gunei.xyz
- TZ=America/Argentina/Buenos_Aires
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
tag: "{{.Name}}/{{.ID}}"
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3001/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
networks:
gunei-network:
external: true
3.4.3 Frontend Production (Preparado)
Ubicación:
cd /opt/apps/gunei-erp/frontend/production
docker-compose.yml:
services:
frontend:
image: ghcr.io/gunei-dev/gunei-erp-front:production
pull_policy: always
container_name: gunei-frontend-production
ports:
- "3101:3001" # Puerto diferente
networks:
- gunei-network
environment:
- NODE_ENV=production
- API_BASE_URL=https://api-prod.gunei.xyz
- BASE_URL=https://api-prod.gunei.xyz
- PUBLIC_BASE_URL=https://api-prod.gunei.xyz
- PORT=3001
- ORIGIN=https://prod.gunei.xyz
- TZ=America/Argentina/Buenos_Aires
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
tag: "{{.Name}}/{{.ID}}"
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3001/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
networks:
gunei-network:
external: true
3.4.4 Comandos Comunes
Staging:
# Ver logs del frontend staging
docker logs gunei-frontend-staging -f
# Reiniciar frontend staging
docker restart gunei-frontend-staging
# Ver health status
curl http://localhost:3001/health
curl https://gunei.xyz/health
# Deploy manual
cd /opt/apps/gunei-erp/frontend/staging
docker compose pull frontend
docker compose up -d frontend
# Ver configuración
docker inspect gunei-frontend-staging | grep -A 20 Env
Production:
# Ver logs del frontend production (cuando esté deployado)
docker logs gunei-frontend-production -f
# Reiniciar frontend production
docker restart gunei-frontend-production
# Ver health status (puerto diferente)
curl http://localhost:3101/health
# Deploy manual
cd /opt/apps/gunei-erp/frontend/production
docker compose pull frontend
docker compose up -d frontend
3.4.5 Health Endpoint
Endpoint: /healthMétodo: GET Acceso: Público (no requiere auth)
{
"status": "healthy",
"timestamp": "2026-01-12T23:00:00.000Z",
"service": "gunei-erp-frontend",
"version": "0.0.1",
"runtime": "bun",
"environment": "staging"
}
3.4.6 Troubleshooting Frontend
Problema: Frontend no carga
# Ver logs (staging)
docker logs gunei-frontend-staging --tail 100
# Verificar que está corriendo
docker ps | grep frontend
# Test interno desde Caddy
docker exec caddy-shared curl http://gunei-frontend-staging:3001/health
Problema: "Failed to fetch" en API calls
# Verificar CORS en Caddy
docker exec caddy-shared cat /etc/caddy/Caddyfile | grep -A 10 "api.gunei.xyz"
# Verificar backend está accesible
curl https://api.gunei.xyz/status
# Ver logs del navegador (Network tab en DevTools)
Problema: Frontend staging y production en conflicto
# Los puertos deben ser diferentes:
# - Staging: 3001 (host) → 3001 (container)
# - Production: 3101 (host) → 3001 (container)
# Verificar puertos en uso
netstat -tlnp | grep -E "3001|3101"
# Ver puertos de containers
docker ps --format "table {{.Names}}\t{{.Ports}}"
4. CI/CD Pipeline
4.1 Overview del CI/CD
Ambos repositorios (frontend y backend) tienen GitHub Actions configurado para deployment automático.
Flujo completo:
git push origin develop
↓
GitHub Actions triggered
↓
Build Docker image
↓
Push to GHCR (ghcr.io)
↓
SSH to VPS
↓
Ir al directorio del ambiente correcto
↓
docker compose pull
↓
docker compose up -d
↓
Health check
↓
Discord notification
4.2 Ambientes en CI/CD
| Branch | Ambiente | Directorio Deploy | Container |
|---|---|---|---|
develop | Staging | /opt/apps/gunei-erp/{backend|frontend}/staging | gunei-{backend|frontend}-staging |
main | Production | /opt/apps/gunei-erp/{backend|frontend}/production | gunei-{backend|frontend}-production |
4.3 Frontend CI/CD Workflow
Archivo: .github/workflows/deploy-staging.yml (para develop)
4.3.1 Trigger
on:
push:
branches: [develop] # Deploy a staging
workflow_dispatch: # También manual desde GitHub UI
4.3.2 Deploy Step (actualizado)
- name: Deploy to VPS - Staging
uses: appleboy/ssh-action@v1.0.0
with:
host: ${{ secrets.VPS_HOST }}
username: ${{ secrets.VPS_USER }}
key: ${{ secrets.VPS_SSH_KEY }}
script: |
cd /opt/apps/gunei-erp/frontend/staging
docker compose pull frontend
docker compose up -d frontend
sleep 10
docker compose ps
4.3.3 Health Check (actualizado)
- name: Health Check - Staging
run: |
max_attempts=30
attempt=0
while [ $attempt -lt $max_attempts ]; do
if curl -f https://gunei.xyz/health; then
echo "✅ Staging health check passed!"
exit 0
fi
attempt=$((attempt + 1))
echo "Attempt $attempt/$max_attempts failed, retrying in 2s..."
sleep 2
done
echo "❌ Staging health check failed after $max_attempts attempts"
exit 1
4.4 Backend CI/CD Workflow
Similar estructura pero con paths actualizados:
- name: Deploy to VPS - Staging
uses: appleboy/ssh-action@v1.0.0
with:
host: ${{ secrets.VPS_HOST }}
username: ${{ secrets.VPS_USER }}
key: ${{ secrets.VPS_SSH_KEY }}
script: |
cd /opt/apps/gunei-erp/backend/staging
docker compose pull backend
docker compose up -d backend
sleep 10
docker compose ps
4.5 Production CI/CD (Futuro)
Cuando se implemente production:
Archivo: .github/workflows/deploy-production.yml (para main)
on:
push:
branches: [main] # Deploy a production
workflow_dispatch:
# ... similar estructura pero con:
# - Directorio: /opt/apps/gunei-erp/{service}/production
# - Container: gunei-{service}-production
# - Health check: https://prod.gunei.xyz/health (o equivalente)
4.6 GitHub Secrets (sin cambios)
Los secrets se mantienen igual:
| Secret | Valor | Uso |
|---|---|---|
VPS_HOST | gunei.xyz | Hostname del VPS para SSH |
VPS_USER | root | Usuario SSH |
VPS_SSH_KEY | -----BEGIN OPENSSH PRIVATE KEY-----... | Private key ED25519 completa |
DISCORD_WEBHOOK_URL | https://discord.com/api/webhooks/... | Webhook para notificaciones |
5. Operaciones Diarias
5.1 Deploy Manual por Ambiente
5.1.1 Staging
Backend Staging:
ssh root@gunei.xyz
cd /opt/apps/gunei-erp/backend/staging
docker compose pull backend
docker compose up -d backend
docker compose ps
docker logs gunei-backend-staging --tail 20
curl http://localhost:3000/status
Frontend Staging:
ssh root@gunei.xyz
cd /opt/apps/gunei-erp/frontend/staging
docker compose pull frontend
docker compose up -d frontend
docker compose ps
docker logs gunei-frontend-staging --tail 20
curl http://localhost:3001/health
5.1.2 Production (cuando esté activo)
Backend Production:
ssh root@gunei.xyz
cd /opt/apps/gunei-erp/backend/production
docker compose pull backend
docker compose up -d backend
docker compose ps
docker logs gunei-backend-production --tail 20
curl http://localhost:3100/status
Frontend Production:
ssh root@gunei.xyz
cd /opt/apps/gunei-erp/frontend/production
docker compose pull frontend
docker compose up -d frontend
docker compose ps
docker logs gunei-frontend-production --tail 20
curl http://localhost:3101/health
5.2 Ver Logs por Ambiente
5.2.1 Logs en Tiempo Real
Staging:
# Frontend staging
docker logs gunei-frontend-staging -f
# Backend staging
docker logs gunei-backend-staging -f
# Cloud SQL Proxy
docker logs cloud-sql-proxy -f
# Caddy (compartido)
docker logs caddy-shared -f
Production:
# Frontend production
docker logs gunei-frontend-production -f
# Backend production
docker logs gunei-backend-production -f
5.2.2 Logs Históricos
# Últimas N líneas
docker logs gunei-frontend-staging --tail 50
# Desde hace X tiempo
docker logs gunei-backend-staging --since 1h
docker logs gunei-backend-staging --since 30m
# Entre timestamps
docker logs gunei-backend-staging --since 2026-01-12T10:00:00 --until 2026-01-12T12:00:00
# Buscar errores
docker logs gunei-backend-staging | grep -i error
docker logs gunei-backend-staging | grep -i "database\|connection"
5.2.3 Logs de Caddy por Ambiente
# Logs del frontend staging
docker exec caddy-shared cat /var/log/caddy/staging-frontend.log | tail -20
# Logs del backend staging
docker exec caddy-shared cat /var/log/caddy/staging-backend.log | tail -20
# Logs del frontend production (cuando exista)
docker exec caddy-shared cat /var/log/caddy/production-frontend.log | tail -20
# Buscar por IP
docker exec caddy-shared grep "186.158" /var/log/caddy/staging-backend.log
# Filtrar 500 errors
docker exec caddy-shared grep "status\":500" /var/log/caddy/staging-backend.log
5.3 Reiniciar Servicios por Ambiente
5.3.1 Reinicio Individual
Staging:
docker restart gunei-frontend-staging
docker restart gunei-backend-staging
Production:
docker restart gunei-frontend-production
docker restart gunei-backend-production
Infraestructura (afecta todos los ambientes):
docker restart cloud-sql-proxy
docker restart caddy-shared
5.3.2 Reinicio con docker-compose
Staging:
cd /opt/apps/gunei-erp/frontend/staging
docker compose restart frontend
cd /opt/apps/gunei-erp/backend/staging
docker compose restart backend
Production:
cd /opt/apps/gunei-erp/frontend/production
docker compose restart frontend
cd /opt/apps/gunei-erp/backend/production
docker compose restart backend
5.3.3 Reinicio Completo del Stack
Orden recomendado (reinicio de todos los ambientes):
# 1. Infraestructura primero
docker restart cloud-sql-proxy
sleep 5
# 2. Backends (ambos ambientes)
docker restart gunei-backend-staging
docker restart gunei-backend-production # si está corriendo
sleep 5
# 3. Frontends (ambos ambientes)
docker restart gunei-frontend-staging
docker restart gunei-frontend-production # si está corriendo
# 4. Caddy último (routing)
docker restart caddy-shared
Solo staging:
docker restart cloud-sql-proxy
sleep 5
docker restart gunei-backend-staging
sleep 3
docker restart gunei-frontend-staging
docker restart caddy-shared
5.4 Rollback por Ambiente
Si un deployment falla, podés volver a una versión anterior del ambiente específico.
5.4.1 Rollback con Tag Específico
Staging:
# Listar tags disponibles en GHCR
# https://github.com/orgs/Gunei-Dev/packages
# Pull un tag específico
docker pull ghcr.io/gunei-dev/gunei-erp-front:develop-abc1234
# Actualizar docker-compose.yml temporalmente
cd /opt/apps/gunei-erp/frontend/staging
nano docker-compose.yml
# Cambiar: image: ghcr.io/gunei-dev/gunei-erp-front:develop-abc1234
# Recrear contenedor
docker compose up -d frontend
Production (similar):
cd /opt/apps/gunei-erp/frontend/production
# Seguir proceso similar con tags de production
5.5 Actualizar Configuraciones por Ambiente
5.5.1 Variables de Entorno (Backend)
Staging:
# Editar .env de staging
cd /opt/apps/gunei-erp/backend/staging
nano .env
# Aplicar cambios (recrear contenedor)
docker compose up -d backend
# Verificar
docker exec gunei-backend-staging env | grep NUEVA_VARIABLE
Production:
cd /opt/apps/gunei-erp/backend/production
nano .env
docker compose up -d backend
docker exec gunei-backend-production env | grep NUEVA_VARIABLE
5.5.2 Variables de Entorno (Frontend)
Staging:
# Editar docker-compose.yml
cd /opt/apps/gunei-erp/frontend/staging
nano docker-compose.yml
# Modificar sección environment:
# environment:
# - NUEVA_VAR=valor
# Aplicar cambios
docker compose up -d frontend
# Verificar
docker inspect gunei-frontend-staging | grep -A 10 Env
Production:
cd /opt/apps/gunei-erp/frontend/production
nano docker-compose.yml
docker compose up -d frontend
docker inspect gunei-frontend-production | grep -A 10 Env
5.5.3 Caddyfile (afecta todos los ambientes)
# Editar Caddyfile
nano /opt/infrastructure/caddy/Caddyfile
# Reload sin downtime
docker exec caddy-shared caddy reload --config /etc/caddy/Caddyfile
# Si hay errores, verificar sintaxis
docker exec caddy-shared caddy validate --config /etc/caddy/Caddyfile
# Si el reload falla, reiniciar
docker restart caddy-shared
5.6 Ver Estado del Sistema por Ambiente
5.6.1 Todos los Contenedores
# Ver todos los contenedores
docker ps
# Ver con formato custom
docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"
# Ver solo servicios de Gunei
docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}" | grep -E "gunei|caddy|cloud-sql"
# Ver por ambiente
docker ps | grep staging
docker ps | grep production
5.6.2 Recursos (CPU, RAM, Network)
# Stats en tiempo real (todos los ambientes)
docker stats
# Stats sin stream (una vez)
docker stats --no-stream
# Stats de un contenedor específico
docker stats gunei-frontend-staging --no-stream
docker stats gunei-backend-staging --no-stream
# Comparar ambos ambientes
docker stats gunei-frontend-staging gunei-frontend-production --no-stream
5.6.3 Estado de la Red
# Ver la red gunei-network
docker network inspect gunei-network
# Ver qué contenedores están en la red
docker network inspect gunei-network | grep -A 3 "Containers"
# Verificar conectividad entre servicios
docker exec gunei-frontend-staging ping -c 3 gunei-backend-staging
docker exec gunei-backend-staging ping -c 3 cloud-sql-proxy
5.6.4 Disco y Volúmenes
# Espacio usado por Docker
docker system df
# Espacio por contenedor
docker ps --size
# Espacio en volúmenes
docker volume ls
docker volume inspect postgres_data
# Espacio en host
df -h /opt/infrastructure
df -h /opt/apps
df -h /var/lib/docker
6. Gestión de Ambientes
6.1 Estrategia de Ambientes
Development (Local) → Staging (VPS) → Production (VPS)
↓ ↓ ↓
Developer Laptop Ambiente de prueba Ambiente live
Staging:
- Para testing y validación
- Deploy automático desde branch
develop - Puede tener downtime
- Database:
erp-dev(GCP Cloud SQL) - URLs: gunei.xyz, api.gunei.xyz
Production:
- Para usuarios reales
- Deploy automático desde branch
main(o manual) - Zero-downtime deployments
- Database:
erp(GCP Cloud SQL) - URLs: prod.gunei.xyz, api-prod.gunei.xyz (pendiente configurar)
6.2 Promover de Staging a Production
6.2.1 Workflow Recomendado
# 1. Validar que staging funciona correctamente
curl https://gunei.xyz/health
curl https://api.gunei.xyz/status
# 2. Mergear develop a main (con PR en GitHub)
git checkout main
git merge develop
git push origin main
# 3. Esto triggerea CI/CD de production automáticamente
# O deploy manual si no está configurado CI/CD
# 4. Migrar datos si es necesario
# (Copiar data de staging a production)
6.2.2 Migración de Data entre Ambientes
Copiar data de staging a production:
# 1. Crear backup de staging (via backend container)
docker exec gunei-backend-staging pg_dump -h cloud-sql-proxy -p 5450 -U dba_dev erp-dev > staging_snapshot.sql
# 2. Para operaciones de recreación de DB, usar GCP Console o gcloud CLI
# https://console.cloud.google.com/sql/instances/gunei-erp
# 3. Restaurar snapshot en production (via backend container)
docker exec -i gunei-backend-production psql -h cloud-sql-proxy -p 5450 -U dba_dev -d erp < staging_snapshot.sql
# 4. Verificar
docker exec gunei-backend-production psql -h cloud-sql-proxy -p 5450 -U dba_dev -d erp -c "SELECT COUNT(*) FROM users;"
6.3 Testing entre Ambientes
6.3.1 Smoke Tests
Staging:
# Backend
curl https://api.gunei.xyz/status
curl https://api.gunei.xyz/ping
# Frontend
curl https://gunei.xyz/health
# Database (via backend container)
docker exec gunei-backend-staging psql -h cloud-sql-proxy -p 5450 -U dba_dev -d erp-dev -c "SELECT 1;"
Production (cuando esté activo):
# Backend
curl https://api-prod.gunei.xyz/status
curl https://api-prod.gunei.xyz/ping
# Frontend
curl https://prod.gunei.xyz/health
# Database (via backend container)
docker exec gunei-backend-production psql -h cloud-sql-proxy -p 5450 -U dba_dev -d erp -c "SELECT 1;"
6.3.2 Integration Tests
# Test completo del flujo staging
# 1. Frontend health
test_frontend=$(curl -s https://gunei.xyz/health | jq -r .status)
# 2. Backend health
test_backend=$(curl -s https://api.gunei.xyz/status | jq -r .status)
# 3. Database connectivity
docker exec gunei-backend-staging psql $DATABASE_URL -c "SELECT 1;"
# 4. Resultado
if [ "$test_frontend" = "healthy" ] && [ "$test_backend" = "ok" ]; then
echo "✅ Staging environment OK"
else
echo "❌ Staging environment FAIL"
fi
6.4 Aislamiento entre Ambientes
Garantías de aislamiento:
Database: Databases completamente separadas en GCP Cloud SQL
erp-dev(staging) vserp(production)- Mismo usuario (
dba_dev) pero databases aisladas - No hay foreign keys entre databases
Containers: Contenedores independientes
gunei-backend-stagingvsgunei-backend-production- Puertos diferentes en host
- Logs separados
Configuración: Variables de entorno por ambiente
.envseparados- API keys diferentes (opcional)
- Secrets independientes
Networking: Misma red pero comunicación controlada
- Todos en
gunei-network - Routing por Caddy según dominio
- No hay comunicación cross-environment
- Todos en
7. Monitoreo y Alertas
7.1 Scripts de Monitoreo Actualizados
El VPS tiene scripts de monitoreo en /root/scripts/ actualizados para multi-ambiente:
7.1.1 monitor-logs.sh (actualizado)
#!/bin/bash
echo "==================================="
echo "🔍 Gunei ERP - Monitor de Logs"
echo "==================================="
echo ""
# Función para mostrar logs con colores
show_logs() {
service=$1
title=$2
echo "📦 === $title ==="
if docker ps | grep -q $service; then
docker logs --tail 20 $service 2>&1 | tail -10
else
echo "⚠️ Container $service no está corriendo"
fi
echo ""
}
# Infraestructura
show_logs "cloud-sql-proxy" "Cloud SQL Proxy"
show_logs "caddy-shared" "Caddy Shared"
# Staging
echo "🔷 === STAGING ==="
show_logs "gunei-backend-staging" "Backend Staging"
show_logs "gunei-frontend-staging" "Frontend Staging"
# Production (si existe)
if docker ps | grep -q "gunei-backend-production"; then
echo "🔶 === PRODUCTION ==="
show_logs "gunei-backend-production" "Backend Production"
show_logs "gunei-frontend-production" "Frontend Production"
fi
# Estado de los containers
echo "📊 === Estado de Containers ==="
docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"
echo ""
# Uso de recursos
echo "💾 === Uso de Recursos ==="
docker stats --no-stream --format "table {{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}"
Uso:
/root/scripts/monitor-logs.sh
7.1.2 health-check.sh (actualizado)
#!/bin/bash
echo "🏥 Health Check - Gunei ERP (Multi-Environment)"
echo "================================================"
echo ""
# Colores
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
# Función para verificar un servicio
check_service() {
service=$1
url=$2
if curl -f -s -o /dev/null "$url"; then
echo -e "${GREEN}✅ $service: OK${NC}"
return 0
else
echo -e "${RED}❌ $service: FAIL${NC}"
return 1
fi
}
# Infraestructura
echo "🏗️ Infraestructura:"
if docker ps | grep -q cloud-sql-proxy; then
echo -e "${GREEN}✅ Cloud SQL Proxy: OK${NC}"
else
echo -e "${RED}❌ Cloud SQL Proxy: FAIL${NC}"
fi
# Staging
echo ""
echo "🔷 Staging Environment:"
check_service "Backend Staging /status" "http://localhost:3000/status"
check_service "Frontend Staging /health" "http://localhost:3001/health"
check_service "HTTPS api.gunei.xyz" "https://api.gunei.xyz/status"
check_service "HTTPS gunei.xyz" "https://gunei.xyz/health"
# Production (si existe)
if docker ps | grep -q "gunei-backend-production"; then
echo ""
echo "🔶 Production Environment:"
check_service "Backend Production /status" "http://localhost:3100/status"
check_service "Frontend Production /health" "http://localhost:3101/health"
# URLs públicas cuando estén configuradas:
# check_service "HTTPS api-prod.gunei.xyz" "https://api-prod.gunei.xyz/status"
# check_service "HTTPS prod.gunei.xyz" "https://prod.gunei.xyz/health"
fi
# Database connectivity (via backend containers)
echo ""
echo "🗄️ Database Connectivity (GCP Cloud SQL):"
if docker exec gunei-backend-staging psql -h cloud-sql-proxy -p 5450 -U dba_dev -d erp-dev -c "SELECT 1;" > /dev/null 2>&1; then
echo -e "${GREEN}✅ DB Staging (erp-dev): OK${NC}"
else
echo -e "${RED}❌ DB Staging (erp-dev): FAIL${NC}"
fi
if docker ps | grep -q "gunei-backend-production"; then
if docker exec gunei-backend-production psql -h cloud-sql-proxy -p 5450 -U dba_dev -d erp -c "SELECT 1;" > /dev/null 2>&1; then
echo -e "${GREEN}✅ DB Production (erp): OK${NC}"
else
echo -e "${RED}❌ DB Production (erp): FAIL${NC}"
fi
else
echo -e "${YELLOW}⚠️ DB Production: Backend no deployado${NC}"
fi
# Verificar containers corriendo
echo ""
echo "📦 Docker Containers:"
docker ps --format "table {{.Names}}\t{{.Status}}"
# Uso de disco
echo ""
echo "💾 System Resources:"
df -h / | tail -1 | awk '{print "Disk: "$3" / "$2" ("$5" used)"}'
free -h | grep Mem | awk '{print "RAM: "$3" / "$2" (used/total)"}'
Uso:
/root/scripts/health-check.sh
7.1.3 alert-check.sh (actualizado)
#!/bin/bash
LOG_FILE="/var/log/gunei-health.log"
DATE=$(date '+%Y-%m-%d %H:%M:%S')
# Ejecutar health check y capturar salida
output=$(/root/scripts/health-check.sh 2>&1)
# Buscar FAILs
if echo "$output" | grep -q "FAIL"; then
echo "[$DATE] ❌ ALERT: Service failure detected!" >> $LOG_FILE
echo "$output" >> $LOG_FILE
echo "---" >> $LOG_FILE
# TODO: Enviar notificación Discord aquí
# curl -X POST "$DISCORD_WEBHOOK" ...
else
echo "[$DATE] ✅ All services OK" >> $LOG_FILE
fi
Setup automático con cron:
# Editar crontab
crontab -e
# Agregar línea para ejecutar cada 5 minutos
*/5 * * * * /root/scripts/alert-check.sh
7.2 Notificaciones Discord (sin cambios)
Las notificaciones Discord funcionan igual que antes:
- ✅ GitHub Actions (deployments)
- ⏳ Pendiente: Alertas automáticas del VPS
7.3 Health Checks de Docker
Cada servicio tiene healthchecks configurados que funcionan por ambiente:
Staging:
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3001/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
Production:
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3001/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
Ver health status:
# Ver todos los contenedores con health
docker ps
# Ver health de un contenedor específico
docker inspect gunei-frontend-staging --format='{{.State.Health.Status}}'
docker inspect gunei-backend-staging --format='{{.State.Health.Status}}'
# Ver health de production (cuando esté activo)
docker inspect gunei-frontend-production --format='{{.State.Health.Status}}'
docker inspect gunei-backend-production --format='{{.State.Health.Status}}'
# Ver historial de health checks
docker inspect gunei-frontend-staging --format='{{json .State.Health}}' | jq
8. Troubleshooting
8.1 Problemas Específicos por Ambiente
8.1.1 "Container keeps restarting" - Staging
Síntomas:
docker ps
# STATUS: Restarting (1) Less than a second ago gunei-backend-staging
Diagnóstico:
# Ver logs
docker logs gunei-backend-staging --tail 100
# Ver últimos restarts
docker inspect gunei-backend-staging --format='{{json .State}}' | jq
# Errores comunes:
# - Port 3000 already in use (conflicto con otra cosa)
# - Missing environment variable
# - Database connection failed (credenciales incorrectas)
# - Permission denied
Soluciones:
# Si el puerto está en uso
netstat -tlnp | grep :3000
kill <PID>
# Si falta variable de entorno
docker inspect gunei-backend-staging | grep -A 10 Env
# Agregar en /opt/apps/gunei-erp/backend/staging/.env
# Si la DB no responde
docker exec gunei-backend-staging psql -h cloud-sql-proxy -p 5450 -U dba_dev -d erp-dev -c "SELECT 1;"
8.1.2 "502 Bad Gateway" - Problemas de Routing
Síntomas:
- Browser muestra "502 Bad Gateway"
- Caddy está corriendo pero no puede alcanzar backend/frontend
Diagnóstico:
# Verificar que el servicio está corriendo
docker ps | grep gunei-frontend-staging
# Test desde Caddy (nombre de container correcto)
docker exec caddy-shared wget -O- http://gunei-frontend-staging:3001/health
docker exec caddy-shared wget -O- http://gunei-backend-staging:3000/status
# Ver logs de Caddy
docker logs caddy-shared --tail 50 | grep -i error
Soluciones:
# Si el contenedor no está corriendo
docker start gunei-frontend-staging
# Si está corriendo pero no responde
docker restart gunei-frontend-staging
# Verificar la red
docker network inspect gunei-network | grep gunei-frontend-staging
# Verificar el Caddyfile
docker exec caddy-shared cat /etc/caddy/Caddyfile | grep -A 5 "gunei.xyz"
8.1.3 "Database Connection Failed" - Problemas de Cloud SQL Proxy
Síntomas:
- Backend logs muestran "connection refused" o "authentication failed"
- Endpoints devuelven 500 Internal Server Error
Diagnóstico:
# Verificar cloud-sql-proxy está corriendo
docker ps | grep cloud-sql-proxy
# Ver logs del proxy
docker logs cloud-sql-proxy --tail 50
# Test de conexión desde backend staging
docker exec gunei-backend-staging psql -h cloud-sql-proxy -p 5450 -U dba_dev -d erp-dev -c "SELECT 1;"
# Verificar que el proxy está escuchando
docker exec cloud-sql-proxy netstat -tlnp | grep 5450
Soluciones:
# Si cloud-sql-proxy no está corriendo
cd /opt/infrastructure/cloud-sql-proxy
docker compose up -d
# Verificar credenciales en .env
cat /opt/apps/gunei-erp/backend/staging/.env | grep DATABASE_URL
# Formato esperado:
# DATABASE_URL=postgresql://dba_dev:PASSWORD@cloud-sql-proxy:5450/erp-dev
# Si hay problemas con credentials.json
ls -la /opt/infrastructure/cloud-sql-proxy/credentials.json
# Verificar logs por errores de autenticación
docker logs cloud-sql-proxy | grep -i "error\|auth\|permission"
# Restart del proxy
docker restart cloud-sql-proxy
8.1.4 Conflictos entre Staging y Production
Síntomas:
- Un ambiente funciona pero el otro no
- Puertos en conflicto
- Conexiones a database incorrecta
Diagnóstico:
# Ver puertos de ambos ambientes
docker ps --format "table {{.Names}}\t{{.Ports}}"
# Resultado esperado:
# gunei-backend-staging 0.0.0.0:3000->3000/tcp
# gunei-backend-production 0.0.0.0:3100->3000/tcp
# gunei-frontend-staging 0.0.0.0:3001->3001/tcp
# gunei-frontend-production 0.0.0.0:3101->3001/tcp
# Verificar DATABASE_URL de cada ambiente
docker exec gunei-backend-staging env | grep DATABASE_URL
docker exec gunei-backend-production env | grep DATABASE_URL
Soluciones:
# Si hay conflicto de puertos, parar el servicio que no usas
docker stop gunei-backend-staging # si querés correr solo production
# O cambiar el puerto en docker-compose.yml
cd /opt/apps/gunei-erp/backend/production
nano docker-compose.yml
# ports:
# - "3100:3000" # Asegurar que es diferente
# Verificar que cada ambiente apunta a su database correcta
# Staging debe tener: erp-dev
# Production debe tener: erp
8.2 Comandos de Debugging por Ambiente
8.2.1 Inspeccionar Contenedores
# Ver toda la configuración de staging
docker inspect gunei-frontend-staging
# Ver solo variables de entorno
docker inspect gunei-frontend-staging --format='{{json .Config.Env}}' | jq
# Ver solo networking
docker inspect gunei-frontend-staging --format='{{json .NetworkSettings.Networks}}' | jq
# Ver health status
docker inspect gunei-frontend-staging --format='{{json .State.Health}}' | jq
# Comparar configuración entre ambientes
diff <(docker inspect gunei-frontend-staging) <(docker inspect gunei-frontend-production)
8.2.2 Entrar a un Contenedor
# Staging
docker exec -it gunei-frontend-staging sh
docker exec -it gunei-backend-staging sh
# Production
docker exec -it gunei-frontend-production sh
docker exec -it gunei-backend-production sh
# Cloud SQL Proxy (shell access)
docker exec -it cloud-sql-proxy sh
# Como root
docker exec -it -u root gunei-frontend-staging sh
# Ejecutar comando único
docker exec gunei-backend-staging ls -la /app
docker exec gunei-backend-staging cat /app/package.json
docker exec gunei-backend-staging env
8.2.3 Verificar Conectividad entre Ambientes
# Frontend staging → Backend staging
docker exec gunei-frontend-staging curl http://gunei-backend-staging:3000/status
# Backend staging → Cloud SQL Proxy
docker exec gunei-backend-staging psql -h cloud-sql-proxy -p 5450 -U dba_dev -d erp-dev -c "SELECT 1;"
# Backend production → Cloud SQL Proxy (mismo proxy, diferente DB)
docker exec gunei-backend-production psql -h cloud-sql-proxy -p 5450 -U dba_dev -d erp -c "SELECT 1;"
# Verificar DNS interno
docker exec gunei-frontend-staging nslookup cloud-sql-proxy
docker exec gunei-backend-staging getent hosts cloud-sql-proxy
8.3 Recovery Procedures por Ambiente
8.3.1 Recovery de Staging
# 1. Verificar infraestructura
docker ps | grep -E "cloud-sql-proxy|caddy-shared"
# 2. Reiniciar servicios de staging en orden
docker restart cloud-sql-proxy
sleep 5
docker restart gunei-backend-staging
sleep 5
docker restart gunei-frontend-staging
docker restart caddy-shared
# 3. Verificar
/root/scripts/health-check.sh
8.3.2 Recovery de Production (cuando esté activo)
# 1. Verificar infraestructura (igual que staging)
docker ps | grep -E "cloud-sql-proxy|caddy-shared"
# 2. Reiniciar servicios de production en orden
docker restart cloud-sql-proxy
sleep 5
docker restart gunei-backend-production
sleep 5
docker restart gunei-frontend-production
docker restart caddy-shared
# 3. Verificar
/root/scripts/health-check.sh
8.3.3 Recovery Completo de Todo el Sistema
# CUIDADO: Esto afecta AMBOS ambientes
# 1. Verificar Docker daemon
systemctl status docker
systemctl restart docker
# 2. Iniciar infraestructura
docker start cloud-sql-proxy
sleep 5
docker start caddy-shared
sleep 5
# 3. Iniciar staging
docker start gunei-backend-staging
sleep 3
docker start gunei-frontend-staging
sleep 3
# 4. Iniciar production (si está deployado)
docker start gunei-backend-production
sleep 3
docker start gunei-frontend-production
# 5. Verificar
docker ps
/root/scripts/health-check.sh
9. Apéndices
9.1 Cheat Sheet: Docker (sin cambios)
# === CONTAINERS ===
# Listar contenedores corriendo
docker ps
# Listar todos (incluidos detenidos)
docker ps -a
# Ver logs
docker logs <container> -f
# Iniciar/detener
docker start <container>
docker stop <container>
docker restart <container>
# Eliminar
docker rm <container>
docker rm -f <container> # Forzar
# Ejecutar comando
docker exec <container> <command>
docker exec -it <container> sh
# Ver stats
docker stats <container> --no-stream
# === IMAGES ===
# Listar imágenes
docker images
# Pull image
docker pull <image>:<tag>
# Eliminar image
docker rmi <image>
# Construir image
docker build -t <name>:<tag> .
# Tag image
docker tag <source> <target>
# === COMPOSE ===
# Iniciar servicios
docker compose up -d
# Detener servicios
docker compose down
# Ver logs
docker compose logs -f <service>
# Recrear un servicio
docker compose up -d --force-recreate <service>
# Ver servicios
docker compose ps
# === NETWORKS ===
# Listar redes
docker network ls
# Inspeccionar red
docker network inspect <network>
# === CLEANUP ===
# Limpiar containers detenidos
docker container prune
# Limpiar imágenes sin usar
docker image prune
# Limpiar todo (cuidado!)
docker system prune -a
# Ver espacio usado
docker system df
9.2 Cheat Sheet: Caddy (actualizado)
# === CONFIGURATION ===
# Recargar configuración (nombre actualizado)
docker exec caddy-shared caddy reload --config /etc/caddy/Caddyfile
# Validar configuración
docker exec caddy-shared caddy validate --config /etc/caddy/Caddyfile
# Formatear Caddyfile
docker exec caddy-shared caddy fmt --overwrite /etc/caddy/Caddyfile
# === CERTIFICATES ===
# Listar certificados
docker exec caddy-shared ls -la /data/caddy/certificates/
# Ver detalles de certificado
docker exec caddy-shared cat /data/caddy/certificates/acme-v02.api.letsencrypt.org-directory/gunei.xyz/gunei.xyz.json
# === LOGS ===
# Ver logs del contenedor
docker logs caddy-shared -f
# Ver access logs por ambiente
docker exec caddy-shared cat /var/log/caddy/staging-frontend.log
docker exec caddy-shared cat /var/log/caddy/staging-backend.log
docker exec caddy-shared cat /var/log/caddy/production-frontend.log # cuando exista
docker exec caddy-shared cat /var/log/caddy/production-backend.log # cuando exista
# Tail logs
docker exec caddy-shared tail -f /var/log/caddy/staging-backend.log
# === TESTING ===
# Test health desde Caddy (nombres actualizados)
docker exec caddy-shared wget -O- http://gunei-frontend-staging:3001/health
docker exec caddy-shared wget -O- http://gunei-backend-staging:3000/status
# === TROUBLESHOOTING ===
# Ver configuración cargada
docker exec caddy-shared caddy run --config /etc/caddy/Caddyfile --dry-run
9.3 Cheat Sheet: Cloud SQL (vía Proxy)
# === CONEXIONES (desde containers en gunei-network) ===
# Conectarse a staging (erp-dev)
docker exec -it gunei-backend-staging psql -h cloud-sql-proxy -p 5450 -U dba_dev -d erp-dev
# Conectarse a production (erp)
docker exec -it gunei-backend-production psql -h cloud-sql-proxy -p 5450 -U dba_dev -d erp
# === QUERIES ADMINISTRATIVAS ===
# Listar databases
docker exec gunei-backend-staging psql -h cloud-sql-proxy -p 5450 -U dba_dev -d erp-dev -c "\l"
# Ver tablas de staging
docker exec gunei-backend-staging psql -h cloud-sql-proxy -p 5450 -U dba_dev -d erp-dev -c "\dt"
# Ver tablas de production
docker exec gunei-backend-production psql -h cloud-sql-proxy -p 5450 -U dba_dev -d erp -c "\dt"
# Ejecutar query en staging
docker exec gunei-backend-staging psql -h cloud-sql-proxy -p 5450 -U dba_dev -d erp-dev -c "SELECT COUNT(*) FROM users;"
# Ver conexiones activas
docker exec gunei-backend-staging psql -h cloud-sql-proxy -p 5450 -U dba_dev -d erp-dev -c "SELECT datname, usename, application_name, state FROM pg_stat_activity;"
# === BACKUPS ===
# GCP maneja backups automáticos - ver en GCP Console
# https://console.cloud.google.com/sql/instances/gunei-erp/backups
# Dump local de staging (via proxy)
docker exec gunei-backend-staging pg_dump -h cloud-sql-proxy -p 5450 -U dba_dev erp-dev > backup_staging_$(date +%Y%m%d).sql
# Dump local de production (via proxy)
docker exec gunei-backend-production pg_dump -h cloud-sql-proxy -p 5450 -U dba_dev erp > backup_production_$(date +%Y%m%d).sql
# === HEALTH CHECKS ===
# Verificar cloud-sql-proxy está corriendo
docker ps | grep cloud-sql-proxy
# Ver logs del proxy
docker logs cloud-sql-proxy -f
# Test de conexión staging
docker exec gunei-backend-staging psql -h cloud-sql-proxy -p 5450 -U dba_dev -d erp-dev -c "SELECT 1;"
# Test de conexión production
docker exec gunei-backend-production psql -h cloud-sql-proxy -p 5450 -U dba_dev -d erp -c "SELECT 1;"
# === CLOUD SQL PROXY ===
# Restart del proxy
docker restart cloud-sql-proxy
# Ver estado del proxy
docker exec cloud-sql-proxy ps aux | grep cloud-sql-proxy
# Ver logs completos
docker logs cloud-sql-proxy --tail 100
9.4 Variables de Entorno por Ambiente
9.4.1 Frontend Staging
| Variable | Valor | Descripción |
|---|---|---|
NODE_ENV | staging | Modo de ejecución |
API_BASE_URL | https://api.gunei.xyz | URL del backend (server-side) |
PUBLIC_BASE_URL | https://api.gunei.xyz | URL del backend (client-side) |
PORT | 3001 | Puerto del servidor |
ORIGIN | https://gunei.xyz | Origen para CORS |
TZ | America/Argentina/Buenos_Aires | Timezone |
9.4.2 Frontend Production
| Variable | Valor | Descripción |
|---|---|---|
NODE_ENV | production | Modo de ejecución |
API_BASE_URL | https://api-prod.gunei.xyz | URL del backend (server-side) |
PUBLIC_BASE_URL | https://api-prod.gunei.xyz | URL del backend (client-side) |
PORT | 3001 | Puerto interno (mapeado a 3101) |
ORIGIN | https://prod.gunei.xyz | Origen para CORS |
TZ | America/Argentina/Buenos_Aires | Timezone |
9.4.3 Backend Staging
Variables en /opt/apps/gunei-erp/backend/staging/.env:
# Database - GCP Cloud SQL via Proxy
DATABASE_URL=postgresql://dba_dev:PASSWORD@cloud-sql-proxy:5450/erp-dev
# Application
NODE_ENV=staging
PORT=3000
# AFIP (ejemplos)
AFIP_KEY=...
AFIP_SECRET=...
# JWT
JWT_SECRET=...
9.4.4 Backend Production
Variables en /opt/apps/gunei-erp/backend/production/.env:
# Database - GCP Cloud SQL via Proxy
DATABASE_URL=postgresql://dba_dev:PASSWORD@cloud-sql-proxy:5450/erp
# Application
NODE_ENV=production
PORT=3000 # Puerto interno (mapeado a 3100)
# AFIP
AFIP_KEY=...
AFIP_SECRET=...
# JWT
JWT_SECRET=...
9.5 Puertos y Endpoints por Ambiente
9.5.1 Staging
| Servicio | Puerto Interno | Puerto Expuesto | URL Pública |
|---|---|---|---|
| Caddy Shared | 80, 443 | 80, 443 | - |
| Cloud SQL Proxy | 5450 | - (solo gunei-network) | - |
| Frontend Staging | 3001 | 3001 | https://gunei.xyz |
| Backend Staging | 3000 | 3000 | https://api.gunei.xyz |
Endpoints importantes:
| Endpoint | Método | Descripción |
|---|---|---|
https://gunei.xyz/health | GET | Frontend staging health check |
https://api.gunei.xyz/status | GET | Backend staging health check |
https://api.gunei.xyz/ping | GET | Backend staging ping |
9.5.2 Production (cuando esté activo)
| Servicio | Puerto Interno | Puerto Expuesto | URL Pública |
|---|---|---|---|
| Caddy Shared | 80, 443 | 80, 443 | - |
| Cloud SQL Proxy | 5450 | - (solo gunei-network) | - |
| Frontend Production | 3001 | 3101 | https://prod.gunei.xyz (pendiente) |
| Backend Production | 3000 | 3100 | https://api-prod.gunei.xyz (pendiente) |
Endpoints importantes:
| Endpoint | Método | Descripción |
|---|---|---|
https://prod.gunei.xyz/health | GET | Frontend production health check |
https://api-prod.gunei.xyz/status | GET | Backend production health check |
https://api-prod.gunei.xyz/ping | GET | Backend production ping |
9.6 Mapeo de Migración (Referencia)
Estructura anterior → Estructura nueva:
| Anterior | Nuevo | Notas |
|---|---|---|
/opt/caddy/ | /opt/infrastructure/caddy/ | Ahora compartido |
/opt/postgres/ | - | Eliminado, migrado a GCP Cloud SQL |
/opt/infrastructure/postgres/ | /opt/infrastructure/cloud-sql-proxy/ | PostgreSQL local → GCP Cloud SQL |
/root/gunei-erp-back/ | /opt/apps/gunei-erp/backend/staging/ | Backend staging |
/opt/frontend/ | /opt/apps/gunei-erp/frontend/staging/ | Frontend staging |
| - | /opt/apps/gunei-erp/backend/production/ | Nuevo (production) |
| - | /opt/apps/gunei-erp/frontend/production/ | Nuevo (production) |
Contenedores anteriores → Contenedores nuevos:
| Anterior | Nuevo | Notas |
|---|---|---|
caddy | caddy-shared | Mismo Caddy, nombre actualizado |
postgres-shared | cloud-sql-proxy | PostgreSQL local → GCP Cloud SQL via proxy |
gunei-backend | gunei-backend-staging | Backend en ambiente staging |
gunei-frontend | gunei-frontend-staging | Frontend en ambiente staging |
| - | gunei-backend-production | Nuevo (production) |
| - | gunei-frontend-production | Nuevo (production) |
Databases anteriores → Databases nuevas:
| Anterior | Nuevo | Notas |
|---|---|---|
gunei_erp_staging (postgres-shared:5433) | erp-dev (GCP Cloud SQL via proxy:5450) | Migrado a GCP |
gunei_erp_production (postgres-shared:5433) | erp (GCP Cloud SQL via proxy:5450) | Migrado a GCP |
Infraestructura de Base de Datos:
| Anterior | Nuevo | Notas |
|---|---|---|
| PostgreSQL 17 local (VPS) | GCP Cloud SQL (PostgreSQL) | Backups automáticos por GCP |
| Backups manuales/cron | Backups automáticos GCP | Configurado en GCP Console |
| Puerto 5433 | Puerto 5450 (via proxy) | Cloud SQL Proxy en gunei-network |
Red anterior → Red nueva:
| Anterior | Nuevo | Notas |
|---|---|---|
red-npm | gunei-network | Misma funcionalidad, nombre enterprise |
9.7 Contactos y Recursos (sin cambios)
9.7.1 Equipo
- Adrian Gallegos: Project Manager
- Miguel Culaciati: Frontend Developer
- Facundo Sardi: Backend Developer
- Miguel Culaciati: Devops
9.7.2 Recursos Externos
Documentación:
- Bun Documentation
- Hono Documentation
- SvelteKit Documentation
- Caddy Documentation
- Docker Documentation
- PostgreSQL Documentation
GitHub Repos:
- Frontend: https://github.com/Gunei-Dev/gunei-erp-front
- Backend: https://github.com/Gunei-Dev/gunei-erp-back
Servicios:
- VPS Provider: Contabo
- DNS: Porkbun
- Container Registry: GitHub Container Registry
9.7.3 Próximos Pasos
- Activar Production: Configurar dominios y deployar production
- Grafana + Prometheus: Métricas visuales avanzadas
- Migra + DbMate workflow: Documentar proceso completo de migraciones
- Security hardening: fail2ban, firewall rules, SSH hardening
- Email notifications: Alternativa/complemento a Discord
- GCP Monitoring: Integrar alertas de Cloud SQL con Discord
🎉 Fin del Runbook
Versión: 2.1 - GCP Cloud SQL Migration Fecha: 22 Enero 2026 Autor: Mikle + Claude Proyecto: Gunei ERP
Cambios principales en v2.1:
- ✅ Migración de PostgreSQL local a GCP Cloud SQL
- ✅ Cloud SQL Proxy como container en gunei-network
- ✅ Backups automáticos gestionados por GCP
- ✅ Eliminación de postgres-shared container
- ✅ Actualización de scripts de monitoreo para cloud-sql-proxy
- ✅ Nueva documentación de troubleshooting para Cloud SQL
Cambios anteriores (v2.0):
- ✅ Arquitectura por ambientes (staging/production)
- ✅ Estructura de directorios enterprise
- ✅ Red Docker unificada (gunei-network)
- ✅ Scripts de monitoreo multi-ambiente
- ✅ Documentación completa de cada ambiente