The bundled compose file binds port 2099 to
127.0.0.1 only, so the dashboard is reachable on the host machine but not over the LAN. See Exposing on the LAN to change this.Installation
- Quick install (recommended)
- Docker Compose
- Docker Run (BYO PostgreSQL)
One command. The installer downloads the compose file into
Useful flags:
~/manifest, generates the secrets, and brings up the stack. First boot pulls the app image and Postgres, so give it up to a couple of minutes.Prefer to review the script before running it?
Prefer to review the script before running it?
--dir <path> to install elsewhere, --port <n> to serve on a port other than 2099, --dry-run to preview, --yes to skip the confirmation prompt.Re-running the installer against an existing install directory resumes it. The compose file and your generated secrets are left untouched.When the installer finishes, open http://localhost:2099: a setup screen asks you to create the admin account. Then connect a provider and send your first request — see First request.First request
Signing up leaves you with an empty instance. Three steps to a routed request.1
Connect a provider
In the dashboard sidebar, open Providers and pick how you want to connect:
- Usage-based — paste an API key (OpenAI, Anthropic, Gemini, DeepSeek, …)
- Subscriptions — reuse a plan you already pay for (ChatGPT, Claude, GLM Coding Plan, …)
- Local — Ollama, LM Studio, or llama.cpp running on the host
2
Copy your harness's key
Every harness has its own key, shown when you create it and again under the harness’s Settings. It starts with
mnfst_.3
Send a request
The endpoint is OpenAI-compatible, so any SDK or agent that accepts a base URL works — point it at
http://localhost:2099/v1 with the mnfst_ key. To check it end to end:"model": "auto" asks Manifest to route the request. Any other name is treated as an explicit choice: if the model isn’t available to your harness, the request fails with M302. Send auto to use routing.Errors raised by Manifest itself carry an
M### code, a plain-English cause, and a link to the matching page under manifest.build/docs/errors. The three you are most likely to see on a fresh install:Verifying the image signature
Published images are signed with cosign keyless signing (Sigstore). Verify before pulling:Custom port
If port 2099 is taken, setPORT in .env. That is the whole change:
${PORT:-2099} for both the published host port and the backend’s internal listener, and BETTER_AUTH_URL defaults to http://localhost:${PORT:-2099} — so one line covers all three, with no YAML edit. The install script writes it for you if you pass --port 8080.
For a docker run install there is no .env, so pass the mapping and the URL explicitly. The container keeps listening on 2099 and Docker remaps it:
Upgrading from a pre-2099 install? Your existing stack keeps running on port 3001 with no changes — the backend’s own fallback is still
3001, so the new image works against your old compose file. If you want to refresh your compose file but stay on the legacy port (to avoid reconfiguring OAuth callbacks, reverse proxies, or bookmarks), set PORT=3001 in .env and the bundled compose file will honour it for both the host binding and the internal listener.Exposing on the LAN
By default the compose file binds port 2099 to127.0.0.1 only. The dashboard is reachable from the host but not from other machines on the network. To expose it on the LAN:
1
Bind to all interfaces
In Editing
.env, set:docker-compose.yml by hand does not survive an upgrade; .env does. If you exposed the LAN by editing the ports: line in docker-compose.yml, move that change to HOST_BIND_ADDRESS here: bash install.sh --upgrade replaces the compose file and your LAN access goes with it.2
Set BETTER_AUTH_URL
In
.env, set BETTER_AUTH_URL to the host you’ll reach the dashboard on, e.g. http://192.168.1.20:2099 or https://manifest.mydomain.com. This must match the URL in the browser or Better Auth will reject the login with “Invalid origin”. If you sign in with Google, GitHub or Discord, update the redirect URI in the provider’s console to ${BETTER_AUTH_URL}/api/auth/callback/<provider>, or the provider rejects the login.3
Apply
BETTER_AUTH_URL still points at localhost. The admin account is created, then sign-in fails with “Invalid origin” and drops you back on the login page. The setup screen does not come back, because the account now exists. That account is fine. Set BETTER_AUTH_URL to the URL you type in the browser, run docker compose up -d, then sign in with it.
Image tags
Every release is published with the following tags:
Images are built for both
linux/amd64 and linux/arm64.
Upgrading
Manifest ships a new image on every release. To upgrade an existing compose install:pgdata volume is preserved across upgrades. To control when major upgrades happen, pin the image tag with MANIFEST_VERSION in .env, for example MANIFEST_VERSION=6, then run the two commands above. Unset, the compose file pulls latest. Pin here rather than in docker-compose.yml, which bash install.sh --upgrade replaces.
The two commands above only pull new images. Your docker-compose.yml stays as it is, hand edits included. bash install.sh --upgrade is the other path: it downloads a fresh docker-compose.yml and replaces yours, after copying the old one next to it as docker-compose.yml.backup.<UTC timestamp>. Your .env is left alone. Keep custom compose changes in docker-compose.override.yml, which the installer never downloads.
Upgrading from before v6.18.0 and using logs? Run the installer once with the upgrade flag, or log bodies are written inside the container and lost when it is recreated:(
--upgrade looks for the install in ~/manifest. Installed elsewhere? Pass --dir /your/path, otherwise the script stops with “No installation found at …”.)It adds the manifest_request_recordings volume to your compose file. Later releases include it from the start.Hand-managed compose file? Add the volume yourself
Hand-managed compose file? Add the volume yourself
Declare the named volume and mount it on the backend service:
Backup and persistence
The stack uses two named volumes:
The database volume is the one to back up. Losing the recordings volume costs you stored message bodies and nothing else, and those expire on a retention schedule regardless.
Back up (from the host, with the stack running):
Environment variables
Core
The first two are required and the installer generates both, so a default install boots without you setting anything.
Everything else is optional: provider timeouts, email delivery for alerts and password resets, OAuth logins, connection-pool sizing, recording storage and retention, Autofix, and Sentry. See Environment variables for the full list with defaults.
NODE_ENV and SEED_DATA are fixed by the bundled compose file and are not knobs for a self-hosted install. The image is a production artifact, and the demo-data seeder refuses to run under NODE_ENV=production whatever SEED_DATA says — use the first-run setup wizard to create your admin account.BIND_ADDRESS is likewise set by the image (0.0.0.0, so the container is reachable through Docker’s port mapping); control host exposure with HOST_BIND_ADDRESS in .env, not this variable.Stop and clean up
Data and privacy
Manifest always keeps metadata about each request — model, provider, tier, token counts, cost, latency. Message bodies are separate and optional: with request logs on for a harness, prompts and completions are stored too, in the recordings volume rather than the database. New harnesses have it enabled. See Data and telemetry.Telemetry
Once a day, each install sends an anonymous aggregate report: version, provider mix, token and cost totals. Never prompts, keys, or anything tied to a user. SetMANIFEST_TELEMETRY_DISABLED=1 in your .env to turn it off.
Full field list, what’s never sent, and how to point it at your own endpoint: Data and telemetry.