384 lines
12 KiB
Markdown
384 lines
12 KiB
Markdown
# INSTALL_SUPABASE.md — Supabase self-hosted en Dokploy v0.29.4
|
|
|
|
> Guía operativa basada en la instalación real del proyecto "Datos País sobre el Cuidado" (junio 2026).
|
|
> Registra los pasos exactos, errores conocidos y sus soluciones.
|
|
|
|
---
|
|
|
|
## Decisiones de arquitectura
|
|
|
|
- **Supabase self-hosted completo** (Postgres + Auth + Storage + Realtime + Studio + Kong).
|
|
- **Partida limpia** — no se migran datos desde Cloud, se aplican las migraciones del repo.
|
|
- **Gestión vía Git** — el compose y los volumes viven en un repo dedicado; Dokploy clona ese repo.
|
|
Las variables de entorno viven en Dokploy (no en el repo).
|
|
- **Backups locales** diarios, retención 30 días. Remoto: TECH-DEBT-006.
|
|
- **Studio** no expuesto a internet. Acceso solo vía SSH tunnel.
|
|
|
|
---
|
|
|
|
## Requisitos previos
|
|
|
|
- VPS Ubuntu 22.04+ con Docker 29+ instalado.
|
|
- Dokploy v0.29.4 instalado y accesible.
|
|
- Repo Git disponible (Gitea o GitHub) con acceso desde el VPS y desde Dokploy.
|
|
- Acceso SSH al VPS.
|
|
- Supabase CLI en la máquina de desarrollo local.
|
|
|
|
---
|
|
|
|
## PARTE 1 — Preparar el repo de Supabase
|
|
|
|
### 1.1 Descargar el stack oficial en el VPS
|
|
|
|
```bash
|
|
curl -fsSL https://supabase.link/setup.sh | sh
|
|
```
|
|
|
|
Crea `~/supabase-project/` con esta estructura:
|
|
|
|
```
|
|
~/supabase-project/
|
|
├── docker-compose.yml
|
|
├── volumes/
|
|
│ ├── api/
|
|
│ │ ├── kong.yml <- API gateway (EDITAR: ver 1.2)
|
|
│ │ └── kong-entrypoint.sh
|
|
│ ├── db/
|
|
│ │ ├── _supabase.sql <- CRITICO: crea la base _supabase
|
|
│ │ ├── roles.sql
|
|
│ │ ├── jwt.sql
|
|
│ │ ├── realtime.sql
|
|
│ │ ├── pooler.sql
|
|
│ │ ├── webhooks.sql
|
|
│ │ ├── logs.sql
|
|
│ │ └── init/data.sql
|
|
│ └── pooler/
|
|
│ └── pooler.exs
|
|
```
|
|
|
|
> Estos archivos son los scripts de inicializacion que Postgres ejecuta la primera vez.
|
|
> Sin ellos la base _supabase no se crea y el stack no funciona.
|
|
> Por eso NO se puede usar "docker raw" en Dokploy — ese modo no tiene contexto del directorio.
|
|
|
|
### 1.2 Ajustar kong.yml
|
|
|
|
El kong.yml define las credenciales del API gateway. Verificar que use los mismos
|
|
nombres de variables que se definen en Dokploy:
|
|
|
|
```bash
|
|
grep -n "keyauth" ~/supabase-project/volumes/api/kong.yml
|
|
```
|
|
|
|
Deben quedar dos bloques con UNA sola entrada cada uno:
|
|
|
|
```yaml
|
|
keyauth_credentials:
|
|
- key: $ANON_KEY # nombre exacto de la variable en Dokploy
|
|
|
|
keyauth_credentials:
|
|
- key: $SERVICE_ROLE_KEY # nombre exacto de la variable en Dokploy
|
|
```
|
|
|
|
ERROR comun: si los dos bloques resuelven al mismo valor JWT, Kong falla con
|
|
"uniqueness violation". Verificar que ANON_KEY y SERVICE_ROLE_KEY son distintos.
|
|
|
|
### 1.3 Crear .gitignore
|
|
|
|
```bash
|
|
cat > ~/supabase-project/.gitignore << 'EOF'
|
|
.env
|
|
.env.local
|
|
volumes/db/data/
|
|
volumes/storage/
|
|
volumes/logs/
|
|
*.log
|
|
EOF
|
|
```
|
|
|
|
### 1.4 Push al repo Git
|
|
|
|
```bash
|
|
cd ~/supabase-project
|
|
git init
|
|
git add docker-compose.yml volumes/api/ volumes/db/ volumes/pooler/ .gitignore
|
|
git commit -m "chore: supabase self-hosted stack inicial"
|
|
git remote add origin https://TU_GIT/usuario/supabase-repo.git
|
|
git push -u origin master
|
|
```
|
|
|
|
El .env con los secrets NO se sube (esta en .gitignore).
|
|
|
|
---
|
|
|
|
## PARTE 2 — Generar los secrets
|
|
|
|
Usar siempre openssl rand -hex (solo hex, sin caracteres especiales que rompan el escaping de Docker).
|
|
|
|
### 2.1 Valores base (en el VPS)
|
|
|
|
```bash
|
|
echo "POSTGRES_PASSWORD=$(openssl rand -hex 24)"
|
|
echo "JWT_SECRET=$(openssl rand -hex 40)"
|
|
echo "DASHBOARD_PASSWORD=$(openssl rand -hex 16)"
|
|
echo "SECRET_KEY_BASE=$(openssl rand -hex 40)"
|
|
echo "VAULT_ENC_KEY=$(openssl rand -hex 20)"
|
|
echo "PG_META_CRYPTO_KEY=$(openssl rand -hex 24)"
|
|
```
|
|
|
|
Guardar todos en un gestor de contrasenas antes de continuar.
|
|
|
|
### 2.2 Generar ANON_KEY y SERVICE_ROLE_KEY
|
|
|
|
Con el JWT_SECRET generado, usar Node.js en el VPS:
|
|
|
|
```bash
|
|
node -e "
|
|
const crypto = require('crypto');
|
|
const secret = 'PEGAR_AQUI_JWT_SECRET';
|
|
|
|
function b64url(obj) {
|
|
return Buffer.from(JSON.stringify(obj)).toString('base64url');
|
|
}
|
|
|
|
function makeJWT(role) {
|
|
const h = b64url({alg:'HS256',typ:'JWT'});
|
|
const p = b64url({role,iss:'supabase',iat:1700000000,exp:2000000000});
|
|
const sig = crypto.createHmac('sha256',secret).update(h+'.'+p).digest('base64url');
|
|
return h+'.'+p+'.'+sig;
|
|
}
|
|
|
|
console.log('ANON_KEY='+makeJWT('anon'));
|
|
console.log('SERVICE_ROLE_KEY='+makeJWT('service_role'));
|
|
"
|
|
```
|
|
|
|
---
|
|
|
|
## PARTE 3 — Configurar el servicio en Dokploy
|
|
|
|
### 3.1 Crear el servicio
|
|
|
|
1. Projects -> seleccionar proyecto -> Create Service -> Docker Compose.
|
|
2. Provider: Gitea (o GitHub) -> seleccionar el repo del paso 1.4.
|
|
3. Branch: master | Compose Path: ./docker-compose.yml
|
|
4. IMPORTANTE: desactivar Autodeploy (toggle OFF).
|
|
|
|
### 3.2 Variables de entorno (pestaña Environment)
|
|
|
|
Pegar el bloque completo reemplazando los valores:
|
|
|
|
```env
|
|
POSTGRES_PASSWORD=<openssl rand -hex 24>
|
|
POSTGRES_HOST=db
|
|
POSTGRES_DB=postgres
|
|
POSTGRES_PORT=5432
|
|
JWT_SECRET=<openssl rand -hex 40>
|
|
JWT_EXPIRY=3600
|
|
ANON_KEY=<JWT anon>
|
|
SERVICE_ROLE_KEY=<JWT service_role>
|
|
SUPABASE_ANON_KEY=<igual a ANON_KEY>
|
|
SUPABASE_SERVICE_KEY=<igual a SERVICE_ROLE_KEY>
|
|
DASHBOARD_USERNAME=admin
|
|
DASHBOARD_PASSWORD=<openssl rand -hex 16>
|
|
KONG_HTTP_PORT=8000
|
|
KONG_HTTPS_PORT=8443
|
|
SECRET_KEY_BASE=<openssl rand -hex 40>
|
|
VAULT_ENC_KEY=<openssl rand -hex 20>
|
|
PG_META_CRYPTO_KEY=<openssl rand -hex 24>
|
|
SITE_URL=http://localhost:3000
|
|
API_EXTERNAL_URL=http://localhost:8000
|
|
SUPABASE_PUBLIC_URL=http://localhost:8000
|
|
STUDIO_DEFAULT_ORGANIZATION=NombreOrg
|
|
STUDIO_DEFAULT_PROJECT=NombreProyecto
|
|
SMTP_ADMIN_EMAIL=admin@example.com
|
|
SMTP_HOST=
|
|
SMTP_PORT=587
|
|
SMTP_USER=
|
|
SMTP_PASS=
|
|
SMTP_SENDER_NAME=
|
|
ENABLE_EMAIL_SIGNUP=false
|
|
ENABLE_EMAIL_AUTOCONFIRM=false
|
|
ENABLE_PHONE_SIGNUP=false
|
|
ENABLE_PHONE_AUTOCONFIRM=false
|
|
DISABLE_SIGNUP=true
|
|
ENABLE_ANONYMOUS_USERS=false
|
|
GOTRUE_EXTERNAL_ANONYMOUS_USERS_ENABLED=false
|
|
MAILER_URLPATHS_CONFIRMATION=/auth/v1/verify
|
|
MAILER_URLPATHS_INVITE=/auth/v1/verify
|
|
MAILER_URLPATHS_RECOVERY=/auth/v1/verify
|
|
MAILER_URLPATHS_EMAIL_CHANGE=/auth/v1/verify
|
|
ADDITIONAL_REDIRECT_URLS=
|
|
POOLER_TENANT_ID=pooler
|
|
POOLER_DEFAULT_POOL_SIZE=20
|
|
POOLER_MAX_CLIENT_CONN=100
|
|
POOLER_PROXY_PORT_TRANSACTION=6543
|
|
POOLER_DB_POOL_SIZE=10
|
|
STORAGE_TENANT_ID=default
|
|
GLOBAL_S3_BUCKET=
|
|
REGION=
|
|
S3_PROTOCOL_ACCESS_KEY_ID=
|
|
S3_PROTOCOL_ACCESS_KEY_SECRET=
|
|
LOGFLARE_LOGGER_BACKEND_API_KEY=logflare-local-key
|
|
LOGFLARE_API_KEY=logflare-local-key
|
|
FUNCTIONS_VERIFY_JWT=true
|
|
IMGPROXY_ENABLE_WEBP_DETECTION=true
|
|
IMGPROXY_AUTO_WEBP=true
|
|
PGRST_DB_SCHEMAS=public,storage,graphql_public
|
|
```
|
|
|
|
### 3.3 Deploy y validacion
|
|
|
|
Click en Deploy. Cuando termine verificar:
|
|
|
|
```bash
|
|
docker ps --format "table {{.Names}}\t{{.Status}}" | grep supabase
|
|
|
|
docker exec supabase-db psql -U postgres -c 'SELECT version();'
|
|
|
|
curl -s http://localhost:8000/rest/v1/ -H "apikey: <ANON_KEY>" | head -c 200
|
|
|
|
docker exec supabase-db psql -U postgres -c '\l' | grep supabase
|
|
```
|
|
|
|
Contenedores esperados healthy: supabase-db, supabase-kong, supabase-studio,
|
|
supabase-auth, supabase-rest, supabase-storage, supabase-meta, supabase-pooler,
|
|
supabase-imgproxy, realtime-dev.supabase-realtime.
|
|
supabase-edge-functions puede quedar en Restarting si no se usa Edge Functions (no critico).
|
|
|
|
---
|
|
|
|
## PARTE 4 — Aplicar las migraciones del proyecto
|
|
|
|
El puerto 5432 no esta expuesto externamente. Las migraciones se aplican desde el VPS.
|
|
|
|
### 4.1 Copiar migraciones al VPS (desde la maquina de desarrollo)
|
|
|
|
```bash
|
|
ssh administrator@<VPS_IP> "mkdir -p ~/migrations"
|
|
scp -r supabase/migrations/ administrator@<VPS_IP>:~/migrations/
|
|
```
|
|
|
|
### 4.2 Aplicar en orden
|
|
|
|
```bash
|
|
for f in $(ls ~/migrations/*.sql | sort); do
|
|
echo "Aplicando: $f"
|
|
docker exec -i supabase-db psql -U postgres -d postgres < "$f"
|
|
echo "---"
|
|
done
|
|
```
|
|
|
|
### 4.3 Verificar
|
|
|
|
```bash
|
|
docker exec supabase-db psql -U postgres -d postgres -c \
|
|
"SELECT tablename, policyname FROM pg_policies WHERE schemaname='public' ORDER BY tablename;"
|
|
```
|
|
|
|
### Nota: auth.jwt() en GoTrue v2.189+
|
|
|
|
GoTrue v2.189+ elimino auth.jwt(). Las funciones disponibles son auth.uid(), auth.role().
|
|
Las politicas que usen custom claims JWT (app_role, org_id) fallaran. Ver TECH-DEBT-007.
|
|
No es bloqueante para el piloto porque el dashboard usa service_role que bypasa RLS.
|
|
|
|
---
|
|
|
|
## PARTE 5 — Backups locales
|
|
|
|
### 5.1 Crear el script
|
|
|
|
```bash
|
|
sudo mkdir -p /var/backups/supabase-prod
|
|
sudo chmod 700 /var/backups/supabase-prod
|
|
|
|
sudo tee /usr/local/bin/supabase-backup.sh > /dev/null << 'SCRIPT'
|
|
#!/usr/bin/bash
|
|
set -euo pipefail
|
|
TS=$(date +%Y%m%d_%H%M%S)
|
|
OUT=/var/backups/supabase-prod/supabase_$TS.sql.gz
|
|
docker exec supabase-db pg_dump -U postgres postgres | gzip > "$OUT"
|
|
chmod 600 "$OUT"
|
|
find /var/backups/supabase-prod -name "supabase_*.sql.gz" -mtime +30 -delete
|
|
echo "Backup OK: $OUT ($(du -h "$OUT" | cut -f1))"
|
|
SCRIPT
|
|
|
|
sudo chmod +x /usr/local/bin/supabase-backup.sh
|
|
```
|
|
|
|
### 5.2 Probar y configurar cron
|
|
|
|
```bash
|
|
sudo bash /usr/local/bin/supabase-backup.sh
|
|
|
|
sudo crontab -e
|
|
# Agregar: 0 3 * * * /usr/bin/bash /usr/local/bin/supabase-backup.sh >> /var/log/supabase-backup.log 2>&1
|
|
```
|
|
|
|
### 5.3 Probar restore (obligatorio)
|
|
|
|
```bash
|
|
docker exec supabase-db createdb -U postgres restore_test
|
|
gunzip < $(ls /var/backups/supabase-prod/*.sql.gz | tail -1) | \
|
|
docker exec -i supabase-db psql -U postgres restore_test
|
|
docker exec supabase-db psql -U postgres -d restore_test -c \
|
|
"SELECT count(*) FROM pg_tables WHERE schemaname='public';"
|
|
docker exec supabase-db dropdb -U postgres restore_test
|
|
```
|
|
|
|
---
|
|
|
|
## PARTE 6 — Dominio y SSL (pendiente)
|
|
|
|
En Dokploy, pestaña Domains del servicio supabase:
|
|
- Agregar dominio db.dominio.com -> apuntar a supabase-kong:8000 -> activar SSL.
|
|
- Studio NO tiene dominio publico. Acceso solo via SSH tunnel:
|
|
ssh -L 3001:localhost:3001 administrator@VPS_IP -> abrir http://localhost:3001
|
|
|
|
Actualizar variables de entorno:
|
|
```
|
|
API_EXTERNAL_URL=https://db.dominio.com
|
|
SUPABASE_PUBLIC_URL=https://db.dominio.com
|
|
```
|
|
|
|
---
|
|
|
|
## PARTE 7 — Conectar la app Next.js
|
|
|
|
Actualizar variables de entorno de la app Next.js en Dokploy:
|
|
```
|
|
NEXT_PUBLIC_SUPABASE_URL=https://db.dominio.com
|
|
NEXT_PUBLIC_SUPABASE_ANON_KEY=<ANON_KEY>
|
|
SUPABASE_SERVICE_ROLE_KEY=<SERVICE_ROLE_KEY>
|
|
```
|
|
|
|
---
|
|
|
|
## Errores conocidos y soluciones
|
|
|
|
| Error | Causa | Solucion |
|
|
|---|---|---|
|
|
| password authentication failed for supabase_storage_admin | POSTGRES_PASSWORD con caracteres especiales | Usar openssl rand -hex 24 |
|
|
| database "_supabase" does not exist | volumes/db/ no accesible | Usar modo Git en Dokploy, no docker raw |
|
|
| keyauth_credentials uniqueness violation | ANON_KEY = SERVICE_ROLE_KEY o duplicados en kong.yml | Una sola entrada por bloque en kong.yml |
|
|
| exec: kong-entrypoint.sh is a directory | Volumen corrupto | docker volume rm del volumen afectado |
|
|
| GOTRUE_EXTERNAL_ANONYMOUS_USERS_ENABLED: converting to bool | Variable vacia | Agregar GOTRUE_EXTERNAL_ANONYMOUS_USERS_ENABLED=false |
|
|
| GOTRUE_SMTP_PORT: converting to int | SMTP_PORT vacio | Agregar SMTP_PORT=587 |
|
|
| auth.jwt() does not exist | GoTrue v2.189+ lo elimino | Ver TECH-DEBT-007 |
|
|
| Conflicto de nombres de contenedores | Stack manual y Dokploy corriendo en paralelo | docker compose down desde ~/supabase-project |
|
|
|
|
---
|
|
|
|
## Deuda tecnica
|
|
|
|
- TECH-DEBT-006: Backup remoto (S3) — bloqueante antes del primer dato real de empresa.
|
|
- TECH-DEBT-007: Migrar auth.jwt() a alternativa compatible con GoTrue v2.189+.
|
|
|
|
---
|
|
|
|
## Datos operativos de esta instalacion
|
|
|
|
- VPS: inq-mart-2 - IP VPN: 172.20.5.11
|
|
- Dokploy: https://172.20.5.11 (acceso via VPN)
|
|
- Repo Supabase: https://git.inquality.com.py/marcos/supabase-motor-fomularios
|
|
- Backups: /var/backups/supabase-prod/ (diarios, retencion 30 dias)
|
|
- Studio: ssh -L 3001:localhost:3001 administrator@172.20.5.11 -> http://localhost:3001 |