Local development
Run the whole Sealant stack locally — web app, control-plane API, worker, and a real workspace over SSH.
This page is an overview. The step-by-step runbook —
DEVELOPMENT.md at the repo root
— is canonical; this page won't repeat every command, just the shape of the stack and the parts
worth knowing before you dive in.
Prerequisites
- Docker running — the worker and SSH gateway drive it to build and launch workspaces.
- The Nix dev shell —
direnv allow(ornix develop) gives you Node 24 andpnpm.
Topology
Stateful infra plus the worker and ssh-gateway run in Docker; the API and web app
run on the host with hot reload. A single .env at the repo root is the source of truth — every app
reads it, and compose passes it to the gateway via env_file.
| Service | Where | Port |
|---|---|---|
| postgres / rabbitmq / zot | docker compose up -d | 5433 / 5673 / 5000 |
| api | pnpm --filter @sealant/api dev | 4000 |
| web | pnpm --filter @sealant/web dev | 3000 |
| worker + ssh-gateway | docker compose --profile apps up -d --build | gateway 2222 |
One-time setup
pnpm install
cp .env.example .env # dev defaults already point at local infra
pnpm ssh:setup:dev # only if you want to SSH into workspacespnpm ssh:setup:dev generates .secrets/* keys, writes a Host ws-* block to your SSH config, and
appends the gateway vars — including a shared WORKSPACE_SSH_GATEWAY_TOKEN — to .env.
Running it
docker compose up -d # infra
pnpm db:migrate # schema
pnpm --filter @sealant/db db:seed # default owner (SDK; web users sign up)
pnpm --filter @sealant/api dev # :4000 (terminal 1)
pnpm --filter @sealant/web dev # :3000 (terminal 2)
docker compose --profile apps up -d --build # worker + ssh-gatewayOpen http://localhost:3000, sign up, and create a workspace. The worker builds the image (pushed to the local zot registry) and launches the container — wait until it's running/ready, then:
ssh -F ~/.config/sealant/ssh_config ws-<workspaceId>The gateway authorizes by owner: the principal your SSH key resolves to must match the workspace's owner. See SSH access for how key resolution and authorization work.
Gotchas worth knowing up front
- Worker and ssh-gateway run from a built image. After a code change (or merging
main), rebuild withdocker compose --profile apps up -d --build <service>— a plainrestartruns stale code. db:seedneedsDATABASE_URL. Run it from the Nix shell, or set it inline.- Bare
pnpm devstarts every workspace (web and docs both bind:3000). Scope with--filter. - "Connection closed" right after the SSH banner is authorization, not SSH — check
docker compose logs --tail=5 ssh-gateway. Usually the resolved principal doesn't own the workspace, the key isn't registered, or the workspace isn't running yet.
After you change code
Follow the repo-wide agent defaults in
AGENTS.md: run pnpm format:fix, use
pnpm typecheck (tsgo, not tsc), and never hand-edit pnpm-lock.yaml.
Releasing
Packaging, image builds, and the self-host installer flow are covered in
DEVELOPMENT.md
and in Installer and compose.