What runs where
One server
Everything runs on a single virtual server at Hetzner, managed through Coolify, a self-hosted deployment platform. There is no second environment, no staging server, and no managed cloud services behind any of it.
That is a reasonable choice for a community project of this size, but it means the failure modes are concentrated. If the server is down, everything is down. If the disk fills, everything stops. Both have happened.
Hetzner VPS
|
+-- Traefik reverse proxy, TLS certificates, routing by hostname
|
+-- Coolify deployment platform, plus its own database and Redis
|
+-- Schoolers app the Node container built from this repository
|
+-- Supabase stack roughly fifteen containers, see below
|
+-- Stalwart mail server, currently unable to sendTraefik terminates TLS and routes by hostname: the application on one domain, the Supabase gateway on another, the mail dashboard on a third. Certificates renew automatically.
The Supabase stack
Self-hosted Supabase is not one service. The containers that matter for understanding a problem:
| Container | Role |
|---|---|
supabase-db | PostgreSQL. All application data |
supabase-kong | API gateway. Everything from the browser enters here |
supabase-rest | PostgREST. Turns tables into the REST API the app calls |
supabase-auth | GoTrue. Sign-up, sign-in, tokens |
supabase-storage | Storage API for photos and post images |
supabase-minio | S3-compatible object store backing storage |
imgproxy | Image transformation, used by storage |
supabase-studio | The administrative web interface |
supabase-meta | Database introspection, used by Studio |
supabase-analytics | Logflare, log aggregation |
supabase-vector | Ships container logs into Logflare |
supabase-supavisor | Connection pooler |
realtime-dev | Realtime subscriptions |
Two of these cause confusion often enough to note. supabase-analytics is a hard dependency of Kong and Studio, so when it fails to start, the API gateway and the administrative interface both fail with it, which looks like a much larger outage than it is. And supabase-vector mounts the Docker socket in order to read container logs.
The application database is not called postgres
The production database is named admin, not postgres.
Connecting without specifying a database lands you in the wrong one, where the application's tables do not exist. This has wasted time more than once. Always pass the database name explicitly.
The application container
Built from the Dockerfile in this repository, in three stages: production dependencies are installed in one, the application is built with the full toolchain in a second, and the runtime image copies only the built output and production dependencies into a slim Node image.
The runtime runs serve.mjs, which hosts the fetch handler emitted by the build and serves the built client assets. It listens on port 3000 and binds to all interfaces.
A health check polls the container so that a broken deployment never replaces a working one. See Health checks, which also explains why it must address 127.0.0.1 rather than localhost.
The documentation container
This site is the second application built from the same repository: a VitePress build served by nginx on port 80, from docs/Dockerfile. The image is around 105MB, and being static it costs almost nothing to run.
Resource reality
The server is not large. Two constraints shape decisions:
Disk fills up. Docker images and build caches accumulate, and the disk has reached 83 percent occupancy once already, triggering an alert. Old images need pruning periodically, and automated cleanup should be enabled in Coolify.
There is no CDN. Every image is served from this one server. That is why post images are resized in the browser before upload rather than transformed on the server: the processing happens on the poster's machine, once, instead of on the server for every viewer.
Email does not work
A Stalwart mail server is deployed, but outbound mail is blocked: the hosting provider blocks the standard mail ports by default, and the mail server sits on a different Docker network from Supabase, so container-to-container delivery does not work either.
The consequence is that email verification is disabled and no transactional email is sent. See Authentication for what that means for account security.
Related pages
- Deployment — how code reaches the server.
- Health checks — how a service proves it is working.
- Database migrations — how schema changes are applied.
- Backups — the current state, which is that there are none.
- Routine maintenance — patches, disk space, and what is already automated.