Self-hosted, cross-instance observability and management layer for n8n.
Connect one or more n8n instances; Manifest syncs their automations and surfaces the metadata n8n itself doesn't track — ownership, dependencies, service usage, execution consumption, and organizational structure — across all of them in a single UI.
Source of truth: this page is published from
[README.dockerhub.md](https://github.com/n8n-io/manifest/blob/main/README.dockerhub.md)in the repo. For contributor/build-from-source docs see the main README.
insights:read scope — see below.)executeWorkflow nodes, webhook
triggers, and message queues; augmentable with manual entries.Manifest ships as two version-matched images that run together:
| Image | Role |
|---|---|
jann8n/manifest | The API server (Hono + SQLite). This image. |
jann8n/manifest-web | Caddy + the built SPA. Serves the UI and reverse-proxies /api to the server. |
Both are published from the same release at the same version. Running the
server image alone gives you an API with no UI — use the turn-key compose stack
below, which pulls both.
The turn-key path pulls both images via one pinned version. Nothing is built on the host.
1. Get the compose file ([docker-compose.prod.yml](https://github.com/n8n-io/manifest/blob/main/docker-compose.prod.yml)):
curl -fsSLO https://raw.githubusercontent.com/n8n-io/manifest/main/docker-compose.prod.yml
2. Create .env next to it:
cat > .env <<EOF
MANIFEST_IMAGE_NAMESPACE=jann8n # Docker Hub namespace the images live under
MANIFEST_VERSION=1.0.0 # pin a released version (not 'latest')
DOMAIN=manifest.example.com # the FQDN you'll serve from
AUTH_SECRET=$(openssl rand -hex 32) # signs session cookies
APP_SECRET=$(openssl rand -hex 32) # encrypts stored n8n API keys — BACK THIS UP
EOF
AUTH_SECRETandAPP_SECRETare fail-loud: the compose file uses${VAR:?}and the server's own startup guard refuses to boot if either is missing, so an empty secret never reaches the container. Back upAPP_SECRET— losing it makes every stored n8n API key unrecoverable.
3. Start the stack:
docker compose -f docker-compose.prod.yml up -d
Caddy provisions a Let's Encrypt certificate for DOMAIN automatically (point
its DNS at the host and open ports 80 + 443). No public domain? Set
CADDY_TLS=tls internal in .env for a locally-trusted self-signed cert.
4. Open https://<DOMAIN>/setup to create the first admin account.
Upgrade: bump MANIFEST_VERSION in .env, then
docker compose -f docker-compose.prod.yml pull && docker compose -f docker-compose.prod.yml up -d.
Migrations run automatically at server start against the existing data volume.
After setup, register a connection: paste the n8n base URL and an API key.
GET endpoints and
never writes back to n8n — a read-only key means even a Manifest breach can't
alter your automations.insights:read. Manifest reads each
instance's insights/summary. A key without the insights:read scope
returns 403, and that instance shows "Key missing scope" on the
/consumption page with a fix hint (it is not silently dropped). On
non-Enterprise n8n tiers (Community / Cloud / Pro) keys carry full account
access, so insights:read is already covered; on Enterprise scoped keys,
add it explicitly alongside the other read scopes.Then hit Sync now to pull automations, projects, and execution data.
Set in .env next to the compose file. ALLOWED_ORIGIN, TRUSTED_ORIGINS, and
BASE_URL are derived from DOMAIN by the compose file — don't set them
yourself. Full reference:
[.env.example](https://github.com/n8n-io/manifest/blob/main/.env.example).
Unique values of AUTH_SECRET and APP_SECRET can be generated via command: openssl rand -hex 32
| Variable | Required | Purpose | Default |
|---|---|---|---|
MANIFEST_IMAGE_NAMESPACE | ✅ | Docker Hub namespace both images live under | — |
MANIFEST_VERSION | ✅ | Released version to pin; drives both images | — |
DOMAIN | ✅ | FQDN served (no scheme/path); derives the origin/CORS vars | — |
AUTH_SECRET | ✅ | Signs session cookies (openssl rand -hex 32) | — |
APP_SECRET | ✅ | Derives the key encrypting stored n8n API keys — back it up | — |
CADDY_TLS | — | Empty = auto Let's Encrypt; tls internal = self-signed cert | (empty) |
BLOCK_PRIVATE_SOURCE_URLS | — | Reject n8n URLs on loopback/private ranges (SSRF hardening) | false |
AI_API_KEY | — | Enables AI Organize; unset disables AI features | (empty) |
AI_PROVIDER | — | LLM provider | claude |
AI_MODEL | — | Model override | (provider default) |
LOG_LEVEL | — | debug | info |
Ports: the web image publishes 80 and 443. The server image listens on 3000 internally (not published in the production stack).
Persistence: the SQLite database lives at /app/data/manifest.db, mounted to
the host ./data directory next to the compose file. Caddy's certificates/ACME
state live in the named volumes caddy_data / caddy_config — **back up
./data**, and don't casually delete caddy_data (re-requesting certs hits the
Let's Encrypt rate limit).
Each release publishes the same tags to both repositories
(jann8n/manifest and jann8n/manifest-web):
X.Y.Z — the exact release (pin this via MANIFEST_VERSION)X.Y — the latest patch on that minor linesha-<short> — the source commitlatest — moves only when the release is the highest semverPin a concrete X.Y.Z. latest is a moving tag: a pull that races a release
could fetch a new server latest against an older web latest, breaking the
version-match invariant the single MANIFEST_VERSION exists to protect.
Architecture: **linux/amd64 only**, for both images. arm64 is a deliberate
follow-on (better-sqlite3 is a native addon; a QEMU-emulated arm64 build is slow
and can ship a broken binary).
Sustainable Use License v1.0 - © n8n GmbH. You may use and modify the software for your own internal business purposes or for non-commercial/personal use, and distribute it free of charge for non-commercial purposes. See the [LICENSE.md](https://github.com/n8n-io/manifest/blob/main/LICENSE.md) for the
full terms.
Content type
Image
Digest
sha256:a3cda247e…
Size
236.3 MB
Last updated
about 19 hours ago
docker pull jann8n/manifest