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 defaultsitebin:latestimage. Fully open, no accounts, no feature gates. - Enterprise — the
ee/tree, governed by the Elastic License 2.0. Compiled in only with theeebuild tag (go build -tags ee, imagesitebin: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=enterprisecompiles theee/tree in. Without it you get a community binary: no accounts, no tiers, no licensing at all.LICENSE_ROOTSis 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, atERRORlevel on every start, andsitebin versionprintstrusted 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
| Variable | Purpose |
|---|---|
SITEBIN_TIERS / SITEBIN_TIERS_FILE | Tier definitions (inline JSON or mounted file). |
SITEBIN_DEFAULT_TIER | Tier new/free accounts start on (required in tiers mode). |
SITEBIN_ANON_TIER | Tier 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_SELECT | Allow users to switch among free tiers. |
SITEBIN_OPERATOR_DOMAINS | Comma-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_HOUR | How 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_CONTAINERS | off (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_HOST | unix:///var/run/docker.sock (default) or tcp://host:port for a socket proxy. Docker 26 or newer. |
SITEBIN_CONTAINERS_DATA_MOUNT | Where /data lives for Docker: an absolute host path or volume:<name>. Default: found by inspecting Sitebin's own container. |
SITEBIN_CONTAINERS_SELF | Sitebin's own container id or name. Default: detected. |
SITEBIN_CONTAINERS_RUNTIME | OCI runtime for customer containers, e.g. runsc (gVisor). Default: the Engine's. |
SITEBIN_CONTAINER_MEMORY_MB / _CPUS / _PIDS | Fixed per-container limits: 512 MB (no swap), 0.5 CPU, 256 processes. |
SITEBIN_ALLOW_ANON_CREATE | In accounts mode, still allow anonymous sites. |
SITEBIN_VIEW_DOMAIN | Separate registrable domain user sites are served from — see Configuration and Operations & security. |
SITEBIN_ADMIN_ACCOUNTS | Comma-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 / _SECRET | Google OIDC login. |
SITEBIN_OAUTH_MICROSOFT_CLIENT_ID / _SECRET / _TENANT | Microsoft 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 / _LABEL | Generic 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_URL | Where 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_AUTH | Default 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 / _TLS | Email (verification, password reset). Port default 587; _TLS=true for implicit TLS (465). |
SITEBIN_BILLING | Which 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_SECRET | Stripe billing, direct. Webhook: POST /account/billing/stripe/webhook. |
SITEBIN_PADDLE_API_KEY / _WEBHOOK_SECRET / _SANDBOX | Paddle billing, direct. Webhook: POST /account/billing/paddle/webhook. |
SITEBIN_PAYGATE_URL / _APP_ID / _API_KEY | Sell tiers and resolve subscriptions through a SaaS-Stack PayGate. See SaaS-Stack integration. |
SITEBIN_PAYGATE_CACHE_TTL | Per-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_KEY | Optional 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_REFRESH | How often the instance collects its license from the stack. Default 24h; any positive Go duration. Ignored when SITEBIN_LICENSE_KEY is set. |
SITEBIN_LICENSE_URL | Optional 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_CONSENTS | JSON 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_SECRET | The 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_LICENSING | JSON 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.