Skip to content
On this page

Self-Hosted Deployment

A self-hosted deployment runs the same product as the cloud service, in a Docker Compose stack on servers you control. Your users still sign in through the portal, and the stack checks in with the portal for licensing. Boards, sources, chat, and the database stay inside your network.

Important

Self-hosting is available on the Enterprise plan. Once your organization is on Enterprise, our team provisions and approves an on-premises deployment for it. The approval unlocks the Self-hosted page in the portal and lets your servers pull images.

How it works

The container images live in a private registry. Once running, the stack keeps working for up to 24 hours if the portal is unreachable.

How a self-hosted deployment gets access, pulls, runs and reports in.

If the subscription lapses, running containers keep running, but the portal refuses the next registry sign-in and you can't pull a new release until billing is resolved.

What you'll need

ResourceMinimum
CPU2 cores (4+ for teams over 10)
RAM4 GB (8 GB+ recommended)
Disk20 GB free
OSLinux x86_64, macOS 12+, or Windows 10/11 with Docker Desktop
Docker20.10+ with Compose v2
Toolscurl and jq on Linux and macOS (the registry sign-in script uses them)
DatabasePostgreSQL 14+, reachable from the host — see the note below

The images target linux/amd64. Docker Desktop on Apple Silicon runs them under emulation, which works for evaluation. Linux ARM64 servers are not supported.

Important

You bring your own PostgreSQL: the compose file doesn't run a database. Before you start the stack, prepare a reachable managed or self-run instance and its credentials.

Outbound access

The host needs HTTPS (port 443) to all three of these. A firewall that allows the first two and blocks the third makes a pull stall at 0%.

DestinationPurpose
1yv8vsjuzd.execute-api.us-east-1.amazonaws.comPortal API: registry sign-in, licensing, heartbeat
833239388352.dkr.ecr.us-east-1.amazonaws.comImage registry: manifests and authentication
*.s3.us-east-1.amazonaws.comImage layers, served from S3 in us-east-1

Get your deployment credentials

  1. Our team provisions an on-premises deployment for your organization and approves it. Until then, the portal's Self-hosted page shows a contact card instead of the installer.
  2. Sign in to portal.provenmap.com as an organization admin and open Self-hosted.
  3. Select Create secret. Copy the App ID and the secret into your secrets manager.
Warning

The page shows the secret once. Recreate secret on the same page invalidates the old one for both the heartbeat and registry sign-in. After recreating it, update .env and run the sign-in script again.

Install

Installing takes about ten minutes on a host that meets the requirements.

When your deployment is approved, our team sends you the install kit: the compose file, nginx.conf, .env.template, the sign-in scripts, and the pre-flight script.

1. Unpack the kit. Put its files in ~/provenmap.

BASH
cd ~/provenmap

2. Check the host. The script verifies Docker, memory, disk, port 80, jq, and whether all three outbound hosts are reachable.

BASH
bash preflight.sh

3. Configure. Copy the template, then set the three required values.

BASH
cp -n .env.template .env
nano .env
BASH
PMAP_APP_ID=cd_your_app_id
PMAP_APP_SECRET=ck_live_your_secret
PMAP_DATABASE_PASSWORD=YourSecurePassword123!
PMAP_DATABASE_HOST=db.internal.example.com

4. Sign in to the image registry. The script exchanges your credentials for 12 hours of pull access and, on first run, pins PMAP_VERSION to the current release.

BASH
./registry-login.sh
TEXT
  ok      Signed in to 833239388352.dkr.ecr.us-east-1.amazonaws.com
  ok      Pull access until 2026-09-02T21:14:09.000Z
  ok      Pinned PMAP_VERSION=1.4.8 in .env

5. Start the stack.

BASH
docker compose -f docker-compose.production.yml pull
docker compose -f docker-compose.production.yml up -d

The first start downloads roughly 1 GB of images and takes two to three minutes.

Tip

Pull access lasts 12 hours. Run the sign-in script again before any docker compose pull. Starting or restarting containers you already have does not need it.

Verify the installation

BASH
docker compose -f docker-compose.production.yml ps
curl http://localhost/health
curl http://localhost/api/status

All three containers (provenmap-proxy, provenmap-web, and provenmap-server) report Up (healthy). The health check returns {"status":"ok"}, and the status endpoint reports "license": { "status": "active" } after the server checks in with the portal.

Open http://localhost (or your configured port), select Sign In, and sign in with your portal account. Every user needs a portal account and membership in your organization.

Configuration

Settings live in .env next to the compose file. Restart the affected container after a change: docker compose -f docker-compose.production.yml up -d --force-recreate server.

Required

VariableValue
PMAP_APP_IDThe App ID from the Self-hosted page
PMAP_APP_SECRETThe secret from the Self-hosted page
PMAP_DATABASE_PASSWORDPassword for your PostgreSQL user
PMAP_VERSIONThe exact release to run, for example 1.4.8. The sign-in script writes it on first run

There is no latest tag. Releases are immutable, and your deployment runs exactly the release named here until you change it.

Database

VariableDefaultNotes
PMAP_DATABASE_HOSTlocalhostHostname of your PostgreSQL instance
PMAP_DATABASE_PORT5432
PMAP_DATABASE_USERprovenmap
PMAP_DATABASE_NAMEprovenmap
PMAP_DATABASE_SSLunsetSet to require for managed databases that enforce TLS

The server runs migrations automatically when it starts. They are forward-only and hold an advisory lock, so several restarts in a row are safe.

Connection pool and platform behavior
VariableDefaultNotes
PMAP_DATABASE_MAX_CONNECTIONS10Pool size per server container
PMAP_DATABASE_IDLE_TIMEOUT30000Milliseconds before an idle connection closes
PMAP_DATABASE_CONNECTION_TIMEOUT5000Milliseconds to wait for a connection
PMAPP_OFFLINE_TTL86400Seconds cached entitlements stay valid while the portal is unreachable
PMAPP_HEARTBEAT_INTERVAL3600Seconds between heartbeats
PMAPP_LOG_LEVELinfodebug, info, warn, or error
PMAP_EXIT_ON_FAILUREtrueExit at startup when the portal is unreachable and no valid cache exists

Optional

VariableDefaultNotes
PMAP_PORT80External port of the bundled nginx proxy
PMAP_PORTAL_URLhttps://1yv8vsjuzd.execute-api.us-east-1.amazonaws.com/productionThe portal API. Leave the default — https://portal.provenmap.com is the web app and has no API routes

Chat and in-app insight generation use a model provider, connected once from Settings → Connectors. Plugin commands such as /analyze, /sync, and /insights do not require one.

You can move icon storage from the local filesystem to S3; see Organization Settings. Plugins and MCP clients point at your instance with PMAP_BASE_URL and PMAP_MCP_URL; see Authentication.

Note

On request, we provide trial credits for early usage before you connect your own model provider. We bill them to you, not your users.

Upgrade and roll back

The Self-hosted page in the portal shows the current release and whether your deployment is up to date. To upgrade, change PMAP_VERSION in .env:

BASH
cd ~/provenmap
./registry-login.sh        # prints "Release 1.4.9 is available; you are pinned to 1.4.8"
nano .env                  # PMAP_VERSION=1.4.9
docker compose -f docker-compose.production.yml pull
docker compose -f docker-compose.production.yml up -d

Back up the database first (below). Expect one to two minutes of downtime while containers restart.

To roll back, set PMAP_VERSION to the previous release and run the same pull and start. Database migrations don't roll back. If a release's migration fails, restore your pre-upgrade backup, pin the previous release, and contact us.

Note

We keep recent releases in the registry, not every release ever shipped. If your deployment is pinned to a release that has aged out, you can't pull it onto a new host. Upgrade before you migrate hosts, or contact support.

Back up

Two things hold state: your PostgreSQL database and the provenmap-data volume (sessions, cache, and the cached license).

Back the database up with your usual PostgreSQL process. From the host:

BASH
pg_dump -h db.internal.example.com -U provenmap provenmap | gzip > backups/db-$(date +%Y%m%d).sql.gz

Back the data volume up as a tar archive:

BASH
docker run --rm -v provenmap-data:/data -v "$(pwd)/backups:/backup" \
  alpine tar czf /backup/data-$(date +%Y%m%d).tar.gz /data

If you lose the data volume, the stack rebuilds it from the portal on the next start. Nothing rebuilds the database, so back it up daily and before every upgrade.

SSL and reverse proxies

The stack includes an nginx container on port 80. You have two options for HTTPS:

  • Terminate TLS on your load balancer and forward to port 80. Nothing in the stack changes.
  • Replace nginx.conf with your own configuration, or route straight to the containers from your existing proxy (Traefik, Caddy, HAProxy). Use this routing table:
PathDestinationNotes
/api/*server:3001
/healthserver:3001
/platform/*server:3001
/.well-known/*server:3001
/socket.io/*server:3001Needs WebSocket upgrade headers
/*web:3000Frontend, lowest priority

Keep ports 3000 and 3001 off the public internet; only the proxy should reach them.

Troubleshooting

The sign-in script prints FAIL … [code]

The portal refused registry access. The code in brackets says why.

CodeMeaningFix
deployment_not_approvedYour deployment is not approved yetWait for approval; we email you when it's approved
subscription_inactiveThe subscription does not grant access (past_due, cancelled)Fix billing in the portal. Running containers keep working; new pulls resume once billing is fixed
not_on_premThe credentials belong to a cloud deploymentUse the App ID and secret from the Self-hosted page
deployment_disabledThe deployment was disabledContact support
deletion_pendingThe organization is being deletedContact support if this is unexpected
release_not_publishedNo release is published for self-hostingThe problem is on our side. Contact support
registry_unavailableThe registry did not respondRetry in a few minutes

Invalid deployment credentials means PMAP_APP_ID or PMAP_APP_SECRET is wrong, or someone recreated the secret in the portal. Too many requests means more than ten sign-ins a minute from one address; wait a minute.

denied: Your authorization token has expired

Pull access lasts 12 hours from sign-in. Sign in again and retry:

BASH
./registry-login.sh
docker compose -f docker-compose.production.yml pull
… did not answer as the Portal API

PMAP_PORTAL_URL points at https://portal.provenmap.com, which is the web app. Remove the line from .env so the default applies, or set it to the API host from the outbound access table. Then recreate the server container so it picks up the change:

BASH
docker compose -f docker-compose.production.yml up -d --force-recreate server
Pull stalls at 0% after a successful sign-in

The registry answered, but image layers download from S3 and your firewall blocks it. Allow HTTPS to *.s3.us-east-1.amazonaws.com, the regional pattern, not only *.s3.amazonaws.com. Confirm from the host: any HTTP status means S3 is reachable, and a connection error means it is still blocked:

BASH
curl -sI --max-time 10 https://s3.us-east-1.amazonaws.com/ | head -1
The server exits at startup with "Portal unreachable"

The server needs the portal API on its first start and whenever its cached license is older than PMAPP_OFFLINE_TTL. Check outbound access to the API host from inside the container:

BASH
docker compose -f docker-compose.production.yml exec server \
  node -e "fetch(process.env.PMAP_PORTAL_URL + '/platform/health').then(r => console.log(r.status))"

A 200 means the network is fine and the credentials are the problem. Anything else is a firewall or proxy rule.

Port 80 is already in use

Set PMAP_PORT in .env to a free port and run docker compose -f docker-compose.production.yml up -d. The stack is then at http://localhost:<port>.

What's next