Skip to content

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.

Terminal window
openssl rand -base64 32
VariableDefaultNotes
NODE_ENVdevelopmentSet to production for anything real
APP_VERSIONBaked into the image at build time. Reported by /api/health/version
RELEASE_CHANNELstablestable or beta. Determines which release is recommended, never causes an update
VariableNotes
DATABASE_URLPostgreSQL connection string. Required
POSTGRES_PORTUsed by the development compose stack. Change if 5432 is taken
POSTGRES_PASSWORDUsed by the production compose stack
Terminal window
DATABASE_URL="postgresql://user:pass@localhost:5432/plugboard?schema=public"
VariableDefaultNotes
REDIS_URLredis://localhost:6379Optional. Only the legacy people-sync and digest worker uses it

Optional. Only needed for logos, photos and exports.

VariableNotes
S3_ENDPOINTMinIO locally, S3 in cloud
S3_REGION
S3_ACCESS_KEY
S3_SECRET_KEY
S3_BUCKET

The most important block, and the one most often wrong.

VariableNotes
PUBLIC_URLThe origin your users type, for example https://helpdesk.yourschool.org
PUBLIC_HOSTThe same hostname without the scheme
API_URLOn a source install, the API origin
WEB_URLOn a source install, the web origin
INTERNAL_API_URLWhere the web container reaches the API inside the network, for example http://api:4000
API_PORTDefault 4000
WEB_PORTDefault 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.

VariableDefaultNotes
JWT_SECRETSigns session and refresh tokens. Rotating it logs everybody out
JWT_ACCESS_TTL15mAccess token lifetime
JWT_REFRESH_TTL30dRefresh token lifetime
VariableNotes
SECRETS_MASTER_KEY32-byte base64. Encrypts connector credentials and backups
Terminal window
node -e "console.log(require('crypto').randomBytes(32).toString('base64'))"

Two options. Use a reverse proxy, or point the application at a certificate.

VariableNotes
TLS_CERT_FILEPEM certificate, including intermediates
TLS_KEY_FILEPEM private key
TLS_CA_FILEOptional intermediate chain, if your issuer ships one separately
TLS_PFX_FILEA PKCS#12 file, which is what most Windows tooling produces
TLS_PFX_PASSWORDIts password

Leave all unset to serve plain HTTP and terminate TLS at a proxy. See HTTPS and certificates.

VariableNotes
DEFAULT_TENANT_SLUGResolves a single default tenant when no subdomain matches. For single-tenant self-hosted installs
VariableNotes
INTERNAL_API_TOKENShared secret the worker uses to call internal endpoints
VariableDefaultNotes
PORTAL_USERNAME_ENTRY01 allows username-only entry at the kiosk and parent portal. Refused by default because typing a username proves nothing
PORTAL_SESSION_TTL_MS900000Portal session lifetime, 15 minutes
VariableDefaultNotes
ALLOW_PRIVATE_EGRESS01 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.

VariableNotes
LICENSE_KEYYour issued licence key
LICENSE_PUBLIC_KEYThe vendor’s Ed25519 public key, base64 SPKI PEM. Same for every customer
LICENSE_PUBLIC_KEYSA {kid: key} map, for key rotation
LICENSE_REVOKED_JTISComma-separated licence ids to refuse locally
LICENSE_SIGNING_SECRETLegacy 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.

VariableDefaultNotes
TELEMETRY_ENDPOINTNormally comes from the licence. This is an override
DEPLOYMENT_EDITIONonpremonprem or saas
USAGE_REPORTER_DISABLED01 disables phone-home entirely. Snapshots are still kept locally
USAGE_REPORTER_TICK_MS3600000Reporter loop interval

Only counts are reported: technician accounts, managed devices, enabled modules, version. See licence and plan.

VariableNotes
DISABLED_MODULESComma-separated module keys to hide entirely on this deployment
Terminal window
DISABLED_MODULES=module.cardChecker,module.inductions

A 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.

VariableNotes
SMTP_HOST
SMTP_PORT587 for STARTTLS, 465 for implicit TLS
SMTP_USER
SMTP_PASSWORD
SMTP_FROMFor example ICT Service Desk <[email protected]>

Beyond the initial setup, email is configured as a connector.

VariableDefaultNotes
OLLAMA_URLYour Ollama host
OLLAMA_MODELA model you have pulled

The assistant runs against a model you host and never sends anything to an external AI service. See Ollama.

VariableDefaultNotes
AGENT_JOB_TTL_MS120000How long a queued job waits for an agent
AGENT_LONG_POLL_MS25000How long an agent holds the connection with nothing to do

Defaults are sensible. See connector agents.

On-premises bundle only.

VariableDefaultNotes
AUTO_UPDATEonoff to never update on its own
AUTO_UPDATE_HOUR1Local hour, 0 to 23
AUTO_UPDATE_RESTARTonoff 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.

VariableNotes
PLUGBOARD_IMAGEThe image name, or name@sha256 for a digest pin
PLUGBOARD_VERSIONThe tag, or the bare digest hex
ENV_FILEWhich env file the containers read, so a second stack can run on one host
TUNNEL_TOKENFor the Cloudflare Tunnel ingress profile
ACME_EMAILFor the Caddy ingress profile: where Let’s Encrypt sends expiry warnings

See updating.

VariableNotes
SENTRY_DSNError reporting to your own self-hosted Sentry or GlitchTip. No default destination
VariableNotes
PLUGBOARD_DEMO_DATA1 seeds fictional sample data on a brand new database only
WEBHOOKS_DISABLED1 disables outbound webhook delivery
REPORTS_DISABLED1 disables the scheduled report loop

Vendor side only. Never set on a customer deployment.

VariablePurpose
CONTROL_PLANE_ADMIN_TOKEN32-byte random. The console’s shared admin token
LICENSE_PRIVATE_KEYbase64 Ed25519 PKCS8. The only copy of the signing key
CONTROL_PLANE_PUBLIC_URLWhere a human signs in
TELEMETRY_PUBLIC_URLEmbedded into issued licences as the reporting endpoint
REPORT_SKEW_MSReplay window for usage reports. Default 30 minutes
CP_POSTGRES_PASSWORDIts database password
PANEL_ACCESS_TEAM_DOMAINUsed to fetch the Access signing keys
PANEL_ACCESS_AUDThe Access application’s audience tag
PANEL_REQUIRE_ACCESS_JWT1 to refuse the shared token entirely
PROVISION_SECRET_KEY32 bytes base64. Seals generated secrets between pre-build and provisioning
PROVISION_RUNNER_TOKENWhat a region host presents to claim provisioning work
ONBOARDING_PUBLIC_URLWhere the customer’s confirm page lives
RESEND_API_KEYSends onboarding invitations
MAIL_FROMThe sending address. Its domain must be verified
MAIL_REPLY_TOWhere replies land. The sending subdomain has no inbox
CHARGEBEE_SITEThe site name, before .chargebee.com
CHARGEBEE_API_KEYFull-access API key
CHARGEBEE_WEBHOOK_USERBasic-auth username set on the webhook
CHARGEBEE_WEBHOOK_PASSWORDIts password. Chargebee does not sign webhooks, so this is the whole security boundary
CHARGEBEE_NET_TERM_DAYSPayment terms. Default 30
CHARGEBEE_PRICE_<TIER>Override an item price id

See the control plane.