Skip to content
On this page

🚀 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

Comandos Más Usados

AcciónStagingProduction
Deploycd /opt/apps/gunei-erp/backend/staging && docker compose pull && docker compose up -dcd /opt/apps/gunei-erp/backend/production && docker compose pull && docker compose up -d
Logsdocker logs gunei-backend-staging -fdocker logs gunei-backend-production -f
Restartdocker compose restart backenddocker compose restart backend
Healthcurl https://staging-erpback.gunei.xyz/statuscurl https://erpback.gunei.xyz/status

Conexión PostgreSQL

bash
# 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

RecursoPath
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:

bash
docker exec gunei-backend-staging date

🗺️ Arquitectura Enterprise

Stack Tecnológico

ComponenteTecnologíaVersiónPuertos
RuntimeBunLatest-
Backend FrameworkHonoLatest3000 (staging), 3100 (prod)
Frontend FrameworkSvelteKit5.x3001 (staging), 3101 (prod)
DatabasePostgreSQL175433
Reverse ProxyCaddyLatest80/443
Container RuntimeDocker28.x-
OrchestrationDocker Compose2.x-
CI/CDGitHub Actions--
RegistryGHCR--

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)

Production (Preparado, No Deployado)

  • Frontend: (Pendiente configurar) → gunei-frontend-production:3101
  • Backend: (Pendiente configurar) → gunei-backend-production:3100
  • Database: gunei_erp_production en postgres-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:

ContenedorServicioPuerto InternoPuerto Externo
caddy-sharedReverse Proxy80, 44380, 443
postgres-sharedPostgreSQL 1754325433
gunei-frontend-stagingFrontend Staging30013001
gunei-backend-stagingBackend Staging30003000
gunei-frontend-productionFrontend Production30013101
gunei-backend-productionBackend Production30003100

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)

bash
# 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

bash
# 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

bash
# 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)

bash
# 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:

  1. Ir a: https://github.com/gunei-dev/gunei-erp-{back|front}/settings/secrets/actions
  2. Actualizar VPS_SSH_KEY con la nueva clave privada completa
  3. Asegurarse de copiar TODO (desde -----BEGIN OPENSSH PRIVATE KEY----- hasta -----END OPENSSH PRIVATE KEY-----)
  4. NO agregar espacios o líneas extra al principio o final
  5. Re-run el último workflow para probar

Verificar configuración SSH

bash
# 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

bash
# 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

bash
# 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.xyzgunei-frontend-staging:3001
  • staging-erpback.gunei.xyzgunei-backend-staging:3000

1.3 Red Docker

bash
# Crear red compartida (si no existe)
docker network create gunei-network

# Verificar
docker network inspect gunei-network

2. Configurar Backend Staging

bash
# 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

bash
# 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:

bash
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:

bash
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ámetroValor
Containerpostgres-shared
Imagenpostgres:17
Puerto Externo5433
Puerto Interno5432
Redgunei-network
Data Volume/opt/infrastructure/postgres/data

Databases y Usuarios

DatabaseUsuarioAmbienteUso
gunei_erp_staginggunei_staging_userStagingTesting/QA
gunei_erp_productiongunei_prod_userProductionUsuarios reales

Conexión desde Aplicaciones

Desde containers (red Docker interna):

bash
# 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):

bash
# 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

bash
# 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

bash
# 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:

bash
#!/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

bash
# 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:

yaml
environment:
  - TZ=America/Argentina/Buenos_Aires

Logging Configuration

Logging JSON con rotación automática (evita llenar disco):

yaml
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

yaml
pull_policy: always  # Siempre pull la última versión en deploy

Health Check Configuration

yaml
healthcheck:
  test: ["CMD", "curl", "-f", "http://localhost:3000/status"]
  interval: 30s
  timeout: 10s
  retries: 3
  start_period: 40s

Dependencies

yaml
depends_on:
  postgres:
    condition: service_healthy

Frontend Environment Variables

Staging:

yaml
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:

yaml
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

bash
# 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-network existe
  • [ ] postgres-shared corriendo
  • [ ] caddy-shared corriendo
  • [ ] Databases creadas

Staging:

  • [ ] Directorio /opt/apps/gunei-erp/backend/staging existe
  • [ ] .env configurado con gunei_erp_staging
  • [ ] docker-compose.yml con puerto 3000
  • [ ] Directorio frontend staging existe

Production:

  • [ ] Directorio /opt/apps/gunei-erp/backend/production existe
  • [ ] .env configurado con gunei_erp_production
  • [ ] docker-compose.yml con puerto 3100 (externo)
  • [ ] URLs de Caddy configuradas

🚀 Deployment por Ambiente

Staging Environment

Primer Deployment (Staging)

Backend Staging:

bash
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:

bash
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:

bash
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:

bash
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:

bash
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:

bash
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:

bash
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

BranchAmbienteDirectorio DeployContainerURL
developStaging/opt/apps/gunei-erp/{service}/staginggunei-{service}-staginggunei.xyz
mainProduction/opt/apps/gunei-erp/{service}/productiongunei-{service}-production(pendiente)

Backend CI/CD

Archivo: .github/workflows/deploy-staging.yml (para develop)

Trigger:

yaml
on:
  push:
    branches: [develop]  # Deploy a staging
  workflow_dispatch:     # También manual desde GitHub UI

Deploy Step (actualizado):

yaml
- 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):

yaml
- 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:

yaml
- 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)

yaml
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/production y {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

bash
# 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

bash
# 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:

bash
# 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

bash
# 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

bash
# 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

Production (cuando esté activo)

  • GET /health (Frontend): (URL pendiente)
  • GET /status (Backend): (URL pendiente)
  • Database connectivity (production)

Scripts Disponibles

bash
/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:

bash
# 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:

bash
# 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):

bash
# 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

bash
# 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:

bash
# Backup de staging
docker exec postgres-shared pg_dump -U gunei_staging_user gunei_erp_staging > backup_staging_$(date +%Y%m%d).sql

Production:

bash
# 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:

bash
# Script automatizado
/root/scripts/backup-postgres.sh

Restore por Ambiente

Staging:

bash
/root/scripts/restore-backup.sh /var/backups/postgresql/backup_staging_YYYYMMDD.sql.gz gunei_erp_staging

Production:

bash
/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:

bash
# 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:

bash
# 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)

bash
# 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)

bash
# 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:

bash
# 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:

bash
# 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

bash
# 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:

bash
# 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:

bash
# 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

bash
# 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)

bash
# 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:

bash
# 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:

bash
# 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:

bash
# 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:

bash
# 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:

bash
# 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

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