🚀 Deployment Guide - Gunei ERP (Enterprise Architecture)
Guía completa de deployment del sistema Gunei ERP en VPS Contabo (gunei.xyz)
Versión 2.1 - Arquitectura Enterprise Multi-Ambiente
📋 Tabla de Contenidos
- Quick Reference
- Arquitectura Enterprise
- Pre-requisitos
- SSH Key Configuration
- Configuración Inicial
- PostgreSQL Shared
- Verificación de Configuración
- Docker Compose Configuration
- Deployment por Ambiente
- CI/CD Pipeline
- Gestión de Ambientes
- Monitoreo
- Backups
- Troubleshooting
⚡ Quick Reference
Comandos Más Usados
| Acción | Staging | Production |
|---|---|---|
| Deploy | cd /opt/apps/gunei-erp/backend/staging && docker compose pull && docker compose up -d | cd /opt/apps/gunei-erp/backend/production && docker compose pull && docker compose up -d |
| Logs | docker logs gunei-backend-staging -f | docker logs gunei-backend-production -f |
| Restart | docker compose restart backend | docker compose restart backend |
| Health | curl https://staging-erpback.gunei.xyz/status | curl https://erpback.gunei.xyz/status |
Conexión PostgreSQL
# Desde containers (red Docker interna)
postgresql://user:pass@postgres-shared:5432/database
# Desde host (conexión externa)
psql -h localhost -p 5433 -U postgres
Ubicaciones Clave
| Recurso | Path |
|---|---|
| Backend Staging | /opt/apps/gunei-erp/backend/staging/ |
| Backend Production | /opt/apps/gunei-erp/backend/production/ |
| Frontend Staging | /opt/apps/gunei-erp/frontend/staging/ |
| Frontend Production | /opt/apps/gunei-erp/frontend/production/ |
| PostgreSQL | /opt/infrastructure/postgres/ |
| Caddy | /opt/infrastructure/caddy/ |
| Scripts | /root/scripts/ |
Timezone
Todos los containers usan TZ=America/Argentina/Buenos_Aires. Verificar:
docker exec gunei-backend-staging date
🗺️ Arquitectura Enterprise
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 | PostgreSQL | 17 | 5433 |
| Reverse Proxy | Caddy | Latest | 80/443 |
| Container Runtime | Docker | 28.x | - |
| Orchestration | Docker Compose | 2.x | - |
| CI/CD | GitHub Actions | - | - |
| Registry | GHCR | - | - |
Infraestructura Actual
Internet
↓
┌────────────────────────────┐
│ Caddy Shared (:80/:443) │
│ SSL automático + Routing │
└────────────────────────────┘
↓ ↓
┌───────────────────┐ ┌───────────────────┐
│ STAGING │ │ PRODUCTION │
│ staging-erpfront.gunei.xyz │ │ (URLs pendientes) │
│ staging-erpback.gunei.xyz │ │ │
└───────────────────┘ └───────────────────┘
↓ ↓ ↓ ↓
┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐
│Front │ │Back │ │Front │ │Back │
│:3001 │ │:3000 │ │:3101 │ │:3100 │
└──────┘ └──────┘ └──────┘ └──────┘
↓ ↓ ↓ ↓
└─────────┴────────────┴─────────┘
↓
┌────────────────────────────┐
│ PostgreSQL Shared │
│ Puerto: 5433 (ext) │
│ Puerto: 5432 (int) │
├────────────────────────────┤
│ gunei_erp_staging │
│ └─ gunei_staging_user │
│ gunei_erp_production │
│ └─ gunei_prod_user │
└────────────────────────────┘
↓
┌────────────────────────────┐
│ gunei-network (Docker) │
│ Conecta todos los │
│ servicios internamente │
└────────────────────────────┘
Arquitectura de Directorios
/opt/
├── infrastructure/ # Servicios compartidos
│ ├── caddy/ # Reverse proxy compartido
│ │ ├── Caddyfile # Configuración de routing
│ │ ├── docker-compose.yml # Definición del servicio
│ │ └── caddy_data/ # Certificados SSL
│ │
│ ├── postgres/ # Base de datos compartida
│ │ ├── docker-compose.yml # Definición del servicio
│ │ ├── init-scripts/ # Scripts de inicialización
│ │ └── backups/ # Directorio de backups
│ │
│ └── monitoring/ # Monitoreo (futuro)
│ └── docker-compose.yml
│
└── apps/
└── gunei-erp/
├── backend/
│ ├── staging/
│ │ ├── docker-compose.yml
│ │ └── .env # Variables staging
│ └── production/
│ ├── docker-compose.yml
│ └── .env # Variables production
│
└── frontend/
├── staging/
│ └── docker-compose.yml
└── production/
└── docker-compose.yml
/root/scripts/ # Scripts operacionales
├── monitor-logs.sh
├── health-check.sh
├── backup-postgres.sh
└── ...
Ambientes y URLs
Staging (Ambiente Operativo Actual)
- Frontend: https://staging-erpfront.gunei.xyz →
gunei-frontend-staging:3001 - Backend: https://staging-erpback.gunei.xyz →
gunei-backend-staging:3000 - Database:
gunei_erp_stagingenpostgres-shared:5433 - Branch:
develop - Deploy: Automático via GitHub Actions
Production (Preparado, No Deployado)
- Frontend: (Pendiente configurar) →
gunei-frontend-production:3101 - Backend: (Pendiente configurar) →
gunei-backend-production:3100 - Database:
gunei_erp_productionenpostgres-shared:5433 - Branch:
main - Deploy: Manual (futuro: automático)
Contenedores y Red
Red Docker: gunei-network
Todos los servicios comparten esta red para comunicación interna:
| Contenedor | Servicio | Puerto Interno | Puerto Externo |
|---|---|---|---|
caddy-shared | Reverse Proxy | 80, 443 | 80, 443 |
postgres-shared | PostgreSQL 17 | 5432 | 5433 |
gunei-frontend-staging | Frontend Staging | 3001 | 3001 |
gunei-backend-staging | Backend Staging | 3000 | 3000 |
gunei-frontend-production | Frontend Production | 3001 | 3101 |
gunei-backend-production | Backend Production | 3000 | 3100 |
Nota sobre puertos: PostgreSQL usa puerto 5433 externamente (5432 internamente) para evitar conflictos con otros servicios PostgreSQL en el sistema.
📦 Pre-requisitos
En el VPS
- Ubuntu 24.04 LTS
- Docker 28.x + Docker Compose 2.x
- Red Docker:
gunei-network(creada) - Estructura de directorios enterprise (ver Arquitectura)
Secrets Necesarios (GitHub Actions)
# GitHub Secrets (Settings → Secrets → Actions)
VPS_HOST=gunei.xyz
VPS_USER=root
VPS_SSH_KEY=<ed25519-private-key>
GHCR_TOKEN=<github-token>
DISCORD_WEBHOOK_URL=<webhook-url>
Ubicación: Repo → Settings → Secrets and variables → Actions
Verificar Pre-requisitos en VPS
# Conectar al VPS
ssh root@gunei.xyz
# Verificar Docker
docker --version
docker compose version
# Verificar red Docker
docker network ls | grep gunei-network
# Verificar estructura de directorios
ls -la /opt/infrastructure/
ls -la /opt/apps/gunei-erp/
# Verificar servicios de infraestructura
docker ps | grep -E "caddy-shared|postgres-shared"
🔐 SSH Key Configuration
GitHub Actions SSH Access
El CI/CD usa autenticación por SSH key (ED25519) para conectarse al VPS.
Clave configurada:
- Tipo: ED25519 (más segura y compatible que RSA)
- Ubicación en VPS:
/root/.ssh/github-actions - Public key en:
/root/.ssh/authorized_keys - Secret en GitHub:
VPS_SSH_KEY
Por qué ED25519:
- ✅ Más corta (~400 bytes vs ~3200 bytes RSA)
- ✅ Mejor compatibilidad con GitHub Actions
- ✅ Más moderna y segura (equivalente a RSA-3072)
- ✅ Menos propensa a errores de parsing en GitHub Secrets
- ✅ Más rápida en operaciones criptográficas
Claves SSH actuales en el VPS
# Ver claves públicas autorizadas
cat ~/.ssh/authorized_keys
# Actualmente configuradas:
# 1. Adrian Gallegos (ssh-rsa)
# 2. Mikle (ssh-ed25519)
# 3. GitHub Actions (ssh-ed25519)
Regenerar SSH Key (si es necesario)
# En el VPS
ssh root@gunei.xyz
# Generar nueva clave ED25519
ssh-keygen -t ed25519 -C "github-actions-gunei" -f ~/.ssh/github-actions-new -N ""
# Agregar al authorized_keys
cat ~/.ssh/github-actions-new.pub >> ~/.ssh/authorized_keys
# Backup de la anterior y reemplazar
mv ~/.ssh/github-actions ~/.ssh/github-actions.old
mv ~/.ssh/github-actions-new ~/.ssh/github-actions
# Mostrar la clave privada para copiar al secret
cat ~/.ssh/github-actions
Actualizar en GitHub:
- Ir a: https://github.com/gunei-dev/gunei-erp-{back|front}/settings/secrets/actions
- Actualizar
VPS_SSH_KEYcon la nueva clave privada completa - Asegurarse de copiar TODO (desde
-----BEGIN OPENSSH PRIVATE KEY-----hasta-----END OPENSSH PRIVATE KEY-----) - NO agregar espacios o líneas extra al principio o final
- Re-run el último workflow para probar
Verificar configuración SSH
# En el VPS
# Verificar que la clave está en authorized_keys
grep "github-actions-gunei" ~/.ssh/authorized_keys
# Verificar permisos correctos
ls -la ~/.ssh/
# authorized_keys debe ser 600
# github-actions debe ser 600
# Test de conexión desde local (si tenés la clave)
ssh -i ~/.ssh/github-actions root@gunei.xyz
⚙️ Configuración Inicial
1. Infraestructura Compartida
La infraestructura compartida debe configurarse una sola vez y servirá para todos los ambientes.
1.1 PostgreSQL Shared
# Conectar al VPS
ssh root@gunei.xyz
# Ir al directorio
cd /opt/infrastructure/postgres
# Verificar docker-compose.yml
cat docker-compose.yml
# Iniciar PostgreSQL
docker compose up -d
# Verificar
docker ps | grep postgres-shared
docker logs postgres-shared --tail 20
# Verificar databases
docker exec postgres-shared psql -U postgres -c "\l"
Databases esperadas:
gunei_erp_staging(usuario:gunei_staging_user)gunei_erp_production(usuario:gunei_prod_user)
1.2 Caddy Shared
# Ir al directorio
cd /opt/infrastructure/caddy
# Verificar Caddyfile
cat Caddyfile
# Iniciar Caddy
docker compose up -d
# Verificar
docker ps | grep caddy-shared
docker logs caddy-shared --tail 20
# Verificar SSL
docker exec caddy-shared ls -la /data/caddy/certificates/
Dominios configurados (staging):
staging-erpfront.gunei.xyz→gunei-frontend-staging:3001staging-erpback.gunei.xyz→gunei-backend-staging:3000
1.3 Red Docker
# Crear red compartida (si no existe)
docker network create gunei-network
# Verificar
docker network inspect gunei-network
2. Configurar Backend Staging
# Ir al directorio
cd /opt/apps/gunei-erp/backend/staging
# Crear .env
cat > .env << 'EOF'
# Database - PostgreSQL Shared
DATABASE_URL=postgresql://gunei_staging_user:staging_password@postgres-shared:5432/gunei_erp_staging
# Application
NODE_ENV=staging
PORT=3000
# AFIP (configurar según sea necesario)
AFIP_CERT_PATH=/app/certs/cert.pem
AFIP_KEY_PATH=/app/certs/key.pem
AFIP_KEY=...
AFIP_SECRET=...
# JWT
JWT_SECRET=...
# Otros servicios...
EOF
# Verificar docker-compose.yml
cat docker-compose.yml
Importante: El DATABASE_URL debe apuntar a postgres-shared:5432 (puerto interno de Docker) y usar la database gunei_erp_staging.
3. Configurar Frontend Staging
# Ir al directorio
cd /opt/apps/gunei-erp/frontend/staging
# Verificar docker-compose.yml
cat docker-compose.yml
# Variables de entorno (inline en docker-compose.yml)
# - API_BASE_URL=https://staging-erpback.gunei.xyz
# - PUBLIC_BASE_URL=https://staging-erpback.gunei.xyz
# - PORT=3001
# - ORIGIN=https://gunei.xyz
4. Configurar Production (Opcional)
Similar a staging pero en directorios production/:
Backend Production:
cd /opt/apps/gunei-erp/backend/production
# .env con credenciales de production
DATABASE_URL=postgresql://gunei_prod_user:production_password@postgres-shared:5432/gunei_erp_production
NODE_ENV=production
PORT=3000 # Puerto interno (mapeado a 3100)
Frontend Production:
cd /opt/apps/gunei-erp/frontend/production
# Variables en docker-compose.yml
# - API_BASE_URL=https://erpback.gunei.xyz (cuando se configure)
# - PORT=3001 (mapeado a 3101)
🐘 PostgreSQL Shared
Configuración del Servidor
PostgreSQL corre como servicio compartido para todos los ambientes.
| Parámetro | Valor |
|---|---|
| Container | postgres-shared |
| Imagen | postgres:17 |
| Puerto Externo | 5433 |
| Puerto Interno | 5432 |
| Red | gunei-network |
| Data Volume | /opt/infrastructure/postgres/data |
Databases y Usuarios
| Database | Usuario | Ambiente | Uso |
|---|---|---|---|
gunei_erp_staging | gunei_staging_user | Staging | Testing/QA |
gunei_erp_production | gunei_prod_user | Production | Usuarios reales |
Conexión desde Aplicaciones
Desde containers (red Docker interna):
# Host: postgres-shared (nombre del container)
# Puerto: 5432 (interno)
# Staging
postgresql://gunei_staging_user:password@postgres-shared:5432/gunei_erp_staging
# Production
postgresql://gunei_prod_user:password@postgres-shared:5432/gunei_erp_production
Desde el host (conexión externa):
# Host: localhost
# Puerto: 5433 (mapeado)
psql -h localhost -p 5433 -U postgres
psql -h localhost -p 5433 -U gunei_staging_user -d gunei_erp_staging
psql -h localhost -p 5433 -U gunei_prod_user -d gunei_erp_production
Crear Nueva Database/Usuario
# Conectar como postgres admin
docker exec -it postgres-shared psql -U postgres
# Crear usuario
CREATE USER nuevo_usuario WITH PASSWORD 'password_seguro';
# Crear database
CREATE DATABASE nueva_database OWNER nuevo_usuario;
# Grants
GRANT ALL PRIVILEGES ON DATABASE nueva_database TO nuevo_usuario;
\c nueva_database
GRANT ALL ON SCHEMA public TO nuevo_usuario;
# Verificar
\l
\du
Backups de PostgreSQL Shared
# Backup de staging
docker exec postgres-shared pg_dump -U gunei_staging_user gunei_erp_staging > backup_staging.sql
# Backup de production
docker exec postgres-shared pg_dump -U gunei_prod_user gunei_erp_production > backup_production.sql
# Backup completo (todas las databases)
docker exec postgres-shared pg_dumpall -U postgres > backup_all.sql
✅ Verificación de Configuración
Verificación Completa por Ambiente
Script para verificar que todo está correctamente configurado:
#!/bin/bash
# /root/scripts/verify-config.sh
echo "🔍 Verificación de Configuración Multi-Ambiente"
echo "================================================"
echo ""
# Colores
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
check_pass() { echo -e "${GREEN}✅ $1${NC}"; }
check_fail() { echo -e "${RED}❌ $1${NC}"; }
check_warn() { echo -e "${YELLOW}⚠️ $1${NC}"; }
echo "🏗️ INFRAESTRUCTURA"
echo "-------------------"
# Red Docker
if docker network ls | grep -q gunei-network; then
check_pass "Red gunei-network existe"
else
check_fail "Red gunei-network NO existe"
fi
# PostgreSQL
if docker ps | grep -q postgres-shared; then
check_pass "PostgreSQL Shared corriendo"
# Verificar databases
if docker exec postgres-shared psql -U postgres -lqt | grep -q gunei_erp_staging; then
check_pass "Database gunei_erp_staging existe"
else
check_fail "Database gunei_erp_staging NO existe"
fi
if docker exec postgres-shared psql -U postgres -lqt | grep -q gunei_erp_production; then
check_pass "Database gunei_erp_production existe"
else
check_warn "Database gunei_erp_production no existe (normal si no está deployado)"
fi
else
check_fail "PostgreSQL Shared NO corriendo"
fi
# Caddy
if docker ps | grep -q caddy-shared; then
check_pass "Caddy Shared corriendo"
else
check_fail "Caddy Shared NO corriendo"
fi
echo ""
echo "🟢 STAGING"
echo "----------"
# Backend Staging
if docker ps | grep -q gunei-backend-staging; then
check_pass "Backend Staging corriendo"
# Verificar puerto
PORT=$(docker port gunei-backend-staging 3000 2>/dev/null | cut -d: -f2)
if [ "$PORT" = "3000" ]; then
check_pass "Backend Staging en puerto 3000"
else
check_fail "Backend Staging puerto incorrecto: $PORT"
fi
# Verificar DATABASE_URL
DB_URL=$(docker exec gunei-backend-staging printenv DATABASE_URL 2>/dev/null)
if echo "$DB_URL" | grep -q "gunei_erp_staging"; then
check_pass "Backend Staging apunta a DB staging"
else
check_fail "Backend Staging NO apunta a DB staging"
fi
else
check_warn "Backend Staging no corriendo"
fi
# Frontend Staging
if docker ps | grep -q gunei-frontend-staging; then
check_pass "Frontend Staging corriendo"
PORT=$(docker port gunei-frontend-staging 3001 2>/dev/null | cut -d: -f2)
if [ "$PORT" = "3001" ]; then
check_pass "Frontend Staging en puerto 3001"
else
check_fail "Frontend Staging puerto incorrecto: $PORT"
fi
else
check_warn "Frontend Staging no corriendo"
fi
echo ""
echo "🔵 PRODUCTION"
echo "-------------"
# Backend Production
if docker ps | grep -q gunei-backend-production; then
check_pass "Backend Production corriendo"
PORT=$(docker port gunei-backend-production 3000 2>/dev/null | cut -d: -f2)
if [ "$PORT" = "3100" ]; then
check_pass "Backend Production en puerto 3100"
else
check_fail "Backend Production puerto incorrecto: $PORT (esperado 3100)"
fi
DB_URL=$(docker exec gunei-backend-production printenv DATABASE_URL 2>/dev/null)
if echo "$DB_URL" | grep -q "gunei_erp_production"; then
check_pass "Backend Production apunta a DB production"
else
check_fail "Backend Production NO apunta a DB production"
fi
else
check_warn "Backend Production no deployado (normal si solo usas staging)"
fi
# Frontend Production
if docker ps | grep -q gunei-frontend-production; then
check_pass "Frontend Production corriendo"
PORT=$(docker port gunei-frontend-production 3001 2>/dev/null | cut -d: -f2)
if [ "$PORT" = "3101" ]; then
check_pass "Frontend Production en puerto 3101"
else
check_fail "Frontend Production puerto incorrecto: $PORT (esperado 3101)"
fi
else
check_warn "Frontend Production no deployado"
fi
echo ""
echo "🌐 CONECTIVIDAD"
echo "---------------"
# Health checks
if curl -sf http://localhost:3000/status > /dev/null 2>&1; then
check_pass "Backend Staging responde (localhost:3000)"
else
check_warn "Backend Staging no responde en localhost:3000"
fi
if curl -sf https://staging-erpback.gunei.xyz/status > /dev/null 2>&1; then
check_pass "Backend Staging responde (staging-erpback.gunei.xyz)"
else
check_warn "Backend Staging no responde en staging-erpback.gunei.xyz"
fi
if curl -sf https://staging-erpfront.gunei.xyz/health > /dev/null 2>&1; then
check_pass "Frontend Staging responde (staging-erpfront.gunei.xyz)"
else
check_warn "Frontend Staging no responde en staging-erpfront.gunei.xyz"
fi
echo ""
echo "📊 RESUMEN DE PUERTOS"
echo "---------------------"
docker ps --format "table {{.Names}}\t{{.Ports}}" | grep -E "gunei|postgres|caddy"
Verificación Rápida
# Verificar containers corriendo
docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}" | grep -E "gunei|postgres|caddy"
# Verificar red
docker network inspect gunei-network --format '{{range .Containers}}{{.Name}} {{end}}'
# Verificar puertos en uso
ss -tlnp | grep -E "3000|3001|3100|3101|5433"
# Verificar DATABASE_URL de cada backend
docker exec gunei-backend-staging printenv DATABASE_URL
docker exec gunei-backend-production printenv DATABASE_URL 2>/dev/null || echo "Production no deployado"
🐳 Docker Compose Configuration
Timezone Configuration
Todos los contenedores usan timezone Argentina:
environment:
- TZ=America/Argentina/Buenos_Aires
Logging Configuration
Logging JSON con rotación automática (evita llenar disco):
logging:
driver: "json-file"
options:
max-size: "10m" # Máximo 10MB por archivo
max-file: "3" # Máximo 3 archivos
tag: "{{.Name}}/{{.ID}}"
Image Pull Policy
pull_policy: always # Siempre pull la última versión en deploy
Health Check Configuration
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/status"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
Dependencies
depends_on:
postgres:
condition: service_healthy
Frontend Environment Variables
Staging:
environment:
- NODE_ENV=staging
- API_BASE_URL=https://staging-erpback.gunei.xyz
- BASE_URL=https://staging-erpback.gunei.xyz
- PUBLIC_BASE_URL=https://staging-erpback.gunei.xyz
- PORT=3001
- ORIGIN=https://staging-erpfront.gunei.xyz
- TZ=America/Argentina/Buenos_Aires
Production:
environment:
- NODE_ENV=production
- API_BASE_URL=https://erpback.gunei.xyz
- BASE_URL=https://erpback.gunei.xyz
- PUBLIC_BASE_URL=https://erpback.gunei.xyz
- PORT=3001
- ORIGIN=https://erpfront.gunei.xyz
- TZ=America/Argentina/Buenos_Aires
Verificaciones Post-Deployment
# Verificar timezone
docker exec gunei-backend-staging date
docker exec gunei-frontend-staging date
# Verificar logging config
docker inspect gunei-backend-staging | grep -A 10 LogConfig
# Verificar healthcheck status
docker inspect gunei-backend-staging --format='{{json .State.Health}}' | jq
# Verificar variables de entorno del frontend
docker exec gunei-frontend-staging env | grep -E "API_BASE_URL|ORIGIN|TZ"
# Verificar variables de entorno del backend
docker exec gunei-backend-staging env | grep -E "DATABASE_URL|NODE_ENV|TZ"
Checklist Pre-Deployment
Antes de deployar a un ambiente, verificar:
Infraestructura:
- [ ]
gunei-networkexiste - [ ]
postgres-sharedcorriendo - [ ]
caddy-sharedcorriendo - [ ] Databases creadas
Staging:
- [ ] Directorio
/opt/apps/gunei-erp/backend/stagingexiste - [ ]
.envconfigurado congunei_erp_staging - [ ]
docker-compose.ymlcon puerto 3000 - [ ] Directorio frontend staging existe
Production:
- [ ] Directorio
/opt/apps/gunei-erp/backend/productionexiste - [ ]
.envconfigurado congunei_erp_production - [ ]
docker-compose.ymlcon puerto 3100 (externo) - [ ] URLs de Caddy configuradas
🚀 Deployment por Ambiente
Staging Environment
Primer Deployment (Staging)
Backend Staging:
ssh root@gunei.xyz
cd /opt/apps/gunei-erp/backend/staging
# Login en GHCR (si es necesario)
echo $GHCR_TOKEN | docker login ghcr.io -u gunei-dev --password-stdin
# Pull de la imagen
docker pull ghcr.io/gunei-dev/gunei-erp-back:staging
# Iniciar backend
docker compose up -d backend
# Verificar
docker compose ps
docker compose logs -f backend
curl http://localhost:3000/status
curl https://staging-erpback.gunei.xyz/status
Frontend Staging:
cd /opt/apps/gunei-erp/frontend/staging
# Pull de la imagen
docker pull ghcr.io/gunei-dev/gunei-erp-front:develop
# Iniciar frontend
docker compose up -d frontend
# Verificar
docker compose ps
docker compose logs -f frontend
curl http://localhost:3001/health
curl https://staging-erpfront.gunei.xyz/health
Actualización Manual (Staging)
Backend:
cd /opt/apps/gunei-erp/backend/staging
# Pull nueva versión
docker compose pull backend
# Reiniciar (zero-downtime)
docker compose up -d backend
# Verificar logs
docker compose logs -f backend
Frontend:
cd /opt/apps/gunei-erp/frontend/staging
# Pull nueva versión
docker compose pull frontend
# Reiniciar (zero-downtime)
docker compose up -d frontend
# Verificar logs
docker compose logs -f frontend
Deployment Completo (Staging)
Para deployar tanto backend como frontend:
ssh root@gunei.xyz
# Backend primero (por dependencias)
cd /opt/apps/gunei-erp/backend/staging
docker compose pull backend
docker compose up -d backend
sleep 10
# Frontend después
cd /opt/apps/gunei-erp/frontend/staging
docker compose pull frontend
docker compose up -d frontend
sleep 5
# Verificar todo
/root/scripts/health-check.sh
Production Environment (Cuando esté activo)
Primer Deployment (Production)
Similar a staging pero en directorios production/:
Backend Production:
cd /opt/apps/gunei-erp/backend/production
docker compose pull backend
docker compose up -d backend
docker compose logs -f backend
curl http://localhost:3100/status
Frontend Production:
cd /opt/apps/gunei-erp/frontend/production
docker compose pull frontend
docker compose up -d frontend
docker compose logs -f frontend
curl http://localhost:3101/health
🤖 CI/CD Pipeline
Overview del Pipeline
Developer Push (develop/main)
↓
GitHub Actions Trigger
↓
Build Docker Image
↓
Push to GHCR
↓
SSH to VPS (ED25519 key)
↓
Ir al directorio del ambiente correcto
↓
docker compose pull + up -d
↓
Health Check
↓
Discord Notification
Ambientes en CI/CD
| Branch | Ambiente | Directorio Deploy | Container | URL |
|---|---|---|---|---|
develop | Staging | /opt/apps/gunei-erp/{service}/staging | gunei-{service}-staging | gunei.xyz |
main | Production | /opt/apps/gunei-erp/{service}/production | gunei-{service}-production | (pendiente) |
Backend CI/CD
Archivo: .github/workflows/deploy-staging.yml (para develop)
Trigger:
on:
push:
branches: [develop] # Deploy a staging
workflow_dispatch: # También manual desde GitHub UI
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/backend/staging
docker compose pull backend
docker compose up -d backend
sleep 10
docker compose ps
Health Check (actualizado):
- name: Health Check - Staging
run: |
max_attempts=30
attempt=0
while [ $attempt -lt $max_attempts ]; do
if curl -f https://staging-erpback.gunei.xyz/status; 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
Frontend CI/CD
Archivo: .github/workflows/deploy-staging.yml (para develop)
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/frontend/staging
docker compose pull frontend
docker compose up -d frontend
sleep 10
docker compose ps
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: URL de production
Componentes del Workflow
Build & Push:
- Docker Buildx para multi-platform
- Cache en GHCR para builds rápidos
- Tags:
staging/productiony{branch}-{sha}
Deploy:
- SSH al VPS (ED25519 key)
- Navegación al directorio correcto del ambiente
- Pull de nueva imagen
- Restart con zero-downtime
- Wait + health check
Notifications (Discord):
- 🚀 Deploy iniciado (naranja)
- ✅ Deploy exitoso (verde) - incluye stats de commit
- ⚠️ Health check falló (amarillo)
- ❌ Deploy falló (rojo)
Monitoring del Workflow
# Ver logs en tiempo real (staging)
ssh root@gunei.xyz
docker logs gunei-backend-staging -f
docker logs gunei-frontend-staging -f
# Ver último deployment (staging)
cd /opt/apps/gunei-erp/backend/staging
docker compose logs --tail=50 backend
# Ver último deployment (production)
cd /opt/apps/gunei-erp/backend/production
docker compose logs --tail=50 backend
🔄 Gestión de Ambientes
Estrategia de Ambientes
Development (Local) → Staging (VPS) → Production (VPS)
↓ ↓ ↓
Developer Laptop Pruebas/QA Usuarios Reales
Staging:
- Para testing y validación
- Deploy automático desde branch
develop - Puede tener downtime
- Database:
gunei_erp_staging - URLs: staging-erpfront.gunei.xyz, staging-erpback.gunei.xyz
Production:
- Para usuarios reales
- Deploy manual o automático desde branch
main - Zero-downtime deployments
- Database:
gunei_erp_production - URLs: (pendiente configurar)
Promover de Staging a Production
Workflow Recomendado
# 1. Validar que staging funciona correctamente
curl https://staging-erpfront.gunei.xyz/health
curl https://staging-erpback.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 si se requiere)
Migración de Data entre Ambientes
Copiar data de staging a production:
# 1. Crear backup de staging
docker exec postgres-shared pg_dump -U gunei_staging_user gunei_erp_staging > staging_snapshot.sql
# 2. Limpiar production (CUIDADO!)
docker exec postgres-shared psql -U postgres -c "DROP DATABASE IF EXISTS gunei_erp_production;"
docker exec postgres-shared psql -U postgres -c "CREATE DATABASE gunei_erp_production;"
docker exec postgres-shared psql -U postgres -c "GRANT ALL PRIVILEGES ON DATABASE gunei_erp_production TO gunei_prod_user;"
# 3. Restaurar snapshot en production
docker exec -i postgres-shared psql -U gunei_prod_user -d gunei_erp_production < staging_snapshot.sql
# 4. Verificar
docker exec postgres-shared psql -U gunei_prod_user -d gunei_erp_production -c "SELECT COUNT(*) FROM users;"
Rollback por Ambiente
Si un deployment falla, podés volver a una versión anterior del ambiente específico.
Rollback de Staging
# 1. Listar tags disponibles en GHCR
# https://github.com/orgs/Gunei-Dev/packages
# 2. Pull un tag específico
docker pull ghcr.io/gunei-dev/gunei-erp-back:develop-abc1234
# 3. Actualizar docker-compose.yml temporalmente
cd /opt/apps/gunei-erp/backend/staging
nano docker-compose.yml
# Cambiar: image: ghcr.io/gunei-dev/gunei-erp-back:develop-abc1234
# 4. Recrear contenedor
docker compose up -d backend
# 5. Verificar
curl https://staging-erpback.gunei.xyz/status
docker logs gunei-backend-staging --tail 20
Rollback de Production
Similar proceso pero en directorio production/ y con tags de production.
📊 Monitoreo
Health Checks Automáticos
# Script ejecutado cada 5 minutos vía cron
/root/scripts/health-check.sh
# Ver estado
tail -f /var/log/gunei-health.log
Endpoints Monitoreados
Staging
GET /health(Frontend): https://staging-erpfront.gunei.xyz/healthGET /status(Backend): https://staging-erpback.gunei.xyz/status- Database connectivity (staging)
Production (cuando esté activo)
GET /health(Frontend): (URL pendiente)GET /status(Backend): (URL pendiente)- Database connectivity (production)
Scripts Disponibles
/root/scripts/monitor-logs.sh # Ver logs de todos los servicios
/root/scripts/health-check.sh # Verificar salud del sistema (multi-ambiente)
/root/scripts/alert-check.sh # Check + alertas (cron)
/root/scripts/verify-config.sh # Verificar configuración multi-ambiente
Scripts actualizados para multi-ambiente - detectan automáticamente qué ambientes están corriendo.
Ver MONITORING.md para detalles completos.
Logs por Ambiente
Staging:
# Backend staging
docker logs gunei-backend-staging -f
docker logs gunei-backend-staging --tail 50
# Frontend staging
docker logs gunei-frontend-staging -f
docker logs gunei-frontend-staging --tail 50
Production:
# Backend production
docker logs gunei-backend-production -f
docker logs gunei-backend-production --tail 50
# Frontend production
docker logs gunei-frontend-production -f
docker logs gunei-frontend-production --tail 50
Infraestructura (compartida):
# PostgreSQL (afecta ambos ambientes)
docker logs postgres-shared -f
# Caddy (routing de ambos ambientes)
docker logs caddy-shared -f
# Logs de acceso de Caddy por ambiente
docker exec caddy-shared cat /var/log/caddy/staging-erpfront.log
docker exec caddy-shared cat /var/log/caddy/staging-erpback.log
docker exec caddy-shared cat /var/log/caddy/production-erpfront.log # cuando exista
docker exec caddy-shared cat /var/log/caddy/production-erpback.log # cuando exista
💾 Backups
Sistema Automático
# Schedule (crontab) - actualizado para PostgreSQL Shared
0 2 * * * /root/scripts/backup-postgres.sh # Backup diario 2 AM
0 3 * * * /root/scripts/verify-backup.sh # Verify 3 AM
0 4 * * 0 /root/scripts/cleanup-backups.sh # Cleanup domingos 4 AM
0 5 * * * /root/scripts/check-disk-space.sh # Disk check 5 AM
Scripts actualizados para PostgreSQL Shared - hacen backup de ambas databases (gunei_erp_staging y gunei_erp_production).
Backup Manual por Ambiente
Staging:
# Backup de staging
docker exec postgres-shared pg_dump -U gunei_staging_user gunei_erp_staging > backup_staging_$(date +%Y%m%d).sql
Production:
# Backup de production
docker exec postgres-shared pg_dump -U gunei_prod_user gunei_erp_production > backup_production_$(date +%Y%m%d).sql
Todas las databases:
# Script automatizado
/root/scripts/backup-postgres.sh
Restore por Ambiente
Staging:
/root/scripts/restore-backup.sh /var/backups/postgresql/backup_staging_YYYYMMDD.sql.gz gunei_erp_staging
Production:
/root/scripts/restore-backup.sh /var/backups/postgresql/backup_production_YYYYMMDD.sql.gz gunei_erp_production
Ver BACKUPS.md para detalles completos del sistema de backups actualizado.
🔧 Troubleshooting
Backend/Frontend No Responde (General)
Síntoma: Servicio no responde en staging o production
Diagnóstico:
# Verificar que el contenedor está corriendo
docker ps | grep gunei-backend-staging # o el servicio específico
# Ver logs
docker logs gunei-backend-staging --tail 100
# Verificar health
curl http://localhost:3000/status # staging backend
curl http://localhost:3100/status # production backend (si existe)
Solución:
# Reiniciar servicio específico
cd /opt/apps/gunei-erp/backend/staging
docker compose restart backend
# Si el problema persiste, rebuild
docker compose up -d --build backend
Backend No Responde (Staging)
# Ver logs
cd /opt/apps/gunei-erp/backend/staging
docker compose logs backend
# Reiniciar
docker compose restart backend
# Verificar variables de entorno
docker exec gunei-backend-staging env | grep DATABASE
# Test de conexión a PostgreSQL
docker exec gunei-backend-staging psql -h postgres-shared -p 5432 -U gunei_staging_user -d gunei_erp_staging -c "SELECT 1;"
Frontend No Responde (Staging)
# Ver logs
cd /opt/apps/gunei-erp/frontend/staging
docker compose logs frontend
# Reiniciar
docker compose restart frontend
# Verificar que Caddy puede alcanzarlo
docker exec caddy-shared wget -O- http://gunei-frontend-staging:3001/health
Error de Base de Datos (Multi-Ambiente)
Síntoma: Backend no puede conectarse a PostgreSQL
Diagnóstico:
# Verificar PostgreSQL está corriendo
docker ps | grep postgres-shared
# Ver logs de PostgreSQL
docker logs postgres-shared --tail 50
# Test de conexión desde backend staging
docker exec gunei-backend-staging pg_isready -h postgres-shared -p 5432 -U gunei_staging_user
# Test de conexión desde backend production (si existe)
docker exec gunei-backend-production pg_isready -h postgres-shared -p 5432 -U gunei_prod_user
# Verificar que las databases existen
docker exec postgres-shared psql -U postgres -c "\l" | grep gunei_erp
Solución:
# Si PostgreSQL no está corriendo
cd /opt/infrastructure/postgres
docker compose up -d
# Verificar credenciales en .env del ambiente
cat /opt/apps/gunei-erp/backend/staging/.env | grep DATABASE_URL
cat /opt/apps/gunei-erp/backend/production/.env | grep DATABASE_URL
# Formato esperado staging:
# DATABASE_URL=postgresql://gunei_staging_user:staging_password@postgres-shared:5432/gunei_erp_staging
# Formato esperado production:
# DATABASE_URL=postgresql://gunei_prod_user:production_password@postgres-shared:5432/gunei_erp_production
SSL Issues (Caddy)
Síntoma: HTTPS no funciona o certificado inválido
# Logs de Caddy
docker logs caddy-shared --tail 50 | grep -i "tls\|certificate"
# Verificar certificados
docker exec caddy-shared ls -la /data/caddy/certificates/
# Recargar config (sin downtime)
cd /opt/infrastructure/caddy
docker exec caddy-shared caddy reload --config /etc/caddy/Caddyfile
# Si persiste, reiniciar Caddy
docker restart caddy-shared
Deployment Fallido (CI/CD)
Síntoma: GitHub Actions workflow falla
Diagnóstico:
# Ver workflow en GitHub
# Actions → [workflow name] → [run]
# Verificar en VPS qué ambiente afectó
ssh root@gunei.xyz
# Staging
docker ps | grep staging
cd /opt/apps/gunei-erp/backend/staging
docker compose logs backend
# Production (si corresponde)
docker ps | grep production
cd /opt/apps/gunei-erp/backend/production
docker compose logs backend
Solución - Rollback Manual:
# Ver tags disponibles en GHCR
# https://github.com/orgs/Gunei-Dev/packages
# Staging
cd /opt/apps/gunei-erp/backend/staging
docker pull ghcr.io/gunei-dev/gunei-erp-back:develop-[previous-sha]
# Actualizar image tag en docker-compose.yml
docker compose up -d
# Production
cd /opt/apps/gunei-erp/backend/production
docker pull ghcr.io/gunei-dev/gunei-erp-back:main-[previous-sha]
# Actualizar image tag en docker-compose.yml
docker compose up -d
SSH Authentication Failed en GitHub Actions
Síntoma:
ssh: handshake failed: ssh: unable to authenticate, attempted methods [none publickey]
Causa: Clave SSH mal configurada o tipo de clave incompatible (RSA muy larga)
Solución 1: Verificar el secret
# En GitHub Secrets, VPS_SSH_KEY debe contener:
# - Clave COMPLETA (desde -----BEGIN hasta -----END)
# - Sin espacios extra al principio o final
# - Sin líneas vacías en el medio (excepto las del formato base64)
Solución 2: Usar ED25519 en vez de RSA (recomendado)
# En el VPS
ssh root@gunei.xyz
# Generar nueva clave ED25519
ssh-keygen -t ed25519 -C "github-actions-gunei" -f ~/.ssh/github-actions-new -N ""
# Agregar a authorized_keys
cat ~/.ssh/github-actions-new.pub >> ~/.ssh/authorized_keys
# Reemplazar la antigua
mv ~/.ssh/github-actions ~/.ssh/github-actions-rsa-old
mv ~/.ssh/github-actions-new ~/.ssh/github-actions
# Copiar la nueva clave privada
cat ~/.ssh/github-actions
Luego actualizar VPS_SSH_KEY en GitHub Secrets con la nueva clave.
Solución 3: Re-run del workflow
Después de actualizar el secret, hacer Re-run del workflow fallido en GitHub Actions.
Docker Image Pull Failed
Síntoma:
Error response from daemon: pull access denied
Solución:
# Re-login en GHCR
echo $GHCR_TOKEN | docker login ghcr.io -u gunei-dev --password-stdin
# Verificar permisos del token en GitHub
# Settings → Developer settings → Personal access tokens
# Debe tener: read:packages, write:packages
Health Check Timeout
Síntoma: Health check falla pero container está running
Solución:
# Verificar endpoint manualmente (staging)
curl http://localhost:3000/status
curl https://staging-erpback.gunei.xyz/status
# Verificar endpoint manualmente (production)
curl http://localhost:3100/status
# Si el endpoint es diferente, actualizar en:
# - workflow: .github/workflows/deploy-*.yml
# - docker-compose.yml: healthcheck section
# - /root/scripts/health-check.sh
Conflictos entre Staging y Production
Síntoma: Un ambiente funciona pero el otro no, o hay interferencias
Diagnóstico:
# Verificar puertos (deben ser diferentes)
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
Solución:
# Si hay conflicto de puertos, ajustar en docker-compose.yml
cd /opt/apps/gunei-erp/backend/production
nano docker-compose.yml
# Asegurar: ports: ["3100:3000"]
# Verificar que cada ambiente apunta a su database correcta
# Staging → gunei_erp_staging
# Production → gunei_erp_production
Disco Lleno
Síntoma:
no space left on device
Solución:
# Ver uso de disco
df -h
# Limpiar imágenes Docker antiguas
docker system prune -a --volumes
# Limpiar logs antiguos
journalctl --vacuum-time=7d
# Ejecutar cleanup de backups
/root/scripts/cleanup-backups.sh
# Ver qué está usando más espacio
du -sh /opt/* | sort -hr | head -10
du -sh /var/lib/docker/* | sort -hr | head -10
Servicios Externos
- VPS Provider: Contabo
- DNS: Porkbun
- Container Registry: GitHub Container Registry
Equipo
- Adrian Gallegos: Project Manager
- Miguel Culaciati: Frontend Developer
- Facundo Sardi: Backend Developer
- Mikle: DevOps
Last updated: 14 Enero 2026 Version: 2.1.0
Cambios en v2.1:
- ✅ Diagrama de arquitectura mejorado con staging y production
- ✅ Sección detallada de PostgreSQL Shared (puerto 5433, databases, usuarios)
- ✅ Sección de Verificación de Configuración con script completo
- ✅ Checklist pre-deployment por ambiente
- ✅ Conexiones desde containers vs host documentadas
- ✅ Docker Compose Configuration (timezone, logging, healthcheck, pull_policy)
- ✅ Frontend environment variables por ambiente
- ✅ Verificaciones post-deployment
Cambios en v2.0:
- ✅ Arquitectura enterprise multi-ambiente
- ✅ Estructura /opt/infrastructure + /opt/apps
- ✅ Servicios compartidos (caddy-shared, postgres-shared)
- ✅ Red Docker unificada (gunei-network)
- ✅ CI/CD actualizado para multi-ambiente