Contentrain Studio supports multiple deployment shapes that vary along four independent axes:
- Edition — whether the
ee/directory is loaded (Community vs Enterprise) - Billing mode — how subscription state is resolved (
off/polar/stripe/flat) - Plan source — where the effective plan comes from (
subscription/operator/fixed) - Tenancy — single workspace vs multiple workspaces
The NUXT_DEPLOYMENT_PROFILE environment variable (auto-detected when unset, explicit when set) selects a preset that configures these axes coherently. This document lists the supported presets, maps them to real-world scenarios, and gives per-scenario environment checklists.
For feature-level behavior per edition, see
EDITIONS.md. For legal mapping, seeLICENSING.md.
| Profile | Edition | Billing mode | Plan source | Default / fixed plan | Typical scenario |
|---|---|---|---|---|---|
managed |
ee (required) | polar or stripe | subscription | free (on signup) |
1 — Contentrain.io SaaS |
dedicated |
ee (required) | flat or polar | operator | enterprise |
3 — Managed dedicated for a single customer |
on-premise |
ee (required) | off | operator | enterprise |
2, 5, 10 — Customer runs ee/ on their infra |
community |
agpl | off | fixed | community |
4, 6, 7, 11, 12 — AGPL-only self-host |
On boot, resolveDeployment() infers the profile from runtime signals:
ee/ loaded? |
Polar/Stripe configured? | Inferred profile |
|---|---|---|
| Yes | Yes | managed |
| Yes | No | on-premise |
| No | any | community |
The dedicated profile must be explicit — auto-detection cannot distinguish it from managed. Set NUXT_DEPLOYMENT_PROFILE=dedicated when hosting a single tenant with flat-fee billing.
Explicit setting always wins over auto-detection. This is the override path for:
- running
managedlocally with billing disabled for development; - running
communitywhenee/exists on disk but must not be activated; - pinning
on-premisevsdedicatedsemantics.
Profile: managed — multi-tenant, subscription-driven.
NUXT_DEPLOYMENT_PROFILE=managed # explicit for clarity
NUXT_POLAR_ACCESS_TOKEN=polar_oat_…
NUXT_POLAR_WEBHOOK_SECRET=…
NUXT_POLAR_STARTER_PRODUCT_ID=…
NUXT_POLAR_PRO_PRODUCT_ID=…
NUXT_POLAR_SERVER=production
# billing flag is auto-set by the payment registry; no need to set NUXT_PUBLIC_BILLING_ENABLEDee/must be present and bundled with the image.- Attribution (
LICENSE-EXCEPTIONS§7(c)) — app footer and/aboutpage must link to the public source repo for the deployed tag. - AGPL-3.0 §13 source disclosure — satisfied by the public GitHub repo at the deployed tag.
- Webhooks from Polar →
POST /api/billing/webhook/polar→ upsertspayment_accountsand syncsworkspaces.plan.
Profile: on-premise — customer runs the whole stack on their servers.
NUXT_DEPLOYMENT_PROFILE=on-premise
# No Polar / Stripe env vars — billing is off.
# Plan is set by the operator through DB or admin UI:
# UPDATE workspaces SET plan = 'enterprise' WHERE id = '…';
# Default plan is 'enterprise' if workspace.plan is null.ee/must be present — licensed separately viaee/LICENSE§2.2.- Authorized Users and Instance count per the executed order form.
- Webhooks disabled; usage metering outbox drains locally as a no-op.
- License-key enforcement (offline signed JWT + grace period) is a roadmap item for a future Enterprise Edition release; v1.0 deployments rely on the executed order form +
ee/LICENSE§6.2 audit rights.
Profile: dedicated — one workspace, flat fee or custom subscription, hosted by Contentrain.
NUXT_DEPLOYMENT_PROFILE=dedicated
# Choose one billing flavor:
# (a) flat fee → leave Polar/Stripe env vars unset, set plan via admin
# (b) subscription → configure Polar like Scenario 1 but single-tenant- Same
ee/image asmanaged. - Typically isolated database / DNS / secret scope per customer.
- Addendum in the order form clarifies scope.
Profile: community — AGPL core only, no managed billing.
NUXT_DEPLOYMENT_PROFILE=community
# No Polar / Stripe env vars.
# `ee/` must NOT be installed (or must be explicitly excluded from the deploy).- All workspaces resolve to plan
community. Numerical limits unenforced (unlimited). requires_ee: truefeatures are hidden in the UI and 404 at the API.NUXT_ANTHROPIC_API_KEY(if configured by the operator) powers AI chat with the operator's own Anthropic account.- Attribution (
LICENSE-EXCEPTIONS§7(c)) and trademark (LICENSE-EXCEPTIONS§7(e)) obligations apply. - AGPL-3.0 §13 — the operator must publish the corresponding source of the deployed tag (typically a link to the upstream repo, plus their own modifications if any).
Same profile and operational shape as Scenario 2 (on-premise). The difference is commercial: the customer is self-operating rather than receiving dedicated hosting from Contentrain.
Same profile as Scenario 4 (community). ee/ is not available under AGPL; a fork that includes ee/ without an executed agreement violates ee/LICENSE §3.3 and §3.4. Fork operators must also:
- Rebrand per
LICENSE-EXCEPTIONS§7(e) — the "Contentrain" name and logos cannot be used for the fork's product identity. - Satisfy AGPL §13 independently — publish their corresponding source.
Same profile as Scenario 4 (community). Reselling the EE is prohibited; reselling the AGPL core as a paid service is permitted subject to AGPL §13 and the LICENSE-EXCEPTIONS §7(e) trademark rule. A commercial agreement with Contentrain is recommended for support, trademark use, or offering EE features to reseller customers.
Profile: managed or dedicated depending on the OEM contract.
Requires a separately executed OEM agreement (ee/LICENSE §2.4). The OEM contract specifies:
- which EE features are exposed;
- whether the OEM's customers are counted as Authorized Users;
- attribution and branding rules;
- AGPL §13 compliance ownership (the OEM typically takes this on).
Profile: dedicated (most common).
Requires a separately executed white-label agreement (ee/LICENSE §2.5). The white-label contract grants limited trademark rights to rebrand the EE. The AGPL core's LICENSE-EXCEPTIONS §7(c) attribution can be relaxed only by written permission in the contract.
Profile: on-premise with additional operational constraints.
NUXT_DEPLOYMENT_PROFILE=on-premise
# All outbound-network-dependent features must be disabled or configured for internal mirrors:
# - Polar / Stripe env vars unset (billing off)
# - Resend disabled (email sending off, or configured for internal SMTP)
# - Anthropic key optional (configure with internal proxy if needed)
# - GitHub App can be replaced by a GitHub Enterprise Server instance- Air-gapped deployments rely on the executed order form's stated term + the
ee/LICENSE§6.2 audit mechanism. Offline license-key signing (JWT + grace period) is a roadmap item — seeROADMAP.md. - Usage metering outbox accumulates rows but never drains (no outbound webhooks).
- Auto-update cadence is manual — customer pins to a specific release tag and upgrades on their schedule.
Any profile can be used locally. AGPL-3.0 applies to all contributions to the core. Contributions to ee/ are not accepted from external parties (ee/LICENSE §5.3).
Typically community (no ee/) or an Evaluation License (ee/LICENSE §2.3 — 60 days, 1 instance, 5 users, non-production).
# Supabase pair (default)
pnpm install
pnpm db:start # local Supabase
pnpm db:migrate
pnpm dev # auto-detects community
# Managed + postgres pair (any plain PostgreSQL)
pnpm install
docker run -d -p 5432:5432 -e POSTGRES_PASSWORD=postgres postgres:16
NUXT_POSTGRES_URL=postgres://postgres:postgres@localhost:5432/postgres pnpm db:migrate:pg
pnpm dev # with the managed-pair env set (see .env.example)The auth/database provider pair (supabase + supabase or managed + postgres, selected via NUXT_AUTH_PROVIDER / NUXT_DATABASE_PROVIDER) is an independent axis: any profile can run on either pair. Mixing pairs is rejected at boot.
Naming heads-up: the
managedprofile (ee + subscription billing on contentrain.io) and themanagedauth provider (the Supabase-free built-in auth) are unrelated concepts that happen to share a name. Acommunity-profile deployment can absolutely run themanagedauth provider.
| Variable | managed |
dedicated |
on-premise |
community |
|---|---|---|---|---|
NUXT_DEPLOYMENT_PROFILE |
set / auto | set explicitly | set / auto | set / auto |
NUXT_SESSION_SECRET |
required | required | required | required |
NUXT_AUTH_PROVIDER / NUXT_DATABASE_PROVIDER |
pair choice | pair choice | pair choice | pair choice |
NUXT_SUPABASE_* |
required (supabase pair) | required (supabase pair) | required (supabase pair) | required (supabase pair) |
NUXT_POSTGRES_URL |
required (managed pair) | required (managed pair) | required (managed pair) | required (managed pair) |
NUXT_AUTH_JWT_SECRET |
required (managed pair) | required (managed pair) | required (managed pair) | required (managed pair) |
NUXT_SESSION_PASSWORD |
required (managed pair)¹ | required (managed pair)¹ | required (managed pair)¹ | required (managed pair)¹ |
NUXT_OAUTH_GITHUB_* |
required (managed pair) | required (managed pair) | required (managed pair) | required (managed pair) |
NUXT_OAUTH_GOOGLE_* |
optional (managed pair) | optional (managed pair) | optional (managed pair) | optional (managed pair) |
NUXT_GITHUB_APP_* |
required | required | required | required (if using GitHub) |
NUXT_POLAR_* |
required | optional (flat fee ok) | — | — |
NUXT_STRIPE_* |
optional fallback | optional | — | — |
NUXT_PUBLIC_BILLING_ENABLED |
auto-set true by registry | auto-set | auto-set false | auto-set false |
NUXT_RESEND_* |
recommended² | recommended² | optional² | optional² |
NUXT_CDN_R2_* |
optional (EE media) | optional | optional | — (EE feature) |
NUXT_ANTHROPIC_API_KEY |
optional (Studio-hosted key) | optional | optional | required for AI chat |
¹ Session store for the OAuth login module — dev auto-generates it, deployed builds must set it (boot warns otherwise).
² NUXT_RESEND_API_KEY becomes required on the managed pair regardless of profile (magic link + invite emails).
All profiles share the same base requirements (session secret, one provider pair, GitHub). Only billing / EE feature envs differ.
The effective deployment state is exposed to the client at runtime via useRuntimeConfig().public.deployment:
const { public: { deployment } } = useRuntimeConfig()
// { profile: 'managed', edition: 'ee', billingMode: 'polar' }Server code uses resolveDeployment() directly from server/utils/deployment.ts. The values are computed once at boot in server/plugins/00.billing-flag.ts and do not change per request.
LICENSING.md— legal overviewEDITIONS.md— Community vs Enterprise feature matrixDEPLOYMENT.md— general deployment topologySELF_HOSTING.md— Community Edition guidePAYMENT_PROVIDERS.md— billing plugin registryDOCKER.md— image and tag policy