Supabase
Postgres backend with auth, storage, realtime and a dashboard
Deploy NowREADME
Postgres backend with auth, storage, realtime and a dashboard.
Studio, the dashboard, has full access to your database, and the password you set at deploy is the only thing guarding it. Pick a strong one.
Overview
Supabase is an open-source backend built on Postgres: user sign-up and sign-in, an auto-generated REST API over your tables with row level security, file storage with on-the-fly image resizing, realtime messaging over websockets, and Studio, a dashboard with a table editor and a SQL editor.
This template is upstream's self-hosted stack at tag self-hosted/v0.8.2, with every component
image pinned to that tag's docker/docker-compose.yml. Instead of one machine running the compose
file, each component gets its own service, and the database is a managed InstaCloud Postgres:
| Service | Runs | Image | Always on |
|---|---|---|---|
gateway | Envoy, with upstream's routes, API key checks and Studio's basic auth. The one URL you use | this template's image | yes |
auth | GoTrue (Supabase Auth) | this template's image | no |
rest | PostgREST | this template's image | no |
realtime | Supabase Realtime | this template's image | yes |
storage | Storage API and imgproxy, on one volume | this template's image | no |
studio | Studio and postgres-meta | this template's image | no |
db | Managed Postgres 16 | platform managed | platform managed |
The template's own image only puts upstream's binaries side by side (Envoy, GoTrue, PostgREST, the
Realtime release, Studio, postgres-meta, the Storage API and imgproxy) and adds an entrypoint that
picks one component per machine. Nothing is rebuilt from source except the Storage API's fs-xattr
addon, which upstream ships built for Alpine and is rebuilt against glibc.
Every service that uses the database (auth, rest, realtime, storage and studio) creates
the schema Supabase expects when it boots, if no other service has yet, so the stack comes up in
whatever order the platform starts it.
Two pairs share a machine on purpose. postgres-meta has no authentication, so it runs next to Studio
and listens on loopback only. imgproxy has none either, and reads the stored files straight off the
storage volume, so it runs next to the Storage API, also on loopback. Studio's own machine refuses
every request that did not come through the gateway, so the gateway's basic auth cannot be skipped.
The one exception is /healthz, which the platform's health check calls directly and which Studio
answers from its profile route.
What you get by hosting it
- One HTTPS URL, the gateway's, for everything:
/auth/v1,/rest/v1,/storage/v1,/realtime/v1,/graphql/v1, and Studio at/. It is the URL you give@supabase/supabase-js. - Your data in a managed Postgres that the platform runs, rather than a database container inside the stack.
- Studio behind HTTP basic auth with the username and password you choose at deploy.
- The schema Supabase expects, created on first boot from upstream's own
supabase/postgresmigrations at the tag the compose file pins (17.6.1.136). - An anon key and a service role key, signed with a JWT secret generated at deploy. Studio shows both under Project Settings > API Keys, on the Legacy anon, service_role API keys tab.
- Uploaded files on a persistent volume, and image transformations through imgproxy.
- Email sign-up that works without SMTP: new users are confirmed automatically.
What you need before deploying
A username and a password for Studio. There is no default and nothing is generated: the deploy form starts with both fields empty. A password the platform minted would be one it could never show you again, because a template variable is stored write-only.
Configuration
| Variable | Required | What it does |
|---|---|---|
ADMIN_USERNAME | yes | Username for Studio's basic auth on the gateway. You choose it, using letters, digits and . _ @ - only. Any other character stops the gateway from starting, with the reason in its logs. |
ADMIN_PASSWORD | yes | Password for Studio. You choose it. |
Generated at deploy and shared by the services that need them, never shown back by the platform: the JWT secret every component signs or checks tokens with, the token the gateway attaches for Studio's machine, and Realtime's own secrets. You do not need to read any of them. The anon and service role keys are derived from the JWT secret on every boot, so every service agrees on them.
Every Supabase login role (authenticator, supabase_auth_admin, supabase_storage_admin,
supabase_admin, supabase_read_only_user) gets the managed database's own password, the way
upstream gives them all POSTGRES_PASSWORD.
To change a setting upstream exposes as an environment variable, set it on the service it belongs to and restart that service. Common ones:
- SMTP for real confirmation and recovery mail:
GOTRUE_SMTP_HOST,GOTRUE_SMTP_PORT,GOTRUE_SMTP_USER,GOTRUE_SMTP_PASS,GOTRUE_SMTP_ADMIN_EMAILandGOTRUE_SMTP_SENDER_NAMEonauth. Then setGOTRUE_MAILER_AUTOCONFIRMtofalse. - Your app's URL for redirects:
GOTRUE_SITE_URLandGOTRUE_URI_ALLOW_LISTonauth. - Closing sign-up:
GOTRUE_DISABLE_SIGNUPset totrueonauth. - Studio's AI assistant:
OPENAI_API_KEYonstudio.
After deploy
-
Open the gateway's URL. The browser asks for the username and password you deployed with, and Studio opens.
-
Copy the keys from Project Settings > API Keys > Legacy anon, service_role API keys:
anonfor your app,service_rolefor servers only. The other tab, for publishable and secret keys, is empty on purpose. -
Point the client at the gateway:
import { createClient } from '@supabase/supabase-js' const supabase = createClient('https://<gateway-url>', '<anon key>') -
Create a table in Studio's table editor or SQL editor, enable row level security on it, and add a policy.
supabase.auth.signUp,supabase.from('<table>'),supabase.storageandsupabase.channel(...)broadcast then work as on Supabase's own platform. -
To connect to the database directly, use the
dbservice's connection string from the console. The console's Database > Extensions tab lists only the extensions turned on from that tab, so the ones Supabase created over SQL (pgcrypto,uuid-ossp,pg_graphqland the rest) show as off there. Studio's own Extensions page shows the real state. Turning one on in the console is harmless, but turning it off afterwards runsDROP EXTENSION, which forpg_graphqlturns GraphQL off.
Known limitations
- GraphQL (
/graphql/v1) is off. The managed Postgres does not shippg_graphqlyet, and requests answerpg_graphql extension is not enabled. The template triescreate extension pg_graphqlon every boot ofauth,rest,realtime,storageandstudiountil it succeeds. A deploy made after the database offers it gets GraphQL on first boot. An earlier deploy keeps its database running on the old image, so restart the database from the platform (insta postgres restart, orPOST /projects/{id}/database/restart) and then restartrest. - Realtime
postgres_changesdoes not deliver yet. It needs logical decoding with thewal2jsonoutput plugin, which the managed Postgres does not ship yet. A subscription still answers "Subscribed to PostgreSQL", but no change ever arrives. Broadcast works. The template already setswal_level = logicalat boot, like upstream's own database, but Postgres only reads it at start. Once the database offerswal2json:- restart the database from the platform (
insta postgres restart, orPOST /projects/{id}/database/restart), then restartrealtime, - add your tables to the
supabase_realtimepublication, for examplealter publication supabase_realtime add table public.messages;.
- restart the database from the platform (
- No Edge Functions and no Supavisor. The functions runtime is not part of this template, and the managed database brings its own connection pooler.
- Logs and analytics in Studio are off, since Logflare and Vector are not part of the stack.
- The new
sb_publishable_andsb_secret_keys are not configured. Use the legacy anon and service role keys, which every Supabase SDK accepts. - Six compute services, billed on actual usage.
gatewayandrealtimeare always on.authandstudioidle-stop and wake on the next request: measured, the first sign-in afterauthslept took about 3 seconds, and the first Studio page about 6.restandstoragemay idle-stop too, but their own steady background traffic (about 100 to 200 bytes a second, measured) counts as activity, so in practice they stay up. - The other services have their own URLs as well.
auth,rest,realtimeandstorageeach check JWTs themselves, so reaching one directly grants nothing the public anon key does not.studiorefuses any request that did not come through the gateway. - Right after
realtimerestarts, a client's first websocket attempt can fail while it boots. The Supabase SDKs reconnect on their own.
Links
- Architectures:
linux/amd64andlinux/arm64. Every upstream image is a two-architecture index, and this template's image only copies their files together. - Self-hosting documentation: https://supabase.com/docs/guides/self-hosting/docker
- Upstream: https://github.com/supabase/supabase, tag
self-hosted/v0.8.2 - Images:
supabase/studio:2026.09.07-sha-7996410,supabase/gotrue:v2.196.0,postgrest/postgrest:v14.17,supabase/realtime:v2.134.10,supabase/storage-api:v1.74.0,darthsim/imgproxy:v3.31.4,supabase/postgres-meta:v0.99.0,envoyproxy/envoy:v1.39.1 - License: Apache-2.0 (upstream
supabase/supabase).
Services & Specs
Provisioned and operated by InstaCloud — credentials are injected automatically at deploy time.
- Image
- ghcr.io/insforge/insta-oss/templates/supabase:0.1.1
- Port
- 9999
- Healthcheck
- /health
- Image
- ghcr.io/insforge/insta-oss/templates/supabase:0.1.1
- Port
- 3000
- Healthcheck
- /
- Image
- ghcr.io/insforge/insta-oss/templates/supabase:0.1.1
- Port
- 3000
- Healthcheck
- /healthz
- Image
- ghcr.io/insforge/insta-oss/templates/supabase:0.1.1
- Port
- 8000
- Healthcheck
- /
- Image
- ghcr.io/insforge/insta-oss/templates/supabase:0.1.1
- Port
- 5000
- Healthcheck
- /status
- Image
- ghcr.io/insforge/insta-oss/templates/supabase:0.1.1
- Port
- 4000
- Healthcheck
- /healthcheck
Variables
You supply 2 variables before the first deploy.
Required
ADMIN_USERNAMEUsername for the Studio dashboard, asked for by the browser when you open the gateway URL. Letters, digits and . _ @ - only
ADMIN_PASSWORDPassword for the Studio dashboard. Studio has full access to your database, so pick a strong one