⬡
Topología del Clúster
machine-1
Frontend + Proxy
Madrid · 4 vCPU / 8 GB RAM
IP pública: 91.216.48.10 · WireGuard: 10.210.0.1
IP pública: 91.216.48.10 · WireGuard: 10.210.0.1
caddy
cultivaia-web
n8n
redis
machine-2
API + Workers
Frankfurt · 4 vCPU / 8 GB RAM
IP pública: 5.161.102.30 · WireGuard: 10.210.0.2
IP pública: 5.161.102.30 · WireGuard: 10.210.0.2
caddy
cultivaia-api
cultivaia-app
redis
db-machine
Solo DB
Madrid · 2 vCPU / 4 GB RAM
Sin IP pública · WireGuard: 10.210.0.3
Sin IP pública · WireGuard: 10.210.0.3
postgres
redis
WireGuard Mesh
Los 3 nodos se comunican cifrados en la red overlay 10.210.0.0/16. Postgres y Redis solo escuchan en la red interna — ningún puerto expuesto a Internet.
⚡
Bootstrapping del Clúster
1
Inicializar primera máquina
Crea el clúster y configura WireGuard + Caddy
2
Unir máquinas restantes
Incorpora Frankfurt y db-machine al mesh
3
Deploy inicial
Sube imágenes y despliega todos los servicios
setup.sh
bash
# 1. Bootstrapear clúster en machine-1 (Madrid)
uc machine init root@91.216.48.10 \
--name machine-1 \
--network 10.210.0.0/16 \
--public-ip auto
# 2. Añadir Frankfurt
uc machine add root@5.161.102.30 \
--name machine-2 \
--public-ip auto
# 3. DB-machine sin IP pública ni Caddy
uc machine add root@10.10.0.5 \
--name db-machine \
--public-ip none \
--no-caddy
# Verificar topología
uc machine ls
⚙
DNS — Wildcard cultivaia.com
cultivaia.com
A
91.216.48.10
*.cultivaia.com
A
91.216.48.10
app.cultivaia.com
CNAME
cultivaia.com
api.cultivaia.com
CNAME
cultivaia.com
n8n.cultivaia.com
CNAME
cultivaia.com
Con el wildcard
*.cultivaia.com → 91.216.48.10, cada nuevo subdominio funciona sin cambiar DNS. Caddy obtiene TLS de Let's Encrypt automáticamente.
⬛
compose.yaml — Despliegue completo CULTIVA IA
compose.yaml
yaml
# CULTIVA IA — compose.yaml para Uncloud
services:
cultivaia-web:
build: ./apps/web
image: ghcr.io/cultivaia/web:{{gitsha 7}}.{{gitdate "20060102"}}
x-ports:
- cultivaia.com:4321/https
- www.cultivaia.com:4321/https
x-machines: machine-1
environment:
API_URL: http://cultivaia-api:3000
cultivaia-api:
build: ./apps/api
image: ghcr.io/cultivaia/api:{{gitsha 7}}.{{gitdate "20060102"}}
x-ports:
- api.cultivaia.com:3000/https
x-machines:
- machine-2
environment:
DATABASE_URL: postgres://cultivaia:${DB_PASS}@postgres:5432/cultivaia_prod
REDIS_URL: redis://redis:6379
NODE_ENV: production
deploy:
replicas: 2
cultivaia-app:
build: ./apps/admin
image: ghcr.io/cultivaia/app:{{gitsha 7}}.{{gitdate "20060102"}}
x-ports:
- app.cultivaia.com:3001/https
x-machines:
- machine-2
environment:
NEXT_PUBLIC_API_URL: https://api.cultivaia.com
NEXTAUTH_URL: https://app.cultivaia.com
NEXTAUTH_SECRET: ${NEXTAUTH_SECRET}
n8n:
image: n8nio/n8n:latest
x-caddy: |
n8n.cultivaia.com {
reverse_proxy {{upstreams 5678}} {
import common_proxy
}
basicauth /webhook/* {
webhook $2a$14$hashed_pass_here
}
}
x-machines: machine-1
volumes:
- n8n-data:/home/node/.n8n
environment:
DB_TYPE: postgresdb
DB_POSTGRESDB_HOST: postgres
DB_POSTGRESDB_DATABASE: n8n
DB_POSTGRESDB_PASSWORD: ${DB_PASS}
postgres:
image: postgres:16
x-machines: db-machine # Pinado en máquina dedicada
volumes:
- pg-data:/var/lib/postgresql/data
environment:
POSTGRES_USER: cultivaia
POSTGRES_PASSWORD: ${DB_PASS}
POSTGRES_MULTIPLE_DATABASES: cultivaia_prod,n8n
# Sin x-ports: DB solo accesible vía overlay WireGuard
redis:
image: redis:7-alpine
x-machines: db-machine
volumes:
- redis-data:/data
# Sin x-ports: Redis solo accesible vía overlay WireGuard
volumes:
pg-data:
redis-data:
n8n-data:
⬤
Servicios desplegados
| Servicio | URL pública | DNS interno | Estado |
|---|---|---|---|
| cultivaia-web | cultivaia.com | cultivaia-web | Running |
| cultivaia-api | api.cultivaia.com | cultivaia-api | ×2 Running |
| cultivaia-app | app.cultivaia.com | cultivaia-app | Running |
| n8n | n8n.cultivaia.com | n8n | Running |
| postgres | — interno — | postgres | db-machine |
| redis | — interno — | redis | db-machine |
| caddy | global | caddy | Global |
⚡
CI/CD — GitHub Actions
git push
main
→
build
Docker
→
uc build
--push
--push
push images
→
uc deploy
--no-build
--no-build
zero-downtime
.github/workflows/deploy.yml
yaml
name: Deploy CULTIVA IA
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install uc CLI
run: |
curl -sSL https://get.uncloud.run | sh
- name: Build & Push images
run: uc build --push
env:
UC_CLUSTER_TOKEN: ${{ secrets.UC_TOKEN }}
- name: Deploy (zero-downtime)
run: uc deploy --no-build
env:
UC_CLUSTER_TOKEN: ${{ secrets.UC_TOKEN }}
DB_PASS: ${{ secrets.DB_PASS }}
NEXTAUTH_SECRET: ${{ secrets.NEXTAUTH_SECRET }}
⚙
Comandos Operacionales
ops-cheatsheet.sh
bash
# ── ESTADO DEL CLUSTER ──────────────────────────
uc machine ls # Listar máquinas y estado WG
uc service ls # Servicios y réplicas
uc ps # Todos los contenedores del cluster
uc caddy config # Ver Caddyfile generado actual
# ── LOGS Y DEBUG ────────────────────────────────
uc logs -f cultivaia-api
uc logs --since 1h n8n
uc service inspect postgres
uc exec cultivaia-api # Abrir shell
uc exec postgres psql -U cultivaia cultivaia_prod
# ── ESCALADO ────────────────────────────────────
uc scale cultivaia-api 4 # Escalar a 4 réplicas
uc scale cultivaia-api 2 # Reducir
# ── ROLLBACK ────────────────────────────────────
uc deploy --recreate # Forzar recreación
# ── VOLÚMENES ───────────────────────────────────
uc volume ls
uc volume ls -m db-machine
# ── CONTEXTOS ───────────────────────────────────
uc ctx ls
uc ctx use prod
⚠
Errores Frecuentes y Soluciones
| Error | Solución |
|---|---|
| Editar Caddyfile directamente | Usar x-caddy en compose o --caddyfile en uc service run |
| Proxy a HTTPS upstream con cert self-signed (ej. BMC) | Añadir transport http { tls_insecure_skip_verify } en el bloque Caddy |
| uc caddy config sin bloques de usuario | Socket admin de Caddy no alcanzable — revisar uc inspect caddy y uc logs caddy |
| Contenedor no alcanza IP LAN externa | Verificar que el host de Caddy tiene ruta de red al target (iptables/routing del VPS) |
| Volúmenes perdidos tras uc service rm | Los volúmenes nombrados persisten. Solo los anónimos se borran automáticamente |
| deploy falla: imágenes no encontradas | Usar uc deploy (build+push+deploy) en lugar de --no-build en primer deploy |
⬡
Exponer dispositivos externos vía Caddy (NAS, Panel Admin VPS)
Para exponer el panel web del proveedor de VPS (IP LAN interna) sin levantar un contenedor real, se usa un contenedor
pause como ancla del servicio con Caddyfile personalizado.
nas.caddyfile + comando uc
bash
# 1. Crear el snippet Caddyfile para el NAS interno
cat > ~/nas.caddyfile <<'EOF'
https://nas.cultivaia.com {
reverse_proxy https://192.168.1.100 {
transport http {
tls_insecure_skip_verify # cert self-signed del NAS
}
}
log
}
EOF
# 2. Registrar como servicio "pause" en Uncloud
uc service run \
--name nas-proxy \
--caddyfile ~/nas.caddyfile \
--machines machine-1 \
registry.k8s.io/pause:3.9
# 3. Verificar que el bloque aparece en Caddy
uc caddy config | grep nas.cultivaia.com