Files
motor-de-encuestas/INSTALL_SUPABASE.md
2026-06-10 08:59:50 -03:00

12 KiB

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

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:

grep -n "keyauth" ~/supabase-project/volumes/api/kong.yml

Deben quedar dos bloques con UNA sola entrada cada uno:

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

cat > ~/supabase-project/.gitignore << 'EOF'
.env
.env.local
volumes/db/data/
volumes/storage/
volumes/logs/
*.log
EOF

1.4 Push al repo Git

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)

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:

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:

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:

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)

ssh administrator@<VPS_IP> "mkdir -p ~/migrations"
scp -r supabase/migrations/ administrator@<VPS_IP>:~/migrations/

4.2 Aplicar en orden

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

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

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

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)

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