Self-host Enterprise
The community self-host bundle is the free edition of the product: full 9-module
suite, full agent, full knowledge graph. It also requires a LICENSE_KEY —
a free, self-serve, 1-seat, 1-year community-tier JWT minted by our cloud
signing service. The same offline verifier handles both tiers; the JWT's
tier claim is what decides what unlocks.
What community doesn't include is the enterprise tier: SSO/SAML, ABAC, deep audit search/export, system-admin DSAR queue. Those features physically ship in the binary, but are gated at runtime by the license tier — only an enterprise-tier JWT unlocks them.
Self-host Enterprise is for the case where you want those features on-prem (compliance, security clearance, customer firewall) without giving up the data-locality story.
What you get
| Feature | Self-host community | Self-host Enterprise |
|---|---|---|
| 9 modules + agent + knowledge graph | ✅ | ✅ |
File projection (memory.md, skills/) | ✅ | ✅ |
| BYO LLM key / bundled Ollama | ✅ | ✅ |
| Manual backup / restore CLI | ✅ | ✅ |
| SSO/SAML (Google Workspace, Entra, Okta, OneLogin, custom) | ❌ | ✅ |
| ABAC (attribute-based access-control policies) | ❌ | ✅ |
| Audit deep (tenant-wide search + signed exports) | ❌ | ✅ |
| System-admin DSAR queue (operator-only — bulk fulfillment, cross-user export, super-admin portal) | ❌ no super-admin portal at all | ✅ |
| User self-serve DSAR (Settings → Privacy) | ❌ license-gated | ✅ |
Free community LICENSE_KEY (1 seat, 1 year, renewable) | ✅ | n/a — replaced by paid enterprise key |
Paid enterprise LICENSE_KEY (seat-counted, feature-set claim) | ❌ | ✅ |
The self-serve DSAR row matters: even community self-host is GDPR-
compliant out of the box. The enterprise gate is on the
system-admin (operator-side) DSAR queue console — the super-admin
portal at admin.<basedomain> where the platform operator handles
cross-user bulk operations. Tenant admins and users always have
self-serve, on every edition.
Getting a license
Email sales@ccdsuite.com with:
- Organization name
- Expected user-seat count
- Desired feature set (any of:
sso_saml,abac,audit_deep,dsar_self_serve,dsar_admin_queue,multi_org,custom_retention) - License term (12 / 24 / 36 months)
- Optional: hostname binding (locks the license to a specific deploy domain — set to your production hostname for tighter anti-piracy)
You'll receive a signed JWT and a one-pager with the install steps.
Installation
Three env-var changes turn a community install into an enterprise install. No new container image needed — the same binary verifies the license at boot.
# In your .env / docker-compose override / k8s secret:
EDITION=selfhost-enterprise
LICENSE_KEY=eyJhbGciOiJFZERTQSJ9.eyJ0aWVyIjoi...
# LICENSE_PUBLIC_KEY is optional — used only for staging/dev where you
# want to test with a self-issued license against a non-production key.
Restart the web service. On boot you'll see (real format from the instrumentation hook):
[license] OK — tier=enterprise customer="Acme Co" features=[sso_saml,abac,audit_deep] expires_in_days=362
If the license is missing/expired/invalid, you'll see a multi-line banner:
──────────────────────────────────────────────────────────────
CCD Suite — licence key required (reason: expired).
Self-host needs a key to run. Get a FREE community key in seconds:
https://ccdsuite.com/self-host
then set LICENSE_KEY in your .env and restart.
──────────────────────────────────────────────────────────────
Boot behavior depends on NODE_ENV:
- Production (
NODE_ENV=production): the app exits with code 1 after printing the banner. Your container manager (Docker, k8s, systemd) will see the failure and crash-loop. Fix the key and restart. - Development (
NODE_ENV != production): the app logs the banner but continues to boot so you can debug locally without a valid key.
Pick NODE_ENV deliberately when you set up your environment — a
production deploy with NODE_ENV unset will still hard-block (the
default is production-strict).
Verifying the install
The admin License page lives at GET /api/admin/license/status and
returns the live verification result:
The route requires an authenticated super-admin session (cookie-
based, not Bearer tokens). The easiest way to call it from the CLI
is to copy your sb-access-token cookie from the browser DevTools
and pass it in:
curl -s -b "sb-access-token=$SUPER_ADMIN_COOKIE" \
https://admin.your.deploy.example.com/api/admin/license/status | jq
(Or just hit the admin License page in a browser — the JSON renders inline.)
Sample output (enterprise, valid):
{
"edition": "selfhost-enterprise",
"mode": "enterprise",
"valid": true,
"customer": "Acme Co",
"features": ["sso_saml", "abac", "audit_deep"],
"seats": 100,
"domain": null,
"expiresAt": 1782345600,
"daysRemaining": 362,
"licenseId": "a8e86c79-10f0-47e2-b4c1-76b1931da815"
}
When a gated route is hit without a valid license, callers get a typed JSON error:
{
"error": "feature_not_entitled",
"feature": "sso_saml",
"reason": "no_license",
"hint": "This feature requires a valid enterprise license. Set EDITION=selfhost-enterprise and LICENSE_KEY in your environment."
}
Status code is 402 Payment Required when the license is
missing/expired/invalid, and 403 Forbidden when the license is valid
but doesn't include the requested feature (i.e. you need to upgrade your
plan, not renew it).
Renewal
Licenses are short-lived by design (12 months is the default term). 30 days before expiry, an automated email goes to the address on file with renewal instructions. The cron that drives this lives on the cloud platform side; your install doesn't need to do anything to opt in.
Once you have the renewed JWT, swap the LICENSE_KEY env var. The
verifier caches results for 60 seconds, so the new key picks up
within a minute on its own — a restart is optional but immediate.
Old ciphertext / audit entries / DSAR requests remain accessible
(the license gates the features, not the data — data is yours
regardless).
Domain binding — read this before you set it
If you ask sales to bind your license to a hostname (the domain
claim), the verifier hard-rejects every request that doesn't match.
This is one-way: changing your deploy domain after issuance requires
requesting a brand-new license. Don't set domain unless you have a
stable production hostname you don't expect to change.
Offline verification
Verification is offline. The license is a JWT signed by our Ed25519 private key; the matching public key is baked into the binary. Your install does not phone home to verify. This makes Self-host Enterprise viable for:
- Airgapped customer environments (gov / defense / regulated finance)
- Customer networks that block outbound to the public internet
- High-availability deploys where a licensing-server outage shouldn't affect you
The trade-off is no online revocation. We mitigate this with short license lifetimes and a renewal cadence that matches your business relationship — if you need true online revocation, talk to us about the cloud edition.
Troubleshooting
Boot log shows [license] FAILED — reason=invalid_signature.
The LICENSE_KEY value doesn't verify under the embedded public key.
Common causes:
- Pasted the JWT from email with a soft-wrap line break in the middle. Confirm no whitespace within the token.
- Using a staging license against a production-built binary, or vice versa. Make sure the issuer matches the binary's bake.
- Pre-production build with a placeholder public key. Set
LICENSE_PUBLIC_KEYenv var to the matching public-key.pem.
Boot log shows [license] FAILED — reason=expired.
Renew the license (sales@ccdsuite.com). The data isn't lost — features
just refuse new operations until renewed.
Boot log shows [license] FAILED — reason=domain_mismatch.
The license was issued with a domain claim bound to a specific
hostname and your deploy isn't on that host. Either deploy on the bound
hostname, or request a domain-unbound license (less anti-piracy, more
flexibility).
Settings → Security → SSO page is empty.
The admin nav hides enterprise feature pages when the entitlement
check returns false. Confirm EDITION=selfhost-enterprise is set
(not selfhost), and the boot log shows [license] OK. If the
license is valid but the page is still hidden, hit
GET /api/admin/license/status to verify the feature is in the
license's features array.
Going back to community
Remove LICENSE_KEY from the environment and set EDITION=selfhost.
Restart. Enterprise features will be hidden in the UI and denied at the
API layer. No data is deleted; if you later restore the license, every
SSO config / ABAC policy / audit row remains in place.
Read next
- Production hardening — lock down a self-host install before flipping on enterprise features.
- Configuration reference — every env var, including license vars.
- Self-host vs cloud — the full feature matrix.