--- title: "Cómo instalar Supabase self-hosted en Ubuntu 24.04" description: "Despliega Supabase self-hosted con Docker Compose en Ubuntu 24.04, genera claves seguras, publícalo con Nginx y HTTPS y prueba la API con Row Level Security." category: "Aplicaciones Web Extendidas" language: es source: https://cubepath.com/es/docs/aplicaciones-web-extendidas/instalacion-de-supabase-self-hosted updated: 2026-09-25 --- # Cómo instalar Supabase self-hosted en Ubuntu 24.04 Supabase es una alternativa de código abierto a Firebase construida sobre PostgreSQL: combina base de datos, API REST generada automáticamente (PostgREST), autenticación (GoTrue), almacenamiento de archivos, Realtime y el panel Studio. En este tutorial desplegarás Supabase en Ubuntu 24.04 con Docker Compose, generarás tus propias claves, lo publicarás detrás de Nginx con HTTPS y comprobarás la API con una tabla protegida por Row Level Security. ## Requisitos previos Para seguir esta guía necesitas: - Un servidor con Ubuntu 24.04 LTS, por ejemplo un VPS de CubePath, con al menos 4 GB de RAM (8 GB recomendados), 2 vCPU y 40 GB de disco. Supabase levanta más de diez contenedores. - Un usuario no root con privilegios `sudo`. - Un subdominio con un registro A apuntando al servidor. En esta guía se usa `supabase.tudominio.com`. - Docker Engine y el plugin de Docker Compose instalados desde el repositorio oficial de Docker. Esta guía usa `!override` en un archivo de Compose, que requiere Docker Compose 2.24.4 o posterior. Comprueba la versión de Compose: ```bash sudo docker compose version ``` ```output Docker Compose version v2.39.2 ``` ## Paso 1: Descargar los archivos de Supabase La configuración oficial para autoalojar Supabase está en el directorio `docker` del repositorio del proyecto. Clónalo y copia esos archivos a un directorio de trabajo propio, para poder actualizar el repositorio sin pisar tu configuración: ```bash sudo apt update sudo apt install git git clone --depth 1 https://github.com/supabase/supabase mkdir ~/supabase-project cp -rf supabase/docker/* ~/supabase-project cp supabase/docker/.env.example ~/supabase-project/.env cd ~/supabase-project ``` Comprueba que tienes el archivo de Compose, el `.env` y el directorio de volúmenes: ```bash ls -a ``` ```output . .. .env docker-compose.yml volumes ... ``` ## Paso 2: Generar secretos y claves JWT El `.env` de ejemplo trae contraseñas y claves públicas conocidas por todo el mundo. Nunca arranques Supabase con ellas en un servidor accesible desde Internet. `ANON_KEY` y `SERVICE_ROLE_KEY` son tokens JWT firmados con `JWT_SECRET`. Crea un pequeño script en Python (incluido en Ubuntu 24.04) que los genere: ```bash nano ~/gen-jwt.py ``` ```python import base64, hashlib, hmac, json, sys, time def b64url(data): return base64.urlsafe_b64encode(data).rstrip(b"=").decode() secret, role = sys.argv[1], sys.argv[2] now = int(time.time()) header = {"alg": "HS256", "typ": "JWT"} payload = {"role": role, "iss": "supabase", "iat": now, "exp": now + 5 * 365 * 24 * 3600} signing_input = ".".join( b64url(json.dumps(part, separators=(",", ":")).encode()) for part in (header, payload) ) signature = hmac.new(secret.encode(), signing_input.encode(), hashlib.sha256).digest() print(f"{signing_input}.{b64url(signature)}") ``` En la misma sesión de terminal, genera los secretos y las dos claves: ```bash JWT_SECRET=$(openssl rand -hex 32) ANON_KEY=$(python3 ~/gen-jwt.py "$JWT_SECRET" anon) SERVICE_ROLE_KEY=$(python3 ~/gen-jwt.py "$JWT_SECRET" service_role) ``` Escribe todos los valores en `.env`. La contraseña de PostgreSQL usa solo caracteres hexadecimales porque forma parte de URLs de conexión: ```bash sed -i \ -e "s|^POSTGRES_PASSWORD=.*|POSTGRES_PASSWORD=$(openssl rand -hex 24)|" \ -e "s|^JWT_SECRET=.*|JWT_SECRET=${JWT_SECRET}|" \ -e "s|^ANON_KEY=.*|ANON_KEY=${ANON_KEY}|" \ -e "s|^SERVICE_ROLE_KEY=.*|SERVICE_ROLE_KEY=${SERVICE_ROLE_KEY}|" \ -e "s|^DASHBOARD_PASSWORD=.*|DASHBOARD_PASSWORD=$(openssl rand -hex 16)|" \ -e "s|^SECRET_KEY_BASE=.*|SECRET_KEY_BASE=$(openssl rand -hex 32)|" \ -e "s|^VAULT_ENC_KEY=.*|VAULT_ENC_KEY=$(openssl rand -hex 16)|" \ .env ``` Comprueba que los valores han cambiado: ```bash grep -E '^(POSTGRES_PASSWORD|JWT_SECRET|DASHBOARD_USERNAME|DASHBOARD_PASSWORD)=' .env ``` ```output POSTGRES_PASSWORD=5f0c3b9e8a... JWT_SECRET=9d41e6c07b... DASHBOARD_USERNAME=supabase DASHBOARD_PASSWORD=a73be21f04... ``` Anota `DASHBOARD_PASSWORD` y `ANON_KEY`: los necesitarás para entrar en Studio y para probar la API. Si tu `.env` contiene otras variables de secreto de ejemplo (por ejemplo `PG_META_CRYPTO_KEY` o `LOGFLARE_PRIVATE_ACCESS_TOKEN`), sustitúyelas también por valores generados con `openssl rand -hex 32`. > Importante: si más adelante cambias `JWT_SECRET`, tendrás que regenerar `ANON_KEY` y `SERVICE_ROLE_KEY` con el nuevo secreto. Con claves firmadas por otro secreto, todas las peticiones fallarán con errores de JWT. ## Paso 3: Configurar las URLs públicas Supabase necesita conocer la URL por la que se accede a la API y la URL de tu aplicación, a la que GoTrue redirige tras un inicio de sesión o una confirmación de correo. Edita `.env`: ```bash nano .env ``` Ajusta estas variables con tus dominios: ```ini SITE_URL=https://app.tudominio.com API_EXTERNAL_URL=https://supabase.tudominio.com SUPABASE_PUBLIC_URL=https://supabase.tudominio.com ``` Para que los correos de confirmación y recuperación de contraseña funcionen, completa también las variables `SMTP_ADMIN_EMAIL`, `SMTP_HOST`, `SMTP_PORT`, `SMTP_USER`, `SMTP_PASS` y `SMTP_SENDER_NAME` con los datos de tu servidor de correo. ## Paso 4: Limitar los puertos publicados a localhost Por defecto, Compose publica la API (Kong, puertos 8000 y 8443) y el pooler de PostgreSQL (Supavisor, puertos 5432 y 6543) en todas las interfaces. Docker gestiona sus propias reglas de iptables, así que UFW no los bloquea. Como Nginx será la única entrada pública, crea un archivo de override que los publique solo en `127.0.0.1`: ```bash nano docker-compose.override.yml ``` ```yaml services: kong: ports: !override - "127.0.0.1:8000:8000/tcp" - "127.0.0.1:8443:8443/tcp" supavisor: ports: !override - "127.0.0.1:5432:5432" - "127.0.0.1:6543:6543" ``` `docker compose` carga `docker-compose.override.yml` automáticamente, y `!override` sustituye la lista de puertos original en lugar de añadirse a ella. ## Paso 5: Arrancar Supabase Descarga las imágenes (varios GB, tarda unos minutos) y arranca los servicios: ```bash sudo docker compose pull sudo docker compose up -d ``` Espera un minuto y comprueba el estado. Todos los contenedores deben aparecer como `running` y los que tienen comprobación de salud como `healthy`: ```bash sudo docker compose ps --format 'table {{.Name}}\t{{.Status}}' ``` ```output NAME STATUS supabase-auth Up 2 minutes (healthy) supabase-db Up 2 minutes (healthy) supabase-kong Up 2 minutes (healthy) supabase-rest Up 2 minutes supabase-storage Up 2 minutes (healthy) supabase-studio Up 2 minutes (healthy) ... ``` Verifica que los puertos solo escuchan en localhost: ```bash sudo ss -tlnp | grep -E ':(8000|5432|6543) ' ``` Todas las líneas deben mostrar `127.0.0.1` como dirección local. Prueba el servicio de autenticación a través de Kong con tu `ANON_KEY` (en la misma sesión en la que la generaste): ```bash curl -s http://127.0.0.1:8000/auth/v1/health -H "apikey: ${ANON_KEY}" ``` ```output {"version":"v2.177.0","name":"GoTrue","description":"GoTrue is a user registration and authentication API"} ``` ## Paso 6: Publicar Supabase con Nginx y HTTPS Instala Nginx y Certbot, y abre los puertos web en UFW: ```bash sudo apt install nginx certbot python3-certbot-nginx sudo ufw allow OpenSSH sudo ufw allow 'Nginx Full' sudo ufw enable ``` Crea el sitio de Nginx. Todo el tráfico va a Kong, que enruta internamente a la API REST, Auth, Storage, Realtime y Studio. Las cabeceras `Upgrade` y `Connection` son necesarias para los WebSockets de Realtime: ```bash sudo nano /etc/nginx/sites-available/supabase ``` ```nginx map $http_upgrade $connection_upgrade { default upgrade; '' close; } server { listen 80; listen [::]:80; server_name supabase.tudominio.com; client_max_body_size 50m; location / { proxy_pass http://127.0.0.1:8000; proxy_http_version 1.1; 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_set_header Upgrade $http_upgrade; proxy_set_header Connection $connection_upgrade; proxy_read_timeout 3600s; } } ``` Activa el sitio, comprueba la sintaxis y recarga Nginx: ```bash sudo ln -s /etc/nginx/sites-available/supabase /etc/nginx/sites-enabled/ sudo nginx -t sudo systemctl reload nginx ``` ```output nginx: the configuration file /etc/nginx/nginx.conf syntax is ok nginx: configuration file /etc/nginx/nginx.conf test is successful ``` Solicita el certificado. Certbot añade el bloque HTTPS y la redirección desde HTTP: ```bash sudo certbot --nginx -d supabase.tudominio.com ``` Abre `https://supabase.tudominio.com` en el navegador. Kong te pedirá usuario y contraseña: usa `DASHBOARD_USERNAME` (por defecto `supabase`) y `DASHBOARD_PASSWORD`. Verás el panel Studio. ## Paso 7: Crear una tabla y probar la API REST En Studio, abre **SQL Editor** y ejecuta este SQL. Crea una tabla, activa Row Level Security y añade una política que permite leer las filas con la clave anónima: ```sql create table public.notas ( id bigint generated always as identity primary key, texto text not null, creada_en timestamptz not null default now() ); alter table public.notas enable row level security; create policy "lectura publica de notas" on public.notas for select to anon using (true); insert into public.notas (texto) values ('Hola desde Supabase'); ``` PostgREST expone la tabla automáticamente en `/rest/v1/notas`. Consúltala desde tu equipo: ```bash curl -s "https://supabase.tudominio.com/rest/v1/notas?select=*" \ -H "apikey: TU_ANON_KEY" ``` ```output [{"id":1,"texto":"Hola desde Supabase","creada_en":"2026-09-25T10:12:31.482+00:00"}] ``` Prueba ahora a insertar con la clave anónima. Como no existe ninguna política `insert`, RLS lo rechaza: ```bash curl -s -X POST "https://supabase.tudominio.com/rest/v1/notas" \ -H "apikey: TU_ANON_KEY" \ -H "Content-Type: application/json" \ -d '{"texto":"intento anónimo"}' ``` ```output {"code":"42501","details":null,"hint":null,"message":"new row violates row-level security policy for table \"notas\""} ``` Así debe comportarse: la `ANON_KEY` se usa en el navegador y solo puede hacer lo que permitan tus políticas. La `SERVICE_ROLE_KEY` se salta RLS, así que guárdala solo en tu backend. ## Paso 8: Mantenimiento básico Para aplicar cambios en `.env`, recrea los contenedores. `docker compose restart` no vuelve a leer el archivo: ```bash cd ~/supabase-project sudo docker compose up -d ``` Haz una copia de seguridad lógica de la base de datos con `pg_dump` dentro del contenedor: ```bash sudo docker exec supabase-db pg_dump -U postgres -d postgres -Fc > supabase-$(date +%F).dump ``` Para actualizar, revisa las notas de versión de Supabase, actualiza el repositorio clonado, compara su `docker-compose.yml` con el tuyo y después ejecuta: ```bash sudo docker compose pull sudo docker compose up -d ``` ## Solución de problemas **Algún contenedor se queda en `unhealthy` o reiniciándose.** Consulta sus logs usando el nombre del servicio de Compose, por ejemplo `sudo docker compose logs db --tail 50` o `sudo docker compose logs auth --tail 50`. Con menos de 4 GB de RAM es habitual que el sistema mate procesos por falta de memoria; compruébalo con `sudo dmesg | grep -i oom`. **La API responde `Invalid authentication credentials` o `JWSInvalidSignature`.** `ANON_KEY` y `SERVICE_ROLE_KEY` no están firmadas con el `JWT_SECRET` actual. Regenera ambas con el script del paso 2 y ejecuta `sudo docker compose up -d`. **Si cambiaste `POSTGRES_PASSWORD` después del primer arranque, los servicios no conectan a la base de datos.** La contraseña se fija al inicializar el volumen de datos en `volumes/db/data`; cambiar `.env` después no la actualiza. En una instalación nueva sin datos puedes parar todo con `sudo docker compose down -v`, borrar `volumes/db/data` y arrancar de nuevo. ## Conclusión Tienes Supabase autoalojado en Ubuntu 24.04, con secretos propios, la API y Studio publicados solo a través de Nginx con HTTPS, y una tabla protegida por Row Level Security. Como siguientes pasos, configura el SMTP para los correos de autenticación, programa el `pg_dump` con cron y copia los volcados fuera del servidor, y conecta tu aplicación con el cliente `@supabase/supabase-js` usando `https://supabase.tudominio.com` y tu `ANON_KEY`.