Getting started
This guide walks through a local full stack: Postgres in Docker, the Rust gateway, and the Mithril file-explorer UI.
Prerequisites
- Rust (see
rust-toolchain.toml; 1.88+) - Node.js and npm (for Nx, Vite, Orval)
- Docker (for local Postgres)
Dev Container (recommended for onboarding)
If you use VS Code or Cursor with the Dev Containers extension, you can open this repo in the devcontainer defined in .devcontainer/devcontainer.json at the repository root.
What you get:
- A Linux environment with Rust, Node.js, Docker-in-Docker,
lld/clang, andsccachepre-wired for fast builds. - On attach, the devcontainer runs
npm run dev, which starts the full Composegatewayprofile (docker compose --profile gateway up -d --build). - Port forwarding exposes
8080(nginx edge — UI +/api), not Vite:14200. Open the file explorer athttp://127.0.0.1:8080on your host.
If you do not already have a root .env, create it from the template (the gateway profile requires POSTGRES_PASSWORD, ATCR_*, and OAuth variables described below). This only copies when .env is missing:
test -f .env || cp .env.example .envThe devcontainer postCreateCommand runs the same step so a fresh container gets .env automatically when absent.
Environment
The gateway does not embed defaults: every variable below must be set (usually via a root .env loaded by dotenvy).
- Copy the template if you do not already have
.env:
test -f .env || cp .env.example .envAdjust values if needed. The file includes:
Gateway (required):
DATABASE_URL,PUBLIC_BASE_URL,HOST,PORT,CORS_ALLOWED_ORIGINSRLS (local / Compose):
DATABASE_ADMIN_URL(superusersubstratum),DATABASE_URL(loginsubstratum_gateway), andPOSTGRES_PASSWORDso bootstrap can create the gateway role — see.env.exampleDocker Postgres:
POSTGRES_USER,POSTGRES_PASSWORD(required; no default indocker-compose.yml),POSTGRES_DB— must match the bootstrap URL andsubstratum_gatewaypassword
docker compose reads the same .env for variable substitution. Do not define Postgres variables in two files: use the root .env.example only (there is no separate Docker env template with duplicate keys).
Note: The database password is fixed the first time the data volume is created. If you change POSTGRES_PASSWORD or DATABASE_URL later, see Troubleshooting.
1. Start Postgres
From the repository root:
docker compose up -dPostgres listens on host port 15432 (see docker-compose.yml).
Docker Compose: Postgres + gateway + file-explorer + edge (local OAuth)
The gateway Compose profile is the primary local stack for OAuth against a loopback PDS (http://localhost:3000). It starts Postgres, the gateway, the file-explorer Vite dev server, and an nginx edge reverse proxy:
edgeserves/→ Vite and/api+/.well-known→ gateway on a single origin (http://127.0.0.1:8080).- Vite listens on
:14200inside the Compose network only (nginx upstream); do not open that port on the host. - The PDS runs on the host at
http://localhost:3000(not proxied through edge). SetPDS_PUBLIC_URL,PDS_OAUTH_ISSUER_ORIGIN, andPDS_HANDLE_DOMAIN=testin.env(see MODE B in.env.example). Handles look likealice.test.
docker compose --profile gateway up -d --build
# or: npm run devOpen http://127.0.0.1:8080 for the app and OAuth. Use 127.0.0.1, not localhost, so authorize navigations stay cross-site with the PDS at http://localhost:3000. Gateway DATABASE_URL is set automatically in Compose.
For production topology and OAuth / PDS origin rules, see Operations — Production deployment and OAuth and PDS origins.
2. Run the gateway (host / without Compose)
cargo run -p substratum-gatewayThe gateway listens on HOST:PORT from .env (MODE A uses 0.0.0.0:18080). Use this when Postgres is in Docker and you run the binary locally with npm run dev:ui. Set VITE_PUBLIC_APP_ORIGIN=http://127.0.0.1:18080 in apps/file-explorer/.env.development.local so Login redirects off Vite :14200 before OAuth. Loopback PDS OAuth works with PDS_PUBLIC_URL=http://localhost:3000 and the Compose gateway profile at http://127.0.0.1:8080.
3. Regenerate the TypeScript API client (when routes change)
cargo run -p substratum-gateway --bin dump-openapi
npm run api-client:generate4. Run the file-explorer UI
Recommended (Compose + edge): from the repository root:
npm install
npm run devThen open http://127.0.0.1:8080. The devcontainer forwards 8080 only; Vite :14200 is internal to Compose.
Advanced (MODE A — host gateway + Vite on the host): run the gateway with cargo run -p substratum-gateway, then:
npm run dev:uiSet VITE_PUBLIC_APP_ORIGIN in apps/file-explorer/.env.development.local to your gateway origin (see .env.development.local.example). OAuth still requires the browser origin to match PUBLIC_BASE_URL.
Nx shortcuts
If cargo is on your PATH:
npx nx run gateway:serve— same ascargo run -p substratum-gatewaynpm run dev:stack— Docker only:docker compose --profile gateway up -d --buildnpm run openapi:dump— run the OpenAPI dump target
CORS
CORS_ALLOWED_ORIGINS is required (comma-separated).
- MODE B (Compose edge): include
http://127.0.0.1:8080(andhttp://localhost:8080if you use that alias). See commented values in.env.example. - MODE A (host Vite): include the Vite origin, e.g.
http://127.0.0.1:14200andhttp://localhost:14200, when runningnpm run dev:uiagainst a host gateway.
Optional: logging
By default the gateway prints human-readable log lines. For JSON (e.g. log aggregators), set SUBSTRATUM_LOG_JSON=1 or true in .env.
Uncomment or set in .env:
RUST_LOG=info,sqlx=warnDocumentation site (this site)
To work on these docs locally:
npm run docs:devThe project uses VitePress 2 alpha so the docs toolchain stays on a patched Vite 7 line (npm audit clean). When VitePress stable depends on a fixed Vite 7.x, consider pinning a non-alpha release.
Troubleshooting
password authentication failed for user "substratum"
The Docker volume was likely created with an older password (for example when both user and password were substratum). POSTGRES_PASSWORD in Compose only applies on first init.
Option A — reset dev data (simplest)
docker compose down -v
docker compose up -d
cargo run -p substratum-gatewayOption B — keep data
Set POSTGRES_PASSWORD and the password in DATABASE_URL to the password the existing volume was created with.