Self-host the platform
FlowCP runs in one of two deployment modes:
Cloud — the managed, multi-tenant service at flowcp.ai. MCP requests are metered against your plan, and your plan comes from your Stripe subscription.
Self-Hosted — you run the whole FlowCP platform yourself from a single all-in-one Docker image. MCP requests are unlimited (no metered quota), and your plan comes from a signed license key.
Both modes ship the same product and the same plans (Starter, Team, Business, Enterprise). Features are gated by plan, not by mode — see Plans & features for the full matrix.
This is not the same as per-server export. Export & self-host your MCP server exports one published server as a portable standard FlowCP folder and runs just its MCP endpoint on the standalone runtime — no dashboard, no database. The guide you're reading now runs the entire platform (dashboard + control-plane API + MCP runtime, plus Postgres and Redis) on your own infrastructure, so you can connect apps, generate tools, configure OAuth, and publish servers exactly as you would on Cloud. Pick per-server export when you only need to host a finished server's endpoint; pick the platform self-host when you want the whole product behind your own perimeter.
Prerequisites
Docker and the Docker Compose plugin (
docker compose).A host that can expose three ports (or a reverse proxy in front of them).
No separate Postgres or Redis is required — the all-in-one stack bundles both.
What ships
A single all-in-one image published to ghcr.io/inovastudio/mcp-builder that runs the dashboard, the control-plane API, and the MCP runtime in one container. The accompanying docker-compose.selfhost.yml adds bundled Postgres and Redis services and wires the volumes and environment for you.
Quickstart
cp .env.selfhost.example .env # then edit .env
docker compose -f docker-compose.selfhost.yml up -dDatabase migrations run automatically on boot, so the stack is ready once the container reports healthy. Open the dashboard at http://localhost:3000, create your account, and connect your first app.
Ports
3000
Dashboard (web)
The UI you sign in to.
3001
Control-plane API
OAuth authorization server + management API.
3002
MCP runtime
The public /mcp/<server-id> endpoint.
Put a reverse proxy / TLS terminator in front of these if you're exposing the platform beyond localhost, and set the public-URL variables below to the externally reachable origins. API_PUBLIC_URL and MCP_RUNTIME_PUBLIC_URL are read at runtime, so OAuth callback, login, embed, git-webhook and MCP-client URLs follow your domain — the published image needs no rebuild for a custom domain.
Set the public-URL variables on the dashboard (web) service too. The dashboard resolves
API_PUBLIC_URLandMCP_RUNTIME_PUBLIC_URLfrom its own environment (via/api/config) to render each server's public endpoint and client-config snippets. In the bundled compose stack every service shares one.env, so this is automatic — but if you split the web, API and runtime into separate services, aMCP_RUNTIME_PUBLIC_URLset only on the runtime/API won't reach the dashboard, and server URLs render as "MCP runtime URL not configured" instead of your domain. Set it on the web service and restart (no rebuild needed).
Environment reference
Copy .env.selfhost.example to .env and fill in the values you need. The variables most relevant to self-hosting:
FLOWCP_SELF_HOSTED
yes
Set to true to run in Self-Hosted mode (unlimited requests, license-key plans, no Stripe UI).
FLOWCP_LICENSE_KEY
no
Signed license key that activates a paid plan. Blank → free Starter plan.
DATABASE_URL
yes
Postgres connection string. Points at the bundled postgres service by default.
REDIS_URL
yes
Redis connection string (BullMQ + rate limiting). Points at the bundled redis service.
WEB_PUBLIC_URL
yes
Public URL of the dashboard (e.g. https://flowcp.example.com).
API_PUBLIC_URL
yes
Public URL of the control-plane API (OAuth authorization-server metadata).
MCP_RUNTIME_PUBLIC_URL
yes
Public URL of the MCP runtime (OAuth protected-resource metadata + the server endpoint / client-config snippets shown in the dashboard). Must also be present on the web service.
OAUTH_ENCRYPTION_KEY
auto
AES-256-GCM key for secrets at rest. Auto-generated on first boot if unset (see below).
OAUTH_STATE_KEY
auto
AES-256-GCM key for OAuth state / auth codes. Auto-generated on first boot if unset.
SESSION_SECRET
auto
HMAC key for session cookies and CSRF. Auto-generated on first boot if unset.
PLATFORM_OAUTH_CLIENT_SECRET
auto
Secret for the platform's own OAuth wrapper. Auto-generated on first boot if unset.
MCP_ALLOWED_ORIGINS
no
Comma-separated browser origins allowed to call the MCP runtime. Unset accepts every origin. See Origin validation below.
In Self-Hosted mode no Stripe configuration is shipped or required; the Billing UI is replaced by a License panel.
Origin validation
The MCP specification requires a server to validate the Origin header on incoming connections, which is what stops a page on an attacker's site from driving your runtime through a visitor's browser (a DNS-rebinding attack).
Set MCP_ALLOWED_ORIGINS to a comma-separated list of the browser origins that should be able to reach your runtime:
Two things are always allowed and never need listing:
Requests with no
Originheader. Claude Desktop, Cursor, and the FlowCP CLI are not browsers and send none. Origin validation only ever affects browser-initiated requests, so these clients are unaffected.Same-origin requests — where the
Originhost matches the host the request arrived on. A custom domain serving its own embedded chat widget works without an entry.
MCP_RUNTIME_PUBLIC_URL and WEB_PUBLIC_URL are included automatically.
Leaving
MCP_ALLOWED_ORIGINSunset accepts every origin. That is the default so an upgrade cannot silently cut off a working browser client; the runtime logs a warning at boot while enforcement is off. Set the variable once you know which browser clients you serve.
A rejected request gets HTTP 403 with {"error":"FORBIDDEN_ORIGIN"}.
The /data volume and secret auto-generation
The compose file mounts a persistent volume at /data. On first boot, any of these secrets that you did not provide in .env are generated automatically and written to /data/.flowcp/secrets.env:
OAUTH_ENCRYPTION_KEYOAUTH_STATE_KEYSESSION_SECRETPLATFORM_OAUTH_CLIENT_SECRET
On every subsequent boot the stack reads the generated values back from that file, so they stay stable across restarts and upgrades.
Warning — back up your
/datavolume.OAUTH_ENCRYPTION_KEYencrypts every secret stored in the database (OAuth client secrets, shared upstream credentials, etc.). If you lose the/datavolume and didn't pin these keys in.envyourself, the auto-generated keys are gone and previously-encrypted secrets can no longer be decrypted — you'll have to re-enter them. For production, either back up/dataor set the four secrets explicitly in.envand store them in your own secret manager.
Activate a paid plan
Self-Hosted derives its plan from FLOWCP_LICENSE_KEY:
Obtain a signed license key for your plan (Team, Business, or Enterprise).
Set
FLOWCP_LICENSE_KEY=<key>in.env.Recreate the stack:
License keys are verified offline against a public key baked into the official image, so activation needs no extra configuration or network calls — just the key itself. (To trust a non-official signing key, set FLOWCP_LICENSE_PUBLIC_KEY to your own public PEM; it overrides the baked-in default.)
With no key set, the platform runs on the free Starter plan (core features, unlimited requests). The License panel in the dashboard shows the active plan and the features it unlocks. See Plans & features for what each plan includes.
Issuing license keys (platform operators)
If you run the platform and mint keys for your own installs, sign them with the private key matching the baked-in (or FLOWCP_LICENSE_PUBLIC_KEY) public key:
The printed token is the value end users set as FLOWCP_LICENSE_KEY. Generate the keypair offline (openssl genpkey -algorithm ed25519 -out flowcp-license.key then openssl pkey -in flowcp-license.key -pubout) and keep the private key in a secret manager.
Upgrade
Pull the latest image and recreate the stack — migrations run automatically on the new boot:
Because all secrets persist in /data and the schema migrates forward on boot, upgrades are a pull-and-up with no manual migration step.
Last updated