Desplegar Supabase self-hosted en un VPS: guía completa 2026

¿Por qué self-hostear Supabase?
Supabase se ha convertido en el backend por defecto de la era de la IA. Es lo que usan las apps construidas con Lovable, Cursor, Bolt y prácticamente cualquier proyecto que necesite "una base de datos y login". La versión cloud es excelente, con un free tier que cubre proyectos pequeños. Pero ¿por qué ejecutarlo tú mismo?
- Tus datos, tu servidor: Cumplimiento normativo, contratos con clientes o simple principio — algunos datos no deberían vivir en la nube de terceros.
- Costes fijos a escala: El pricing cloud crece con el uso; un VPS de $10-20/mes corre el stack completo para todos los proyectos que quieras.
- Sin pausas ni límites: Los proyectos del free tier se pausan por inactividad. Tu instancia propia nunca se pausa, y los límites de filas son los que tu disco permita.
- Es genuinamente open source: Toda la plataforma — Postgres, Auth, Storage, Realtime, Edge Functions — se distribuye como un stack de Docker Compose, mantenido por el propio Supabase.
⚠️ Antes de empezar: Self-hostear Supabase es más exigente que correr una app simple — es un stack multi-servicio con 12+ contenedores. Si nunca has usado Docker, empieza con algo más simple y vuelve aquí. Para la mayoría de proyectos hobby, el free tier de Supabase Cloud es la mejor decisión.
Qué necesitas
- VPS con al menos 4 GB RAM y 2 vCPU (8 GB y 4 cores recomendado para producción)
- Ubuntu 22.04 o 24.04 LTS (recomendado)
- Docker Engine + Docker Compose v2 instalados
- 10+ GB de disco libre (crece con tus datos)
- Un dominio o subdominio (ej.
supabase.tuapp.com) para HTTPS — obligatorio si vas a usar OAuth - Acceso root por SSH
- 30-60 minutos de tu tiempo
Arquitectura del stack
Supabase self-hosted corre unos 12-13 contenedores orquestados por Docker Compose. Esto es lo que se levanta:
| Servicio | Puerto | Qué hace |
|---|---|---|
| PostgreSQL | 5432 | Base de datos principal con extensiones de Supabase |
| Kong (API Gateway) | 8000 | Gateway único para API, Auth, Storage, Realtime |
| GoTrue (Auth) | Interno | Autenticación, JWT, OAuth, magic links |
| PostgREST | Interno | API REST automática sobre PostgreSQL |
| Storage API | Interno | Almacenamiento de archivos con S3 compatibility |
| Realtime | Interno | Websockets para suscripciones en tiempo real |
| Edge Functions | Interno | Runtime para funciones serverless (Deno) |
| Studio (Dashboard) | Interno | Panel de administración web |
| Supavisor | 5432/6543 | Connection pooler (session y transaction mode) |
| imgproxy | Interno | Redimensionamiento de imágenes |
| Vector | Interno | Pipeline de logs (opcional) |
Paso 1 — Preparar el servidor
Conéctate a tu VPS por SSH:
ssh root@tu-ip-del-servidor
Actualiza el sistema:
apt update && apt upgrade -y
Crea un usuario sin root (buena práctica de seguridad):
adduser supabase-user
usermod -aG sudo supabase-user
su - supabase-user
Paso 2 — Instalar Docker
# Instalar Docker Engine
curl -fsSL https://get.docker.com -o get-docker.sh
sudo sh get-docker.sh
# Añadir tu usuario al grupo docker
sudo usermod -aG docker $USER
# Verificar instalación
docker --version
docker compose version
Docker Compose v2 viene incluido con Docker Engine 20.10+. El comando es docker compose, no docker-compose.
Paso 3 — Obtener el código de Supabase
Supabase distribuye la configuración de Docker Compose dentro de su repositorio principal. Tienes dos caminos:
Opción A: Quick start con script automático (recomendado para Linux)
curl -fsSL https://supabase.link/setup.sh | sh
Este script hace todo automáticamente:
- Instala prerrequisitos (git, openssl, jq) y Docker si no están
- Clona el directorio
docker/del repo de Supabase - Crea un directorio de proyecto (
supabase-project) - Te pide las URLs principales (SUPABASE_PUBLIC_URL, API_EXTERNAL_URL, SITE_URL)
- Genera todos los secretos, incluyendo DASHBOARD_PASSWORD y las claves JWT asimétricas
- Descarga las imágenes de Docker
Después de que termine, inicia el stack:
cd supabase-project
sh run.sh start
Puedes ver las credenciales generadas cuando quieras con:
sh run.sh secrets
Opción B: Instalación manual (control total)
# Clonar el repositorio (shallow clone)
git clone --depth 1 https://github.com/supabase/supabase
# Crear directorio de proyecto
mkdir supabase-project
# Copiar archivos de configuración
cp -rf supabase/docker/* supabase-project
# Copiar archivo de entorno
cp supabase/docker/.env.example supabase-project/.env
# Entrar al directorio
cd supabase-project
# Descargar imágenes
docker compose pull
Paso 4 — Generar secretos seguros
Este es el paso más crítico. El archivo .env.example viene con secretos de demostración públicamente documentados. Cualquiera que haya leído la documentación de Supabase conoce tu JWT secret, API keys y password del dashboard si no los cambias.
Ejecutar el generador de claves
# Generar secretos y claves
sh utils/generate-keys.sh
# Añadir las nuevas claves asimétricas
sh utils/add-new-auth-keys.sh
Revisa el output de ambos scripts y verifica el archivo .env antes de continuar.
Variables que DEBES cambiar en .env
# Password de la base de datos (superusuario)
POSTGRES_PASSWORD=tu-password-largo-y-aleatorio
# Secret para firmar JWTs (mínimo 40 caracteres aleatorios)
JWT_SECRET=tu-jwt-secret-de-al-menos-40-caracteres
# Claves API (firmadas con tu JWT_SECRET — no editar a mano)
ANON_KEY=generado-por-el-script
SERVICE_ROLE_KEY=generado-por-el-script
# Credenciales del dashboard
DASHBOARD_USERNAME=tu-usuario
DASHBOARD_PASSWORD=tu-password-seguro
# URLs (tu dominio real)
SITE_URL=https://tuapp.com
API_EXTERNAL_URL=https://supabase.tuapp.com
SUPABASE_PUBLIC_URL=https://supabase.tuapp.com
🚨 Crítico: El default del dashboard es supabase / this_password_is_insecure_and_should_be_updated — el propio nombre te lo dice. Los escáneres buscan activamente instancias con credenciales por defecto. Nunca expongas Supabase a internet sin cambiar todos los secretos.
Paso 5 — Iniciar el stack
# Iniciar todos los servicios
sh run.sh start
# Equivalente a: docker compose up -d --wait
# Verificar estado
docker compose ps
Después de 1-2 minutos, todos los servicios deberían mostrar estado Up [...] (healthy). Si ves algún servicio en estado created sin Up, ejecuta:
# Diagnosticar problemas
sh tests/test-container-logs.sh
# Ver logs de un servicio específico
sh run.sh logs storage
Verificar que la API responde
curl http://localhost:8000/rest/v1/ -H "apikey: tu-anon-key"
Debe devolver una respuesta JSON de PostgREST.
Acceder al dashboard (Studio)
Abre en el navegador: http://tu-ip:8000
Te pedirá usuario y contraseña (los que configuraste en .env).
Paso 6 — Configurar HTTPS con reverse proxy
Para producción — y especialmente si vas a usar OAuth (Google, GitHub, etc.) — necesitas HTTPS. Tienes dos opciones:
Opción A: Caddy (la más simple, SSL automático)
# Instalar Caddy
sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list
sudo apt update && sudo apt install caddy -y
Crea el Caddyfile:
sudo nano /etc/caddy/Caddyfile
supabase.tuapp.com {
reverse_proxy localhost:8000
}
Reinicia Caddy:
sudo systemctl restart caddy
Caddy obtiene automáticamente el certificado TLS de Let's Encrypt en la primera petición. Listo.
Opción B: Nginx + Certbot
# Instalar Nginx y Certbot
sudo apt install -y nginx certbot python3-certbot-nginx
Crea la configuración del sitio:
sudo nano /etc/nginx/sites-available/supabase
server {
listen 80;
server_name supabase.tuapp.com;
location / {
proxy_pass http://localhost:8000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
# Activar sitio
sudo ln -s /etc/nginx/sites-available/supabase /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
# Obtener certificado SSL
sudo certbot --nginx -d supabase.tuapp.com
Paso 7 — Asegurar el VPS
Configurar firewall
# Solo permitir SSH, HTTP y HTTPS
sudo ufw allow ssh
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
sudo ufw status
No abras los puertos 8000, 3000, 5432 ni 6543 al público. Todo debe pasar por el reverse proxy.
Desactivar login SSH por contraseña
# Generar SSH key en tu máquina local
ssh-keygen -t ed25519
ssh-copy-id supabase-user@tu-ip
# En el servidor, editar sshd_config
sudo nano /etc/ssh/sshd_config
# Cambiar:
# PasswordAuthentication no
# PermitRootLogin no
sudo systemctl restart sshd
Instalar fail2ban
sudo apt install -y fail2ban
sudo systemctl enable fail2ban
sudo systemctl start fail2ban
Restringir acceso al dashboard (opcional pero recomendado)
Studio es un panel de administración. El internet no necesita verlo. Puedes restringirlo por IP en Nginx:
location / {
proxy_pass http://localhost:8000;
# ...
allow tu.ip.publica;
deny all;
}
O usar un túnel SSH para acceder:
ssh -L 8000:localhost:8000 supabase-user@tu-ip
Luego abre http://localhost:8000 en tu navegador.
Paso 8 — Conectar tu app
Desde la perspectiva de tu app, un Supabase self-hosted es idéntico al cloud. Solo cambias dos variables de entorno:
# .env de tu aplicación
NEXT_PUBLIC_SUPABASE_URL=https://supabase.tuapp.com
NEXT_PUBLIC_SUPABASE_ANON_KEY=tu-anon-key-generado
# Para Server Components / Server Actions
SUPABASE_SERVICE_ROLE_KEY=tu-service-role-key
Cada librería cliente de Supabase — y cualquier app construida con IA que use una — pick up el nuevo backend sin cambios de código. Si tu app vino de Lovable, Cursor o Bolt, solo cambia estos valores y la app hablará con tu servidor en lugar del cloud.
Ver las credenciales cuando necesites
sh run.sh secrets
Paso 9 — Conexión a la base de datos
Supabase incluye Supavisor como connection pooler. Puedes conectarte de dos formas:
Session mode (conexión directa)
psql 'postgres://postgres.tu-tenant-id:[email protected]:5432/postgres'
Transaction mode (pooling, recomendado para apps)
psql 'postgres://postgres.tu-tenant-id:[email protected]:6543/postgres'
El POOLER_TENANT_ID por defecto es your-tenant-id (puedes cambiarlo en .env).
Paso 10 — Edge Functions
Las Edge Functions viven en volumes/functions/. El setup por defecto incluye una función hello de ejemplo:
# Probar la función hello
curl http://supabase.tuapp.com/functions/v1/hello
Para añadir nuevas funciones, crea un archivo en volumes/functions/tu-funcion/index.ts y reinicia el servicio:
sh run.sh recreate functions
Un docker compose restart simple no es suficiente porque no recoge los cambios de configuración.
Paso 11 — Backups y mantenimiento
Backups de la base de datos
# Backup manual
pg_dump 'postgres://postgres:tu-password@localhost:5432/postgres' > backup_$(date +%Y%m%d).sql
# Backup automático con cron (ej. cada noche a las 3am)
crontab -e
# Añadir:
0 3 * * * pg_dump 'postgres://postgres:tu-password@localhost:5432/postgres' > /home/supabase-user/backups/backup_$(date +\%Y\%m\%d).sql
Testea un restore al menos una vez — un backup sin testear es una esperanza, no un backup.
Backup de archivos de Storage
# Copiar archivos de storage
cp -r volumes/storage/data /home/supabase-user/backups/storage_$(date +%Y%m%d)
Actualizar Supabase
# Antes de actualizar, busca el último tag en:
# https://github.com/supabase/supabase/releases
# Actualizar imágenes
docker compose pull
# Reiniciar con nuevas versiones
docker compose up -d
# O usando el script:
sh run.sh start
Importante: Lee los release notes antes de actualizar. Postgres major versions y cambios de Auth ocasionalmente requieren pasos de migración. Haz siempre un backup antes de actualizar.
Eliminar servicios que no uses
Si no necesitas Realtime, Analytics (Logflare) o Edge Functions, elimina esas secciones de docker-compose.yml para reducir el consumo de RAM significativamente.
Comandos de gestión esenciales
| Comando | Qué hace |
|---|---|
sh run.sh start |
Iniciar todos los servicios |
sh run.sh stop |
Detener todos los servicios |
docker compose ps |
Ver estado de los servicios |
sh run.sh logs servicio |
Ver logs de un servicio específico |
sh run.sh secrets |
Ver credenciales generadas |
sh run.sh recreate functions |
Recrear contenedor de Edge Functions |
docker compose pull |
Descargar nuevas versiones de imágenes |
sh tests/test-container-logs.sh |
Diagnosticar problemas de contenedores |
Solución de problemas
Un contenedor no arranca
# Ver logs del contenedor problemático
sh run.sh logs db # Postgres suele ser el culpable
sh run.sh logs auth
sh run.sh logs storage
# Causa más común: variable de entorno malformada o faltante
# Revisa .env cuidadosamente
Kong no arranca (error de entrypoint)
Si clonaste en Windows, los archivos pueden tener line endings CRLF en lugar de LF. Re-clona el repositorio o normaliza los archivos a LF:
find . -name "*.yml" -exec sed -i 's/\r$//' {} \;
find . -name "*.sh" -exec sed -i 's/\r$//' {} \;
Error "container supabase-vector exited (0)"
Si usas Docker rootless, edita .env y cambia DOCKER_SOCKET_LOCATION a tu socket:
DOCKER_SOCKET_LOCATION=/run/user/1000/docker.sock
El dashboard muestra "connection refused"
- Verifica que Kong está corriendo:
docker compose ps kong - Verifica que el puerto 8000 no está bloqueado por firewall
- Si usas reverse proxy, verifica la configuración de Nginx/Caddy
OutOfMemory en VPS pequeños
# Verificar uso de memoria
free -h
docker stats
# Solución: eliminar servicios innecesarios del docker-compose.yml
# Comentar secciones de: realtime, analytics, imgproxy, vector
Auth emails no llegan
Por defecto, Supabase self-hosted no tiene SMTP configurado. Necesitas un proveedor SMTP:
# En .env, configurar SMTP
[email protected]
SMTP_HOST=smtp.sendgrid.net
SMTP_PORT=587
SMTP_USER=apikey
SMTP_PASS=tu-api-key-de-sendgrid
SMTP_SENDER_NAME=Supabase
[email protected]
Self-hosted vs Supabase Cloud: comparación honesta
| Aspecto | Self-hosted | Cloud |
|---|---|---|
| Coste | $10-25/mes fijo (VPS) | Free tier → $25/mes Pro |
| Datos | 100% en tu servidor | En servidores de Supabase |
| Mantenimiento | Tú gestionas updates, backups | Automático, cero mantenimiento |
| Límites | Tu disco es el límite | 500MB (free) / 8GB (Pro) |
| Pausas | Nunca se pausa | Free tier se pausa por inactividad |
| Backups | Manuales (pg_dump + cron) | Automáticos + point-in-time recovery |
| Branching | No disponible | Disponible en Pro |
| Dashboard features | Llegan después al self-hosted | Primero en cloud |
💡 Decisión práctica: Self-hostea cuando el control de datos o la economía multi-proyecto lo justifiquen, y cuando alguien (tú) vaya a hacer el mantenimiento real. Para el resto, el cloud es la mejor decisión de ingeniería.
Conclusión
Desplegar Supabase self-hosted en un VPS te da control total sobre tu backend: Postgres, Auth, Storage, Realtime y Edge Functions corriendo en tu propia infraestructura, sin límites de filas, sin pausas y sin enviar datos a terceros. El proceso toma entre 30 y 60 minutos si sigues los pasos correctamente.
La clave del éxito está en tres cosas: (1) cambiar todos los secretos antes de exponer nada a internet, (2) poner un reverse proxy con HTTPS delante, y (3) configurar backups automáticos desde el día uno. Si haces eso, tendrás un backend robusto y privado a una fracción del coste del cloud.
Y si tu app usa modelos de IA locales, puedes combinar este setup con Ollama para tener todo tu stack — backend y IA — corriendo en tus propios servidores.