Skip to main content

Documentation Index

Fetch the complete documentation index at: https://manifest.build/docs/llms.txt

Use this file to discover all available pages before exploring further.

Run the full Manifest stack on your own machine. No Node.js required, just Docker. All three paths end in the same place: a running stack at http://localhost:2099 where you sign up. The first account you create becomes the admin. No demo credentials are pre-seeded.
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

Verify

After connecting a provider, send a test request and watch it land in the dashboard. Grab your Manifest API key from the dashboard (it starts with mnfst_) and run:
curl -X POST http://localhost:2099/v1/chat/completions \
  -H "Authorization: Bearer mnfst_YOUR_KEY_HERE" \
  -H "Content-Type: application/json" \
  -d '{"model": "manifest/auto", "messages": [{"role": "user", "content": "Hello"}]}'
If the response comes back with That doesn't look like a Manifest key, you’re still using the placeholder — replace mnfst_YOUR_KEY_HERE with the real key from the dashboard.

Verifying the image signature

Published images are signed with cosign keyless signing (Sigstore). Verify before pulling:
cosign verify manifestdotbuild/manifest:<version> \
  --certificate-identity-regexp="^https://github.com/mnfst/manifest/" \
  --certificate-oidc-issuer="https://token.actions.githubusercontent.com"

Custom port

If port 2099 is taken, change both the mapping and BETTER_AUTH_URL:
docker run -d \
  -p 8080:2099 \
  -e BETTER_AUTH_URL=http://localhost:8080 \
  ...
Or in docker-compose.yml:
ports:
  - '127.0.0.1:8080:2099'
…and in .env:
BETTER_AUTH_URL=http://localhost:8080
If you see an “Invalid origin” error on the login page, BETTER_AUTH_URL doesn’t match the URL you’re accessing the dashboard on. The host matters as much as the port.
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 to 127.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

Change the port binding

Edit docker-compose.yml and change the ports line from "127.0.0.1:2099:2099" to "2099:2099".
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”.
3

Apply

docker compose up -d

Image tags

Every release is published with the following tags:
TagExampleDescription
major.minor.patch5.46.0Fully pinned
major.minor5.46Latest patch within a minor
major5Latest minor+patch within a major
latestLatest stable release
sha-<short>Exact commit for rollback
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:
docker compose pull
docker compose up -d
Database migrations run automatically on boot, no manual steps. Your data in the pgdata volume is preserved across upgrades. Pin to a specific major version (e.g. manifestdotbuild/manifest:5) in docker-compose.yml if you want control over when major upgrades happen.

Backup and persistence

All state lives in the pgdata named volume mounted at /var/lib/postgresql/data in the postgres service. Nothing else in the Manifest container is stateful. Back up (from the host, with the stack running):
docker compose exec -T postgres pg_dump -U manifest manifest > manifest-backup-$(date +%F).sql
Restore into a fresh stack:
docker compose up -d postgres
cat manifest-backup.sql | docker compose exec -T postgres psql -U manifest manifest
docker compose up -d
To list or remove the volume manually:
docker volume ls | grep pgdata
docker compose down -v    # destroys all data

Environment variables

Core
VariableRequiredDefaultDescription
DATABASE_URLYesPostgreSQL connection string
BETTER_AUTH_SECRETYesSession signing secret (min 32 chars)
BETTER_AUTH_URLNohttp://localhost:2099Public URL. Set this when using a custom port
PORTNo2099Internal server port
NODE_ENVNoproductionNode environment
SEED_DATANofalseSeed demo data on startup
Network and security
VariableDefaultDescription
BIND_ADDRESS127.0.0.1Bind address
CORS_ORIGINAllowed CORS origin
API_KEYInternal API key
Rate limiting
VariableDefaultDescription
THROTTLE_TTL60000Rate limit window in ms
THROTTLE_LIMIT100Max requests per window
Default: 100 requests per 60-second window.LLM proxy
VariableDefaultDescription
PROVIDER_TIMEOUT_MS180000Per-attempt timeout (ms) for upstream provider requests. Set strictly below your client’s timeout so the fallback chain has room to run. Slow local models may need this raised. Non-numeric, zero, or negative values fall back to the default.
Email alerts (Mailgun)
VariableDescription
MAILGUN_API_KEYMailgun API key
MAILGUN_DOMAINMailgun domain
MAILGUN_FROMSender address for alerts
OAuth providers
VariableDescription
GOOGLE_CLIENT_ID / GOOGLE_CLIENT_SECRETGoogle OAuth
GITHUB_CLIENT_ID / GITHUB_CLIENT_SECRETGitHub OAuth
DISCORD_CLIENT_ID / DISCORD_CLIENT_SECRETDiscord OAuth
Full env var reference: github.com/mnfst/manifest

Stop and clean up

docker compose down       # Stop services (keeps data)
docker compose down -v    # Stop and delete all data

Telemetry

Once a day, each install sends us a small anonymous report. That’s how we know whether anyone’s actually using the thing, and which providers are popular enough to deserve more work. It’s aggregates, never content: no prompts, no messages, no keys, nothing tied to a user. Thirteen fields total.

What gets sent

FieldExamplePurpose
schema_version1So the shape can grow without breaking old clients
install_idrandom UUIDv4Count distinct installs. Generated once on first boot, persisted, never rotated
manifest_version5.47.0Version adoption across the fleet
messages_total1284Daily activity per install
messages_by_provider{"anthropic": 700, "openai": 500}Provider mix. Anything we don’t recognize collapses to "custom", so self-hosted provider names and URLs stay local
messages_by_tier{"simple": 800, "standard": 400, ...}Routing tier usage
messages_by_auth_type{"api_key": 1200, "subscription": 84}API key vs. paid-subscription usage
tokens_input_total1_450_000Volume-weighted signal
tokens_output_total890_000Same
agents_total4Configuration scale
agents_by_platform{"openclaw": 3, "hermes": 1}Which agent clients people use
platformlinux / darwin / windowsOS distribution
archx64 / arm64Architecture distribution

Never sent

Tenant IDs, user IDs, emails, API keys, prompts, message contents, model names, custom provider URLs, OAuth client IDs, hostnames, raw IPs. The ingest takes a SHA-256 of your IP and throws the original away; we keep the hash so we can rate-limit bad actors without knowing where they actually live.

When

  • Once every 24 hours, per install.
  • The first report is delayed by a random 0–24h offset, so a fleet of containers rebooted together doesn’t all hit the endpoint at the same minute.
  • Off by default when NODE_ENV != production. Dev machines are never going to accidentally send.
  • If the endpoint is down, we log it and try again on the next hourly tick. Your proxy keeps serving requests — the sender never gets in the way.

Turning it off

Put this in your .env (or docker-compose.yml) and restart the container:
MANIFEST_TELEMETRY_DISABLED=1
The sender checks the flag before doing anything else. No database read, no DNS lookup, no request leaves the box.

Sending it somewhere else

If you’d rather run your own fleet dashboard, point TELEMETRY_ENDPOINT at a URL you control:
TELEMETRY_ENDPOINT=https://telemetry.mycompany.internal/v1/report

Docker Hub

The image is available at manifestdotbuild/manifest on Docker Hub.