OurSpace self-hosting setup

The same five questions scripts/init-selfhost.sh asks, answered in your browser. This page is static: nothing runs on a server, nothing is submitted, and no request of any kind leaves this tab. Everything below is computed here, from the copy of .env.example embedded in this file.

1 · Public hostname

OURSPACE_NODE_HOST is stamped on every post as its originServer for federation addressing, and the base API_ORIGIN is derived from it. Leave it blank to skip: the line then stays commented out and the code default localhost:<port> applies. Skipping is not the same as blanking it — backend/src/config.ts falls back with ??, which treats an empty value as a real setting, so an uncommented OURSPACE_NODE_HOST= would make every post's originServer the empty string.

2 · Reverse proxy

TRUST_PROXY is the exact number of proxy hops in front of the API. Without it the X-Forwarded-For header is ignored and every client behind the proxy shares one per-IP rate-limit bucket. Never a bare true — the server refuses to start on anything that is not a whole number.

3 · Ports

The container always listens on 4000; this is the host port it is published on. The shell script checks whether the default is already in use and only asks if it is — this page cannot: a browser has no way to see which ports are listening on your machine. The default below is the same one the script would take. If docker compose up later fails to bind it, change it here and regenerate, or edit OURSPACE_PORT in the .env by hand.

4 · Password-reset email (SMTP)

Entirely optional. Without it the stack runs normally and only /auth/password-reset/* answer 503 ("this server cannot send reset emails"). Mail counts as configured only when both SMTP_HOST and SMTP_FROM are set.

5 · Staging profile

A second, independent API + worker alongside prod. Opt-in and off by default. It is a docker compose --profile flag, not a variable — the only thing written for it is the host port its api-staging publishes on. Staging also needs its own .env.staging, with independently generated secrets: never reuse prod's, or a staging token is also a valid prod credential (docs/SELF_HOSTING.md §17).

Your .env

JWT_SECRET and HLS_URL_SECRET were generated in this tab with crypto.getRandomValues — 32 random bytes each, and guaranteed different from one another. They are never prompted for and never sent anywhere. Everything the wizard does not ask about is left at its documented default, exactly as the shell script leaves it.

Then

Save that as .env in the root of your clone — beside compose.yaml, not inside backend/ — and run:

cd <repo>
export BUILD_COMMIT="$(git rev-parse --short=10 HEAD)"
docker compose up -d --build --wait
  1. Keep the --wait. Without it Compose exits 0 as soon as the containers have been created, not when they are serving — so an instance with a bad secret gives you a clean-looking up while the api crash-loops behind it.
  2. The export is the one thing this page cannot do for you. BUILD_COMMIT is what GET /server-info reports, and it is stamped into the image at build time from the checkout you are standing in; a browser cannot read your git history. Without it your instance reports unknown, which is cosmetic but leaves you unable to tell which commit a running instance is on. Re-run the line after every git pull.
  3. Running the staging profile? The last line becomes docker compose --profile staging up -d --build --wait.
  4. Then check the API is answering: curl 127.0.0.1:4100/health (or whichever port you chose) should return JSON with "status":"ok". The server is now running and completely empty — sign up through the app, and see docs/SELF_HOSTING.md §12 to make that first account an operator. That is the one setting that cannot be made before first run: it wants a User.id, and no users existed yet.