Documentation
Self-host guide
There are two ways to run PoietesCMD: with Docker Compose (the intended deployment), or directly with Node for development.
Docker Compose
You need Docker with the Compose plugin.
1. Configure
cp .env.example .env
Open .env and set three values. Generate each secret with:
node -e "console.log(require('crypto').randomBytes(32).toString('base64'))"
| Variable | What it is |
|---|---|
POSTGRES_PASSWORD | Password for the bundled PostgreSQL. It is placed in a connection URL, so use letters and digits only. |
PCMD_ENCRYPTION_KEY | 32 random bytes (base64 or 64 hex characters). Encrypts provider keys stored through the console. Back it up separately from the database. |
PCMD_PROXY_SECRET | Shared secret between the console and the API. |
Provider keys can go in .env too (OPENAI_API_KEY, ANTHROPIC_API_KEY). See Provider setup.
2. Start
docker compose up -d --build
This builds two images and starts four services: postgres, api, worker and web. The API applies database migrations when it starts. Only web is published, on 127.0.0.1:3000 by default.
3. Create the owner account
There is no default password. Until an owner exists, the API prints a one-time setup token when it starts:
docker compose logs api
Look for the line starting with [setup] Setup token:. Open http://localhost:3000/setup, enter the token and choose a password of at least 12 characters. The token changes every time the API starts until setup is complete. To fix it in advance instead, set PCMD_SETUP_TOKEN in .env.
4. Connect a model and run a task
Open Settings → Providers, add a connection and run its test. Then create a task from Tasks → New task. Until a connection has passed its test, the console says "Connect a model to run your first task" and nothing can be queued.
Exposing the console
By default the console listens on this machine only. To reach it from elsewhere, put a reverse proxy with TLS in front of it and tell PoietesCMD its public address:
PCMD_PUBLIC_ORIGIN=https://agent.example.com
With that set, the session cookie is marked Secure and state-changing requests from any other origin are rejected. Your reverse proxy should set X-Forwarded-For and X-Forwarded-Proto; per-address login throttling relies on the first of those.
A minimal Caddy example:
agent.example.com {
reverse_proxy 127.0.0.1:3000
}
Updating
docker compose up -d --build
Migrations are applied on start. Running tasks go back to the queue when the worker stops and continue when it is back.
Useful commands
docker compose ps # state and health of the four services
docker compose logs -f worker # what the worker is doing
docker compose stop worker # tasks wait; nothing is lost
docker compose down # stop everything, keep the volumes
Development without Docker
You need Node 22 or newer and pnpm 9.
pnpm install
pnpm dev
pnpm dev starts a local PostgreSQL 17 on 127.0.0.1:15791 (real PostgreSQL binaries from the embedded-postgres package, data in .local/pg-dev), the API on port 19320, the worker, and the console on http://localhost:19300. The setup token appears in the output, prefixed with [api].
To use your own PostgreSQL instead, set DATABASE_URL in .env.
Other commands:
| Command | What it does |
|---|---|
pnpm test | Server tests against a real PostgreSQL 17 |
pnpm e2e:local | The six end-to-end workflows against real API and worker processes |
pnpm typecheck | TypeScript in all packages |
pnpm build | Production build of the server and the console |
pnpm migrate | Apply database migrations and exit |
pnpm model:double | A deterministic stand-in for a model endpoint, for trying the console without a key |
pnpm smoke:provider | One real request to a real provider, using keys from your environment |
Configuration reference
Every variable is described in .env.example. The ones that change behaviour most:
| Variable | Default | Effect |
|---|---|---|
PCMD_WORKER_CONCURRENCY | 2 | Tasks one worker runs at once |
PCMD_LEASE_SECONDS | 60 | How long after a worker dies its task can be taken over |
PCMD_HEARTBEAT_SECONDS | 10 | Worker heartbeat; also how fast cancel reaches a running task. Use the same value for the API and the worker |
PCMD_SCHEDULER | true | Run the scheduler in this worker |
PCMD_MODEL_TIMEOUT_MS | 300000 | Longest single model request |
PCMD_FETCH_ALLOWED_PORTS | 80,443 | Ports fetch_url may contact |
PCMD_SESSION_TTL_HOURS | 168 | Session lifetime without activity |
Where data lives
| Data | Location in Compose |
|---|---|
| Tasks, events, memory, schedules, settings, encrypted keys | pgdata volume |
| Workspace files | data volume, /data/workspace |
| Generated artifacts | data volume, /data/artifacts |
To let the agent work on a folder of your own, mount it as the workspace. In docker-compose.yml the api and worker services share the x-server block at the top, so one change covers both:
x-server: &server
# …
volumes:
- data:/data
- /path/on/host/documents:/data/workspace
The API needs write access to it (uploads, deletions); the worker only reads it.
See Backup and restore for what to back up.