Skip to main content

Environment variables

This page lists every environment variable the server reads, in groups. It is for whoever runs a self-hosted site. For how to give the server its settings (a .env file, Docker, wrangler.toml), see Give the server its settings; for the few every site needs, see The settings every site needs.

How values are read​

  • Names are case-sensitive and in capitals: SITE_URL, not site_url.
  • Yes or no settings (DB_AUTO_MIGRATE, DATABASE_SSL, SMTP_SECURE, S3_FORCE_PATH_STYLE) are true only for 1, true, yes or on, in any capitals, and with spaces around ignored. Any other value, such as false, 0, no or off, is false. An empty or missing value gives the setting's default.
  • Numbers are read as whole numbers. Text that is not a number gives the setting's default; text that starts with digits uses the digits (10abc is 10).
  • Lists (ALLOWED_ORIGINS) are comma-separated; spaces around each item are ignored.
  • Addresses (SITE_URL, PUBLIC_FILES_URL, S3_ENDPOINT, UPDATE_URL, ANTHROPIC_BASE_URL) have any trailing slashes removed.
  • Any variable can come from a file. Add _FILE to its name and give a path: ADMIN_PASSWORD_FILE=/run/secrets/admin_password. The file's contents (without the final line break) become the value, unless the plain variable is also set. This works on Node and Docker only, for the server's own settings in the tables below (including the TRANSACTIONAL_ and ANNOUNCEMENT_ ones), but not for SECRETS_PROVIDER, the secrets manager's own variables, CM_RELEASES_DIR, or the front-end build variables. See Keep secrets in files.
  • On Cloudflare, the same names go in wrangler.toml under [vars] (not secret) or are set with wrangler secret put NAME (secret). The database, the file buckets and the sessions store are bindings, not variables; see wrangler.toml explained.

In the tables, the Where column says All (Node, Docker and Cloudflare), Node (Node and Docker, not Cloudflare) or Cloudflare.

Core​

VariableDefaultValuesWhat it doesWhere
SITE_URLnonehttps://choir.example.orgThe public address of the site. Required in production. Emailed login links and unsubscribe links are built from it, and it is never taken from the visitor's request, so nobody can choose where a link leads. Without it, the server warns at start-up and cannot make login links. From 1.6.12 it is also the address each public page gives as its own to search engines and link previews (<link rel="canonical">, og:url), and what turns the address of a preview picture stored on the site into a full one: only its scheme and host are used for these, and without it they are left out. See How your site looks when a link is shared.All
NODE_ENVproductionproduction, developmentAnything other than production counts as development: cookies are not marked Secure, links in emails may follow the page that asked for them when SITE_URL is empty, and localhost addresses are allowed to call the API from other origins. Some warnings (such as EMAIL_PROVIDER=log) are shown only in production. The Docker image sets production.All
PORT3001a port numberThe port the server listens on. In Docker the container always listens on 3001 and the Compose file maps it to HTTP_PORT.Node
ALLOWED_ORIGINSnonehttps://a.example.org,https://b.example.orgOrigins allowed to call the API with login cookies, matched exactly. Needed only if the front end is served from a different address than the server.All
LOG_LEVELinfoanyRead but not used: it changes nothing.All
CM_RELEASES_DIRnoneSet by the launcher (npm start and the Docker image) to tell the server it can install an update itself. Do not set it.Node

Admin sign-in​

VariableDefaultValuesWhat it doesWhere
ADMIN_AUTHenvenv, tableenv: one admin login, the username and password below. table: each admin has an account and logs in with an emailed link, and the username and password remain a spare key. Any other value leaves nobody able to log in; the server warns at start-up. See Admin sign-in.All
ADMIN_USERNAMEnonetextThe admin login with ADMIN_AUTH=env; optional with table.All
ADMIN_PASSWORDnonetextIts password. In production the server warns if it is shorter than 12 characters.All
ADMIN_SESSION_HOURS24whole hoursHow long an admin stays logged in.All
MEMBER_SESSION_DAYS30whole daysHow long a member stays logged in.All

Database​

VariableDefaultValuesWhat it doesWhere
DB_PROVIDERsqlite on Node, d1 on Cloudflaresqlite, postgres, mysql (also postgresql, mariadb), d1Which database to use. An unknown value stops the server with "Unknown DB_PROVIDER". On Cloudflare the site always uses the D1 database bound as DB, whatever this says.All
SQLITE_PATH./data/choir.sqlitea file pathThe SQLite database file.Node
DATABASE_URLnonepostgres://user:pass@host:5432/db or mysql://user:pass@host:3306/dbThe connection address. Required for postgres and mysql.Node
DATABASE_SSLfalseyes or noUse an encrypted connection to PostgreSQL or MySQL. Hosted database services usually need it.Node
DB_AUTO_MIGRATEtrueyes or noCreate any missing tables when the server starts. On Cloudflare the schema is applied with wrangler instead.Node

See Choose a database.

Storage and uploads​

VariableDefaultValuesWhat it doesWhere
STORAGE_PROVIDERlocal on Node, r2 on Cloudflarelocal, s3 on Node; r2, s3 on CloudflareWhere uploaded files are kept. On Node, any other value stops the server. On Cloudflare, anything but s3 means R2.All
STORAGE_LOCAL_DIR./data/storagea directoryWhere local keeps files (public and private inside it).Node
S3_ENDPOINTnonean addressEmpty for Amazon S3. Otherwise the service's address, such as http://minio:9000.All
S3_REGIONautoa region nameThe region. us-east-1 suits MinIO and most S3 lookalikes.All
S3_ACCESS_KEY_ID, S3_SECRET_ACCESS_KEYnonetextThe credentials.All
S3_PUBLIC_BUCKET, S3_PRIVATE_BUCKETnonebucket namesThe two buckets. Keep the private one private. Both buckets and the credentials are required with s3.All
S3_FORCE_PATH_STYLEyes if S3_ENDPOINT is set, otherwise noyes or noAddress buckets as endpoint/bucket/key, as MinIO needs.All
PUBLIC_FILES_URLnonean addressWhere browsers fetch public files. Unset, the server serves them itself at /files/<key>.All
UPLOAD_MAX_IMAGE_MB10megabytesLargest image or poster.All
UPLOAD_MAX_GALLERY_MB100megabytesLargest gallery photo or video. On Cloudflare Workers keep it under about 90, the size of a request body they accept.All
UPLOAD_MAX_FILE_MB50megabytesLargest member file.All
UPLOAD_MAX_TRACK_MB50megabytesLargest rehearsal track.All

See How files are stored and Public file addresses and upload limits.

Sessions​

VariableDefaultValuesWhat it doesWhere
SESSION_STOREdatabase on Node, kv on Cloudflaredatabase, redis, memory on Node; database, kv on CloudflareWhere logins are remembered. memory forgets every login when the server restarts and does not work with more than one process; the server warns in production. On Cloudflare, database uses D1; anything else uses the SESSIONS KV binding, or D1 if the binding is missing.All
REDIS_URLnoneredis://host:6379Required with redis.Node

See Sessions.

Email​

See Email: what the site sends and how to choose a provider. The settings marked Yes in the last column can be repeated with TRANSACTIONAL_ or ANNOUNCEMENT_ in front for one kind of email (ANNOUNCEMENT_SMTP_HOST, TRANSACTIONAL_EMAIL_FROM); see Send announcements through a different service.

VariableDefaultValuesWhat it doesWherePrefix
EMAIL_PROVIDERlognone, log, ses, smtp, resend, sendgrid, mailgun, postmarkHow email is sent. log writes emails to the server log. none turns email off. smtp is not available on Cloudflare. An unknown value stops the server.AllYes
EMAIL_FROMnoneHarmony Community Choir <noreply@example.org>The sender. Required for every provider except log and none.AllYes
EMAIL_REPLY_TOnonean addressWhere replies go.AllYes
EMAIL_BOUNCE_ADDRESSnonean addressAmazon SES only: where bounce and complaint notices are forwarded.AllYes
CONTACT_FORM_TOthe public email in Site Settingsan addressWhere contact-form messages, and notices of someone stopping a kind of mail, are sent.AllNo
EMAIL_BATCH_SIZE15whole numberPeople sent to in each request when an announcement or mailing goes out.AllNo
EMAIL_PER_SECOND8whole numberMessages sent at once, then a pause of a second, to stay inside the provider's limit.AllNo
AWS_REGION, AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEYnonetextAmazon SES; all three required with ses. (Also used by the AWS secrets manager, below.)AllYes
SMTP_HOSTnonea server nameThe mail server. Required with smtp.NodeYes
SMTP_PORT587a port numberThe port.NodeYes
SMTP_SECUREfalseyes or noEncrypted from the first byte (implicit TLS, usually port 465).NodeYes
SMTP_USER, SMTP_PASSnonetextThe mail account. With no user, the site does not log in.NodeYes
RESEND_API_KEYnonetextRequired with resend.AllYes
SENDGRID_API_KEYnonetextRequired with sendgrid.AllYes
MAILGUN_API_KEY, MAILGUN_DOMAINnonetextRequired with mailgun.AllYes
MAILGUN_REGIONusus, euWhich Mailgun endpoint to use.AllYes
POSTMARK_SERVER_TOKENnonetextRequired with postmark.AllYes

The prefixed names in full​

A prefixed variable has no default of its own: when it is empty or missing, that kind of email uses the plain variable, and then the plain variable's default.

Kind of emailVariables
Transactional (login links, contact-form messages, tickets, receipts)TRANSACTIONAL_EMAIL_PROVIDER, TRANSACTIONAL_EMAIL_FROM, TRANSACTIONAL_EMAIL_REPLY_TO, TRANSACTIONAL_EMAIL_BOUNCE_ADDRESS, TRANSACTIONAL_AWS_REGION, TRANSACTIONAL_AWS_ACCESS_KEY_ID, TRANSACTIONAL_AWS_SECRET_ACCESS_KEY, TRANSACTIONAL_SMTP_HOST, TRANSACTIONAL_SMTP_PORT, TRANSACTIONAL_SMTP_SECURE, TRANSACTIONAL_SMTP_USER, TRANSACTIONAL_SMTP_PASS, TRANSACTIONAL_RESEND_API_KEY, TRANSACTIONAL_SENDGRID_API_KEY, TRANSACTIONAL_MAILGUN_API_KEY, TRANSACTIONAL_MAILGUN_DOMAIN, TRANSACTIONAL_MAILGUN_REGION, TRANSACTIONAL_POSTMARK_SERVER_TOKEN
Announcements and mailingsANNOUNCEMENT_EMAIL_PROVIDER, ANNOUNCEMENT_EMAIL_FROM, ANNOUNCEMENT_EMAIL_REPLY_TO, ANNOUNCEMENT_EMAIL_BOUNCE_ADDRESS, ANNOUNCEMENT_AWS_REGION, ANNOUNCEMENT_AWS_ACCESS_KEY_ID, ANNOUNCEMENT_AWS_SECRET_ACCESS_KEY, ANNOUNCEMENT_SMTP_HOST, ANNOUNCEMENT_SMTP_PORT, ANNOUNCEMENT_SMTP_SECURE, ANNOUNCEMENT_SMTP_USER, ANNOUNCEMENT_SMTP_PASS, ANNOUNCEMENT_RESEND_API_KEY, ANNOUNCEMENT_SENDGRID_API_KEY, ANNOUNCEMENT_MAILGUN_API_KEY, ANNOUNCEMENT_MAILGUN_DOMAIN, ANNOUNCEMENT_MAILGUN_REGION, ANNOUNCEMENT_POSTMARK_SERVER_TOKEN

Bot check​

VariableDefaultValuesWhat it doesWhere
CAPTCHA_PROVIDERnonenone, turnstile, hcaptcha, recaptchaWhich check the contact form uses. An unknown name refuses every message and logs Unknown CAPTCHA_PROVIDER.All
CAPTCHA_SITE_KEYnonetextThe public key, given to browsers. Required when a provider is set.All
CAPTCHA_SECRET_KEYnonetextThe secret key, used by the server to check answers. Required when a provider is set.All

See Stop spam with a bot check.

Updates and licence​

VariableDefaultValuesWhat it doesWhere
LICENSE_KEYnonetextYour licence key. With it, the admin panel says when a new version is out. Without it, the server warns and never contacts the release server.All
UPDATE_MODEself when started by the launcher, otherwise notifyself, notify, offself: admins can install an update with one click. notify: the admin panel shows the steps to do it by hand. off: no checks and no notice. self is not possible on Cloudflare. An unknown value is ignored, with a warning.All
UPDATE_CHANNELstablestable, beta, devWhich versions are offered. dev is given only to a licence that is on that channel. An unknown value counts as stable, with a warning.All
UPDATE_URLhttps://updates.choirmastercms.coman addressThe release server.All
DATA_DIR./dataa directoryWhere downloaded releases (releases) and pre-update database backups (backups) are kept. Must be writable and persistent for self.Node

See Your licence key and How updates work.

Writing assistant​

VariableDefaultValuesWhat it doesWhere
ANTHROPIC_API_KEYnonetextSwitches on the writing assistant in the guided setup. Without it the assistant's buttons are not shown. Use is billed to this key.All
ANTHROPIC_BASE_URLhttps://api.anthropic.coman addressOnly for sending requests through a proxy or gateway of your own.All
AI_MODELclaude-haiku-4-5a model nameThe model used.All
AI_DAILY_LIMIT30whole numberPieces of writing a day for the site. 0 means no limit.All

See Let the writing assistant draft your words.

Secrets​

These tell the server to fetch secrets from a secrets manager at start-up. A value already set in the environment is never overwritten. Node only. See Use a secrets manager.

VariableDefaultWhat it does
SECRETS_PROVIDERenvenv, vault, aws-secrets-manager, doppler or infisical. An unknown value stops the server.
VAULT_ADDR, VAULT_TOKEN, VAULT_SECRET_PATHnoneHashiCorp Vault: its address, a token, and the path of a KV secret (for example secret/data/choir). All three are required.
VAULT_NAMESPACEnoneOptional Vault namespace.
AWS_SECRETS_MANAGER_SECRET_ID, AWS_REGION, AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEYnoneAWS Secrets Manager: one secret holding a JSON object of NAME: value. All four are required.
AWS_SESSION_TOKENnoneOptional, for temporary AWS credentials.
DOPPLER_TOKENnoneDoppler: a service token for one config.
INFISICAL_TOKEN, INFISICAL_PROJECT_ID, INFISICAL_ENVIRONMENTnoneInfisical: a service token, the project and the environment (such as prod). All three are required.
INFISICAL_URLhttps://app.infisical.comOptional, for a self-hosted Infisical.
INFISICAL_SECRET_PATH/Optional folder inside the project.

Building the site's front end​

Read when the site is built, not when the server runs.

VariableDefaultWhat it does
VITE_API_URLnoneOnly if the front end is hosted away from the server: the server's address.
VITE_API_PROXYhttp://localhost:3001Development only: where the development server passes /api and /files on to.

Docker Compose only​

These are read by docker-compose.yml, not by the server. Put them in the same .env file. They have no effect on a plain Node or Cloudflare install.

VariableDefaultWhat it does
HTTP_PORT8080The port on the host that reaches the site.
POSTGRES_USERchoirThe PostgreSQL user the Compose file creates and connects as.
POSTGRES_PASSWORDnone, requiredIts password. Compose refuses to start without it.
POSTGRES_DBchoirThe database name.
MINIO_ROOT_USERchoirThe MinIO administrator, which the site also uses as its storage key.
MINIO_ROOT_PASSWORDnone, requiredIts password. Compose refuses to start without it.
S3_PUBLIC_BUCKET, S3_PRIVATE_BUCKETchoir-public, choir-privateThe bucket names Compose creates in MinIO and gives the site.
SESSION_STOREdatabasePassed through to the site.
DOMAINnone, required with the tls profileThe site's domain, for the Caddy container that gets an HTTPS certificate.

Compose sets NODE_ENV, PORT, DB_PROVIDER, DATABASE_URL, STORAGE_PROVIDER, S3_ENDPOINT, S3_REGION, S3_FORCE_PATH_STYLE and the S3 credentials itself, and what it sets wins over .env, so leave those out of .env. See Docker Compose: PostgreSQL and MinIO.

Names starting HOSTED_ and PLATFORM_​

Variables beginning HOSTED_ or PLATFORM_ belong to the publisher's own hosted service. They are not for licensees and have no use on a site you run yourself: leave them unset. The server reads them, so they are listed here for completeness.

VariableDefaultWhat it is for on the hosted service
PLATFORM_TOKENnoneThe secret the hosted service's account system presents to the server.
PLATFORM_BILLING_URLnoneWhere a site's admin is sent to change its plan.
PLATFORM_SUPPORT_URLnoneWhere the service's default page sends someone who needs help. Must start https:// or mailto:.
HOSTED_SECRETS_KEYnoneThe key that a site's own secrets, such as its mail server's password, are sealed under.
HOSTED_STRIPE_KEYnoneThe service's Stripe key, used with each choir's own connected account.
HOSTED_EMAIL_FROMnoneThe pattern for the From line of a site's email.
HOSTED_EMAIL_PER_SECOND10How many announcement emails a second the shared queue may send.
HOSTED_EMAIL_PER_DAY50000How many it may send in 24 hours.
HOSTED_EMAIL_RESERVE5000How much of the day's allowance is kept back for login links.