Skip to main content
astilba’s server and dashboard live in the repo (Apache-2.0) and are self-hostable today.
Self-hosting is Alpha. There is no production container image or deploy artifact yet — you run it from source. There is also no self-service sign-up UI: the first user and organization are created by the seed script (dev) or Better Auth’s API (see First user and organization).

Prerequisites

  • Node.js >= 22.14 and pnpm.
  • PostgreSQL — bring your own, or use the one in the repo’s docker-compose.yml for local development.

Local development

The repo’s docker-compose.yml provisions only Postgres (and Adminer, a DB UI) — not the app. Bring the database up, then run the server and dashboard from source.
1

Start Postgres

pnpm dev:db          # docker compose up -d --wait — Postgres on :5432, Adminer on :8080
2

Configure the server

cp apps/server/.env.example apps/server/.env
The example’s DATABASE_URL already matches the compose Postgres. It also sets a dev-only BETTER_AUTH_SECRET — fine locally, but generate a real one for anything else.
3

Migrate and seed

pnpm --filter @astilba/server db:migrate
pnpm --filter @astilba/server db:seed
The seed creates a demo user, organization, project, and API token, and prints the credentials. It refuses a non-local database unless ASTILBA_SEED_ALLOW_REMOTE=1.
4

Run the server and dashboard

pnpm dev             # runs apps/server and apps/dashboard together
Open the dashboard and sign in with the credentials the seed printed. In dev the Vite dev server proxies /api to the server, so the two run on separate ports.

Environment variables

The server validates its environment on start and fails fast with a clear message if anything is missing or malformed.
VariableRequiredDefaultPurpose
DATABASE_URLyesPostgres connection string
BETTER_AUTH_SECRETyesSession-signing key; must be at least 32 characters
BETTER_AUTH_URLnorequest originThe server’s public URL (cookies/redirects)
PORTno3000Port the Node server listens on
ASTILBA_STATIC_DIRnoPath to the built dashboard, for one-origin serving (see below)
Generate a real secret with:
node -e "console.log(require('node:crypto').randomBytes(32).toString('hex'))"
db:migrate and db:seed load apps/server/.env; start reads the environment directly (no .env). For production, either provide a .env with your production DATABASE_URL before migrating, or run the migrate script with the variables set in the environment.

Production (manual)

There’s no image yet, so production is a manual, from-source deploy. The shape:
1

Provision Postgres and secrets

Point DATABASE_URL at your database, set a real BETTER_AUTH_SECRET (>= 32 chars), and set BETTER_AUTH_URL to your public URL.
2

Apply migrations

Run db:migrate against the production database (see the note above about .env).
3

Build the dashboard

pnpm --filter @astilba/dashboard build   # emits apps/dashboard/dist
4

Serve both on one origin

Set ASTILBA_STATIC_DIR to the built dashboard and start the server:
ASTILBA_STATIC_DIR=/path/to/apps/dashboard/dist \
  pnpm --filter @astilba/server start
The server serves the dashboard’s assets and falls back to index.html for client-routed paths, while /api/* is handled first — so a static file can never shadow the API, and the dashboard and API share one origin. Run it behind a TLS-terminating reverse proxy.
The server targets the web-standard runtime, and the code is written to run on Cloudflare Workers (env is injected per request, Postgres via Hyperdrive) — but a Workers deploy is not a shipped artifact yet.

First user and organization

There is no sign-up page. To create the first account:
  • Locally, db:seed does it for you and prints the credentials.
  • Otherwise, use Better Auth’s endpoints under /api/auth/*: register a user, create an organization, then set it as the session’s active organization (Better Auth’s set-active-organization endpoint) — or just sign in again once the membership exists. The active org is chosen when a session is created, so a session made before the org existed has none, and /api/projects / /api/tokens answer No active organization (403) until you set it. With an active org, mint an API token and you’re ready to push/pull.
Hitting a rough edge self-hosting? It’s Alpha — please open an issue.