Why host your BaaS backend yourself
A BaaS saves you from rewriting authentication, permissions, file storage and real-time APIs for every project. But the cloud offerings of Supabase and Appwrite bill by usage (rows read, storage, MAU) and limit access to the underlying database. By self-hosting on a VPS, you regain direct SQL access, predictable fixed costs and total sovereignty over user data, which is decisive for compliance. Supabase relies entirely on PostgreSQL and exposes an auto-generated REST/GraphQL API plus real-time via the WAL. Appwrite abstracts persistence behind its own API and natively includes multi-runtime serverless functions, a permission system granular down to the document, and an image transcoding engine.
The advantages of self-hosting a BaaS
- Direct SQL access to PostgreSQL (Supabase) for queries, views and migrations with no intermediary.
- Fixed costs tied to the VPS, with no billing by number of rows read or active users.
- User data and files hosted locally: control over data residency.
- Customization of serverless functions with your own runtimes and dependencies.
- No cold start or execution quotas imposed by a third-party cloud.
- Integration into the private Docker network with your other internal services.
| Criterion | Supabase | Appwrite |
|---|---|---|
| Database | Native PostgreSQL (full SQL access) | Own abstraction (MariaDB underneath) |
| Exposed API | Auto-generated REST + GraphQL (PostgREST) | REST + GraphQL + multi-language SDK |
| Real-time | Subscriptions on PostgreSQL changes | Real-time channels per collection |
| Serverless functions | Edge Functions (Deno) | Multi-runtime Functions (Node, Python, PHP, Dart…) |
| Permissions | PostgreSQL Row Level Security | Permissions per document and per role |
| Container architecture | Composed stack (~10 services) | Composed stack around a main container |
| Recommended self-hosted RAM | 4 GB minimum | 2 to 4 GB |
| Target audience | Teams comfortable with SQL | Developers seeking a unified API |
Prerequisites for a healthy deployment
Supabase self-hosted is a stack composed of about ten containers (Postgres, GoTrue, PostgREST, Realtime, Storage, Kong, Studio…). Plan for a VPS of at least 2 vCPU and 4 GB of RAM, with 20 GB of SSD/NVMe disk for the database and files. Appwrite is more modest at bootstrap: 2 vCPU and 2 GB of RAM are enough to start, but move up to 4 GB as soon as the serverless functions run, because each execution launches a container. In both cases: Docker Compose v2 mandatory, a dedicated domain (api.mydomain.com), and a reverse proxy handling HTTPS and WebSockets (indispensable for real-time).
Deploy your self-hosted backend
Get the official stack
For Supabase: git clone --depth 1 https://github.com/supabase/supabase then copy docker/.env.example to .env. For Appwrite, installation is done via docker run of an init script that generates the docker-compose.yml and the .env.
Generate and harden the secrets
This is the most critical step. You must regenerate POSTGRES_PASSWORD, JWT_SECRET, ANON_KEY and SERVICE_ROLE_KEY for Supabase; the default keys are public. For Appwrite, set _APP_OPENSSL_KEY_V1 and a strong admin password.
Configure the domain and public URLs
Set API_EXTERNAL_URL=https://api.mydomain.com and SITE_URL on the Supabase side, or _APP_DOMAIN and _APP_DOMAIN_TARGET on the Appwrite side, so that OAuth redirects and email links are correct.
Launch the stack
Run docker compose up -d. Watch docker compose ps: all services must reach healthy. Supabase Studio is served via Kong on port 8000, the Appwrite interface on the internal 80/443.
Put Kong/Appwrite behind HTTPS
With Traefik or Caddy, route api.mydomain.com to the exposed port and enable WebSocket passthrough (Upgrade/Connection) for real-time. Force the redirect from HTTP to HTTPS.
Initialize and test
Create your first project/user via the Studio (Supabase) or the console (Appwrite), then test auth and a query from a client SDK. Check that the real-time subscriptions properly relay the changes.
For Supabase in production, never store the database on the default Docker volume: mount a dedicated volume and schedule encrypted pg_dumps to external storage, because restoring from a corrupted volume is unmanageable. Also enable Row Level Security on all your tables from creation, otherwise your ANON_KEY grants full access. On the Appwrite side, limit the function runtime and the number of concurrent execution containers via _APP_FUNCTIONS_* to prevent a spike of requests from saturating the VPS.