Enterprise setup

Sitebin is open-core: the community edition is complete on its own, and the enterprise edition adds accounts, tiers & quotas, OAuth login, SMTP, and billing through Stripe, Paddle or the SaaS Stack on top — all configured at container startup, like everything else.

Editions

  • Community (default) — MIT licensed, built with go build / the default sitebin:latest image. Fully open, no accounts, no feature gates.
  • Enterprise — the ee/ tree, governed by the Elastic License 2.0. Compiled in only with the ee build tag (go build -tags ee, image sitebin:latest-ee).

The ee/ source is published in the repository — source-available, so you can read and audit every line before you buy. Both editions build from the same repo, but the community sitebin:latest image contains none of the ee/ code: it is excluded at compile time, so the community binary stays pure MIT.

Building your own Enterprise image

Two build arguments, both easy to miss — and missing them produces a binary that looks fine and then stops creating sites after 90 days with no explanation:

docker build \
  --build-arg EDITION=enterprise \
  --build-arg LICENSE_ROOTS="<base64url ed25519 root public key>[,<another>]" \
  -t sitebin:latest-ee .
  • EDITION=enterprise compiles the ee/ tree in. Without it you get a community binary: no accounts, no tiers, no licensing at all.
  • LICENSE_ROOTS is the comma-separated list of trusted Ed25519 root public keys, baked into the binary at build time. A build with no roots trusts nothing, so no license can ever verify: the instance runs its 90-day trial and then restricts creation. It says so at startup, at ERROR level on every start, and sitebin version prints trusted license roots: NONE (unlicensable build) — check that line on any artifact before you ship it. There is deliberately no environment override for the roots: anything that could set one could mint itself a license.

The published ghcr.io/ittrail/sitebin.io:latest-ee image gets its roots from the LICENSE_ROOTS repository variable of the product repo, and the release workflow refuses to build the enterprise image while that variable is empty. Verify a pulled image with docker run --rm ghcr.io/ittrail/sitebin.io:latest-ee version.

Account modes

SITEBIN_ACCOUNT_MODE selects how the instance handles users (default: open):

  • open — no accounts; anonymous creation, exactly like the community edition.
  • accounts — users log in to create sites.
  • tiers — accounts plus tiered quotas (and optionally paid plans).

Enterprise environment variables

VariablePurpose
SITEBIN_TIERS / SITEBIN_TIERS_FILETier definitions (inline JSON or mounted file).
SITEBIN_DEFAULT_TIERTier new/free accounts start on (required in tiers mode).
SITEBIN_ANON_TIERTier for anonymous creation (empty = require an account). The instance refuses to start if it names a tier missing from the tier file.
SITEBIN_TIER_SELF_SELECTAllow users to switch among free tiers.
SITEBIN_OPERATOR_DOMAINSComma-separated zones you own and point at the instance. Their subdomains attach to operator sites (admin tier + SITEBIN_ADMIN_ACCOUNTS) without a DNS proof and are refused to everyone else. See Custom domains.
SITEBIN_ZONE_NAMES_PER_HOURHow many new names one account may attach through its own zones per hour (default 50, 0 = off). Protects the instance's shared certificate budget; names per zone are unlimited. The number of zones is the tier's max_zones.
SITEBIN_CONTAINERSoff (default) or docker: enables container sites. Needs tiers mode; each tier's max_containers caps the services an account runs across all its sites. An Engine that does not answer never stops startup.
SITEBIN_CONTAINERS_DOCKER_HOSTunix:///var/run/docker.sock (default) or tcp://host:port for a socket proxy. Docker 26 or newer.
SITEBIN_CONTAINERS_DATA_MOUNTWhere /data lives for Docker: an absolute host path or volume:<name>. Default: found by inspecting Sitebin's own container.
SITEBIN_CONTAINERS_SELFSitebin's own container id or name. Default: detected.
SITEBIN_CONTAINERS_RUNTIMEOCI runtime for customer containers, e.g. runsc (gVisor). Default: the Engine's.
SITEBIN_CONTAINER_MEMORY_MB / _CPUS / _PIDSFixed per-container limits: 512 MB (no swap), 0.5 CPU, 256 processes.
SITEBIN_ALLOW_ANON_CREATEIn accounts mode, still allow anonymous sites.
SITEBIN_VIEW_DOMAINSeparate registrable domain user sites are served from — see Configuration and Operations & security.
SITEBIN_ADMIN_ACCOUNTSComma-separated emails allowed to reach the instance register at /account/admin. Gated twice: the account's tier must also carry "admin": true. Unset disables the console entirely.
SITEBIN_OAUTH_GOOGLE_CLIENT_ID / _SECRETGoogle OIDC login.
SITEBIN_OAUTH_MICROSOFT_CLIENT_ID / _SECRET / _TENANTMicrosoft OIDC. _TENANT is a tenant id or verified domain for single-tenant sign-in (issuer matched exactly), or one of the multi-tenant aliases common (default), organizations, consumers, whose tokens are accepted from any Microsoft tenant.
SITEBIN_OAUTH_OIDC_ISSUER / _CLIENT_ID / _CLIENT_SECRET / _LABELGeneric OIDC sign-in against any issuer (Keycloak, Okta, Authentik, the SaaS Stack). _ISSUER is the value that must appear in every token's iss; _LABEL is the login-button text (default SSO).
SITEBIN_OAUTH_OIDC_DISCOVERY_URLWhere the discovery document is fetched, when that is not the issuer's own URL. Set it to the SaaS Stack's Auth Gateway to put sign-in behind the stack's consent gate; the document's own issuer must still equal _ISSUER.
SITEBIN_LOCAL_AUTHDefault true. false = SSO only: no email/password form, signup and reset disabled; with a single OAuth provider, /account/login redirects straight to it. Requires an SITEBIN_OAUTH_* provider.
SITEBIN_SMTP_HOST / _PORT / _USER / _PASS / _FROM / _TLSEmail (verification, password reset). Port default 587; _TLS=true for implicit TLS (465).
SITEBIN_BILLINGWhich backend may charge customers: stripe, paddle or paygate. Exactly one billing backend runs. With a single one configured it is inferred and this can be left out; with two configured and no choice made, the instance refuses to start rather than guess. With paygate selected, Stripe/Paddle credentials are inert and their webhook routes are not mounted.
SITEBIN_STRIPE_SECRET_KEY / _WEBHOOK_SECRETStripe billing, direct. Webhook: POST /account/billing/stripe/webhook.
SITEBIN_PADDLE_API_KEY / _WEBHOOK_SECRET / _SANDBOXPaddle billing, direct. Webhook: POST /account/billing/paddle/webhook.
SITEBIN_PAYGATE_URL / _APP_ID / _API_KEYSell tiers and resolve subscriptions through a SaaS-Stack PayGate. See SaaS-Stack integration.
SITEBIN_PAYGATE_CACHE_TTLPer-user tier cache (default 5m). "Manage subscription" needs no setting: with PayGate and SITEBIN_OAUTH_OIDC_ISSUER set it goes to the stack's hosted plan page, <issuer origin>/apps/<app id>/plan, derived.
SITEBIN_LICENSE_KEYOptional Enterprise license key — four base64url segments: a stack-signed certificate plus the license it vouches for. Verified offline; it never blocks startup. When set it wins over any license collected from the stack, which is what makes an air-gapped install work. Without a valid key the instance runs for a 90-day trial and then declines to create new sites; existing sites are always served.
SITEBIN_LICENSE_REFRESHHow often the instance collects its license from the stack. Default 24h; any positive Go duration. Ignored when SITEBIN_LICENSE_KEY is set.
SITEBIN_LICENSE_URLOptional override for where a renewed license is collected (default <SITEBIN_PAYGATE_URL>/api/v1/licenses/renew). The request carries no credential — the instance presents the license it already holds. Cached under the data dir, applied without a restart; a failure never restricts anything. Ignored when SITEBIN_LICENSE_KEY is set.
SITEBIN_STACK_URL / _APP_ID / _ADMIN_KEY (or _ADMIN_KEY_FILE)Self-registration against the IT-Trail SaaS Stack on every start. _ADMIN_KEY is the stack's platform admin key — a master credential; prefer _ADMIN_KEY_FILE (a docker secret or mounted file) so it never sits in the container's environment. Either way the variable is scrubbed from the process environment once read.
SITEBIN_STACK_CONSENTSJSON list of this deployment's consent documents — terms of service, data processing agreement — that the stack's consent gate asks for at sign-in, in this order. Each needs key, version and url; title is an optional locale map. Raising a version re-asks every user for that document. Absent = keep what the stack holds; an empty list is refused at startup. Replaces the former one-document SITEBIN_STACK_TERMS.
SITEBIN_STACK_GDPR_SECRETThe shared secret the stack signs its GDPR orders with (delete this user, export this user's data). At least 32 characters; required whenever SITEBIN_STACK_URL is set. With it, POST /account/gdpr/delete and /export are mounted and declared; without it neither exists.
SITEBIN_STACK_LICENSINGJSON licensing block declaring what a Sitebin Enterprise license is worth per plan (max_custom_domains) and the grace months. Only the vendor's own deployment sets it.

Tiers

Tiers are defined as JSON, either inline in SITEBIN_TIERS or in a file mounted at SITEBIN_TIERS_FILE:

[
  {
    "id": "free",
    "label": "Free",
    "max_site_bytes": 104857600,
    "max_files": 500,
    "max_sites": 10,
    "webdav": true,
    "custom_domains": 0,
    "max_expiry_days": 7,
    "price": null
  },
  {
    "id": "pro",
    "label": "Pro",
    "max_site_bytes": 1073741824,
    "max_files": 5000,
    "max_sites": 100,
    "webdav": true,
    "custom_domains": 5,
    "max_expiry_days": 0,
    "max_containers": 3,
    "max_forms": 1,
    "trusted": true,
    "price": { "monthly": "6", "annual": "60", "currency": "EUR" }
  },
  {
    "id": "studio",
    "label": "Studio",
    "max_site_bytes": 5368709120,
    "max_files": 10000,
    "max_sites": 500,
    "webdav": true,
    "custom_domains": 25,
    "max_expiry_days": 0,
    "max_containers": 20,
    "max_forms": 10,
    "max_zones": 3,
    "trusted": true,
    "price": { "monthly": "19", "annual": "190", "currency": "EUR" }
  }
]

max_containers caps the container services an account runs across all its sites. 0 or absent means none — like custom_domains, so a tier that forgets the field never gets compute. It only matters with SITEBIN_CONTAINERS=docker.

max_zones is how many zones an account may hold — domains proven once, with unlimited names under them. 0 or absent means none. Names in a zone do not count against custom_domains, but they do count against the license's instance-wide domain ceiling.

max_forms caps each site's forms. 0 or absent means none. It is stamped on a site like its other caps; a smaller plan pauses the newest forms rather than deleting them. It only matters with SITEBIN_FORMS_SMTP_HOST set. A tier with max_forms above 0 must also carry "trusted": true (below), or its sites get no forms.

Two optional flags change behaviour rather than quotas. "trusted": true exempts the tier's sites from the strict content-security headers untrusted uploads get — no host allowlist for scripts, styles, fonts and images, no block on requests to other servers — and from automatic locks: the upload scanner and the CSP tripwire only flag and alert on a trusted site. Grant it to plans whose holders you can hold accountable, and see Operations & security for what the two header levels contain. "admin": true marks a tier whose holders may reach the instance register at /account/admin: every site on the instance, with search, filters, delete and expiry control. Admin needs a second condition — the account's email must also be listed in SITEBIN_ADMIN_ACCOUNTS — so neither the tier source nor the container environment can grant instance-wide access on its own.

A price makes a tier a paid plan the dashboard sells, and it comes in two shapes for the two kinds of backend. monthly, annual and currency are amounts (currency is ISO 4217, default EUR): PayGate is handed these and creates the product in whatever processor the stack uses. stripe and paddle are identifiers for products you created in that provider yourself, and only a direct backend can use them. A tier may carry both; the active backend reads what it needs. display is a human string shown only while no amount is set — an amount always wins, so a shown price cannot disagree with the charged one. Running Stripe or Paddle directly, point the provider's webhooks at POST /account/billing/stripe/webhook and POST /account/billing/paddle/webhook; the hosted service (Drop, Free, Pro €6/€60, Studio €19/€190) bills through PayGate instead.

The account dashboard

The dashboard lives at /account on the main domain. Sites created while signed in are owned by the account and still work over the API with their edit password. Manage opens a site's edit page directly: signed in, its owner needs no edit password there.

It lists each site by its name, its URL and its custom domains — the ones that serve as links, the ones still waiting for their DNS record marked pending DNS. A name is an optional private label of up to 60 characters; it can be given or changed right in the list, without the site's edit password, as well as on the edit page and through the API and MCP.

It also issues API tokens, optionally named. A token creates sites owned by the account without a browser session — which is what makes publishing from CI or an AI agent possible on an instance where sign-in goes through SSO — and stands in for the edit password on every site that account already owns. It is shown once and stored only as a hash, and it acts on sites, never on the account: the dashboard refuses it, so it cannot change a plan or delete the account. See the API page.

License keys are Ed25519-verified offline, against a root public key compiled into the binary — the instance never needs to reach a license server to run, and an air-gapped deployment works. An online instance checks once a day for a renewed key, sending only the key it already holds. A key carries two dates: when the subscription ends, and how long after that it keeps working, so a lapsed payment warns long before it bites. License terms and purchasing are on the Enterprise page.