Environment variables
Configuration lives in .env on a self-hosted install. Set it to mode 600 and
never commit it.
Generate every secret fresh, per deployment. Never copy one from another install, from this page, or from a demo.
openssl rand -base64 32| Variable | Default | Notes |
|---|---|---|
NODE_ENV | development | Set to production for anything real |
APP_VERSION | Baked into the image at build time. Reported by /api/health/version | |
RELEASE_CHANNEL | stable | stable or beta. Determines which release is recommended, never causes an update |
Database
Section titled “Database”| Variable | Notes |
|---|---|
DATABASE_URL | PostgreSQL connection string. Required |
POSTGRES_PORT | Used by the development compose stack. Change if 5432 is taken |
POSTGRES_PASSWORD | Used by the production compose stack |
DATABASE_URL="postgresql://user:pass@localhost:5432/plugboard?schema=public"| Variable | Default | Notes |
|---|---|---|
REDIS_URL | redis://localhost:6379 | Optional. Only the legacy people-sync and digest worker uses it |
Object storage
Section titled “Object storage”Optional. Only needed for logos, photos and exports.
| Variable | Notes |
|---|---|
S3_ENDPOINT | MinIO locally, S3 in cloud |
S3_REGION | |
S3_ACCESS_KEY | |
S3_SECRET_KEY | |
S3_BUCKET |
Addresses
Section titled “Addresses”The most important block, and the one most often wrong.
| Variable | Notes |
|---|---|
PUBLIC_URL | The origin your users type, for example https://helpdesk.yourschool.org |
PUBLIC_HOST | The same hostname without the scheme |
API_URL | On a source install, the API origin |
WEB_URL | On a source install, the web origin |
INTERNAL_API_URL | Where the web container reaches the API inside the network, for example http://api:4000 |
API_PORT | Default 4000 |
WEB_PORT | Default 3000 |
PUBLIC_URL (and WEB_URL) is the CORS allow-list, the base for every emailed
link, the SSO redirect target and the portal address. Set it to an internal name
and you generate links your users cannot open.
https://x, https://x/ and http://x are three different values as far as
CORS is concerned.
INTERNAL_API_URL is distinct from API_URL on purpose. Server-side rendering
going out through a tunnel to reach a container one hop away is slow at best and
a loop at worst.
Authentication
Section titled “Authentication”| Variable | Default | Notes |
|---|---|---|
JWT_SECRET | Signs session and refresh tokens. Rotating it logs everybody out | |
JWT_ACCESS_TTL | 15m | Access token lifetime |
JWT_REFRESH_TTL | 30d | Refresh token lifetime |
Secrets
Section titled “Secrets”| Variable | Notes |
|---|---|
SECRETS_MASTER_KEY | 32-byte base64. Encrypts connector credentials and backups |
node -e "console.log(require('crypto').randomBytes(32).toString('base64'))"Two options. Use a reverse proxy, or point the application at a certificate.
| Variable | Notes |
|---|---|
TLS_CERT_FILE | PEM certificate, including intermediates |
TLS_KEY_FILE | PEM private key |
TLS_CA_FILE | Optional intermediate chain, if your issuer ships one separately |
TLS_PFX_FILE | A PKCS#12 file, which is what most Windows tooling produces |
TLS_PFX_PASSWORD | Its password |
Leave all unset to serve plain HTTP and terminate TLS at a proxy. See HTTPS and certificates.
Multi-tenancy
Section titled “Multi-tenancy”| Variable | Notes |
|---|---|
DEFAULT_TENANT_SLUG | Resolves a single default tenant when no subdomain matches. For single-tenant self-hosted installs |
Internal
Section titled “Internal”| Variable | Notes |
|---|---|
INTERNAL_API_TOKEN | Shared secret the worker uses to call internal endpoints |
Self-service portals
Section titled “Self-service portals”| Variable | Default | Notes |
|---|---|---|
PORTAL_USERNAME_ENTRY | 0 | 1 allows username-only entry at the kiosk and parent portal. Refused by default because typing a username proves nothing |
PORTAL_SESSION_TTL_MS | 900000 | Portal session lifetime, 15 minutes |
Outbound fetch guard
Section titled “Outbound fetch guard”| Variable | Default | Notes |
|---|---|---|
ALLOW_PRIVATE_EGRESS | 0 | 1 allows outbound requests to private, loopback and link-local addresses |
Blocks a tenant-supplied URL (a monitor target, a webhook, a connector base URL) from being used to reach internal hosts or a cloud metadata service.
Set it to 1 on an on-premises install that legitimately monitors LAN addresses.
Leave it off for anything multi-tenant.
Licensing
Section titled “Licensing”| Variable | Notes |
|---|---|
LICENSE_KEY | Your issued licence key |
LICENSE_PUBLIC_KEY | The vendor’s Ed25519 public key, base64 SPKI PEM. Same for every customer |
LICENSE_PUBLIC_KEYS | A {kid: key} map, for key rotation |
LICENSE_REVOKED_JTIS | Comma-separated licence ids to refuse locally |
LICENSE_SIGNING_SECRET | Legacy HS256 shared secret. Only while migrating old keys |
Instances verify with the public key. The private half lives only on the vendor control plane, so entitlement is checked offline and a network outage cannot disable a service desk.
Telemetry
Section titled “Telemetry”| Variable | Default | Notes |
|---|---|---|
TELEMETRY_ENDPOINT | Normally comes from the licence. This is an override | |
DEPLOYMENT_EDITION | onprem | onprem or saas |
USAGE_REPORTER_DISABLED | 0 | 1 disables phone-home entirely. Snapshots are still kept locally |
USAGE_REPORTER_TICK_MS | 3600000 | Reporter loop interval |
Only counts are reported: technician accounts, managed devices, enabled modules, version. See licence and plan.
Modules
Section titled “Modules”| Variable | Notes |
|---|---|
DISABLED_MODULES | Comma-separated module keys to hide entirely on this deployment |
DISABLED_MODULES=module.cardChecker,module.inductionsA blunt instrument: a module hidden this way cannot be re-enabled from the interface.
Used by the production compose stack to seed the SMTP connector.
| Variable | Notes |
|---|---|
SMTP_HOST | |
SMTP_PORT | 587 for STARTTLS, 465 for implicit TLS |
SMTP_USER | |
SMTP_PASSWORD | |
SMTP_FROM | For example ICT Service Desk <helpdesk@yourschool.org> |
Beyond the initial setup, email is configured as a connector.
Local AI
Section titled “Local AI”| Variable | Default | Notes |
|---|---|---|
OLLAMA_URL | Your Ollama host | |
OLLAMA_MODEL | A model you have pulled |
The assistant runs against a model you host and never sends anything to an external AI service. See Ollama.
Connector agents
Section titled “Connector agents”| Variable | Default | Notes |
|---|---|---|
AGENT_JOB_TTL_MS | 120000 | How long a queued job waits for an agent |
AGENT_LONG_POLL_MS | 25000 | How long an agent holds the connection with nothing to do |
Defaults are sensible. See connector agents.
Automatic updates
Section titled “Automatic updates”On-premises bundle only.
| Variable | Default | Notes |
|---|---|---|
AUTO_UPDATE | on | off to never update on its own |
AUTO_UPDATE_HOUR | 1 | Local hour, 0 to 23 |
AUTO_UPDATE_RESTART | on | off to stage the update but apply at your next restart |
Uses the machine’s own local time. A server set to UTC in a school that is not will update in the middle of a school day.
Docker image pinning
Section titled “Docker image pinning”| Variable | Notes |
|---|---|
PLUGBOARD_IMAGE | The image name, or name@sha256 for a digest pin |
PLUGBOARD_VERSION | The tag, or the bare digest hex |
ENV_FILE | Which env file the containers read, so a second stack can run on one host |
TUNNEL_TOKEN | For the Cloudflare Tunnel ingress profile |
ACME_EMAIL | For the Caddy ingress profile: where Let’s Encrypt sends expiry warnings |
See updating.
Diagnostics
Section titled “Diagnostics”| Variable | Notes |
|---|---|
SENTRY_DSN | Error reporting to your own self-hosted Sentry or GlitchTip. No default destination |
Test and development
Section titled “Test and development”| Variable | Notes |
|---|---|
PLUGBOARD_DEMO_DATA | 1 seeds fictional sample data on a brand new database only |
WEBHOOKS_DISABLED | 1 disables outbound webhook delivery |
REPORTS_DISABLED | 1 disables the scheduled report loop |
Control plane
Section titled “Control plane”Vendor side only. Never set on a customer deployment.
| Variable | Purpose |
|---|---|
CONTROL_PLANE_ADMIN_TOKEN | 32-byte random. The console’s shared admin token |
LICENSE_PRIVATE_KEY | base64 Ed25519 PKCS8. The only copy of the signing key |
CONTROL_PLANE_PUBLIC_URL | Where a human signs in |
TELEMETRY_PUBLIC_URL | Embedded into issued licences as the reporting endpoint |
REPORT_SKEW_MS | Replay window for usage reports. Default 30 minutes |
CP_POSTGRES_PASSWORD | Its database password |
PANEL_ACCESS_TEAM_DOMAIN | Used to fetch the Access signing keys |
PANEL_ACCESS_AUD | The Access application’s audience tag |
PANEL_REQUIRE_ACCESS_JWT | 1 to refuse the shared token entirely |
PROVISION_SECRET_KEY | 32 bytes base64. Seals generated secrets between pre-build and provisioning |
PROVISION_RUNNER_TOKEN | What a region host presents to claim provisioning work |
ONBOARDING_PUBLIC_URL | Where the customer’s confirm page lives |
RESEND_API_KEY | Sends onboarding invitations |
MAIL_FROM | The sending address. Its domain must be verified |
MAIL_REPLY_TO | Where replies land. The sending subdomain has no inbox |
CHARGEBEE_SITE | The site name, before .chargebee.com |
CHARGEBEE_API_KEY | Full-access API key |
CHARGEBEE_WEBHOOK_USER | Basic-auth username set on the webhook |
CHARGEBEE_WEBHOOK_PASSWORD | Its password. Chargebee does not sign webhooks, so this is the whole security boundary |
CHARGEBEE_NET_TERM_DAYS | Payment terms. Default 30 |
CHARGEBEE_PRICE_<TIER> | Override an item price id |
See the control plane.