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, notsite_url. - Yes or no settings (
DB_AUTO_MIGRATE,DATABASE_SSL,SMTP_SECURE,S3_FORCE_PATH_STYLE) are true only for1,true,yesoron, in any capitals, and with spaces around ignored. Any other value, such asfalse,0,nooroff, 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 (
10abcis 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
_FILEto 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 theTRANSACTIONAL_andANNOUNCEMENT_ones), but not forSECRETS_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.tomlunder[vars](not secret) or are set withwrangler 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
| Variable | Default | Values | What it does | Where |
|---|---|---|---|---|
SITE_URL | none | https://choir.example.org | The 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_ENV | production | production, development | Anything 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 |
PORT | 3001 | a port number | The port the server listens on. In Docker the container always listens on 3001 and the Compose file maps it to HTTP_PORT. | Node |
ALLOWED_ORIGINS | none | https://a.example.org,https://b.example.org | Origins 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_LEVEL | info | any | Read but not used: it changes nothing. | All |
CM_RELEASES_DIR | none | Set 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
| Variable | Default | Values | What it does | Where |
|---|---|---|---|---|
ADMIN_AUTH | env | env, table | env: 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_USERNAME | none | text | The admin login with ADMIN_AUTH=env; optional with table. | All |
ADMIN_PASSWORD | none | text | Its password. In production the server warns if it is shorter than 12 characters. | All |
ADMIN_SESSION_HOURS | 24 | whole hours | How long an admin stays logged in. | All |
MEMBER_SESSION_DAYS | 30 | whole days | How long a member stays logged in. | All |
Database
| Variable | Default | Values | What it does | Where |
|---|---|---|---|---|
DB_PROVIDER | sqlite on Node, d1 on Cloudflare | sqlite, postgres, mysql (also postgresql, mariadb), d1 | Which 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.sqlite | a file path | The SQLite database file. | Node |
DATABASE_URL | none | postgres://user:pass@host:5432/db or mysql://user:pass@host:3306/db | The connection address. Required for postgres and mysql. | Node |
DATABASE_SSL | false | yes or no | Use an encrypted connection to PostgreSQL or MySQL. Hosted database services usually need it. | Node |
DB_AUTO_MIGRATE | true | yes or no | Create any missing tables when the server starts. On Cloudflare the schema is applied with wrangler instead. | Node |
See Choose a database.
Storage and uploads
| Variable | Default | Values | What it does | Where |
|---|---|---|---|---|
STORAGE_PROVIDER | local on Node, r2 on Cloudflare | local, s3 on Node; r2, s3 on Cloudflare | Where uploaded files are kept. On Node, any other value stops the server. On Cloudflare, anything but s3 means R2. | All |
STORAGE_LOCAL_DIR | ./data/storage | a directory | Where local keeps files (public and private inside it). | Node |
S3_ENDPOINT | none | an address | Empty for Amazon S3. Otherwise the service's address, such as http://minio:9000. | All |
S3_REGION | auto | a region name | The region. us-east-1 suits MinIO and most S3 lookalikes. | All |
S3_ACCESS_KEY_ID, S3_SECRET_ACCESS_KEY | none | text | The credentials. | All |
S3_PUBLIC_BUCKET, S3_PRIVATE_BUCKET | none | bucket names | The two buckets. Keep the private one private. Both buckets and the credentials are required with s3. | All |
S3_FORCE_PATH_STYLE | yes if S3_ENDPOINT is set, otherwise no | yes or no | Address buckets as endpoint/bucket/key, as MinIO needs. | All |
PUBLIC_FILES_URL | none | an address | Where browsers fetch public files. Unset, the server serves them itself at /files/<key>. | All |
UPLOAD_MAX_IMAGE_MB | 10 | megabytes | Largest image or poster. | All |
UPLOAD_MAX_GALLERY_MB | 100 | megabytes | Largest gallery photo or video. On Cloudflare Workers keep it under about 90, the size of a request body they accept. | All |
UPLOAD_MAX_FILE_MB | 50 | megabytes | Largest member file. | All |
UPLOAD_MAX_TRACK_MB | 50 | megabytes | Largest rehearsal track. | All |
See How files are stored and Public file addresses and upload limits.
Sessions
| Variable | Default | Values | What it does | Where |
|---|---|---|---|---|
SESSION_STORE | database on Node, kv on Cloudflare | database, redis, memory on Node; database, kv on Cloudflare | Where 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_URL | none | redis://host:6379 | Required 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.
| Variable | Default | Values | What it does | Where | Prefix |
|---|---|---|---|---|---|
EMAIL_PROVIDER | log | none, log, ses, smtp, resend, sendgrid, mailgun, postmark | How 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. | All | Yes |
EMAIL_FROM | none | Harmony Community Choir <noreply@example.org> | The sender. Required for every provider except log and none. | All | Yes |
EMAIL_REPLY_TO | none | an address | Where replies go. | All | Yes |
EMAIL_BOUNCE_ADDRESS | none | an address | Amazon SES only: where bounce and complaint notices are forwarded. | All | Yes |
CONTACT_FORM_TO | the public email in Site Settings | an address | Where contact-form messages, and notices of someone stopping a kind of mail, are sent. | All | No |
EMAIL_BATCH_SIZE | 15 | whole number | People sent to in each request when an announcement or mailing goes out. | All | No |
EMAIL_PER_SECOND | 8 | whole number | Messages sent at once, then a pause of a second, to stay inside the provider's limit. | All | No |
AWS_REGION, AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY | none | text | Amazon SES; all three required with ses. (Also used by the AWS secrets manager, below.) | All | Yes |
SMTP_HOST | none | a server name | The mail server. Required with smtp. | Node | Yes |
SMTP_PORT | 587 | a port number | The port. | Node | Yes |
SMTP_SECURE | false | yes or no | Encrypted from the first byte (implicit TLS, usually port 465). | Node | Yes |
SMTP_USER, SMTP_PASS | none | text | The mail account. With no user, the site does not log in. | Node | Yes |
RESEND_API_KEY | none | text | Required with resend. | All | Yes |
SENDGRID_API_KEY | none | text | Required with sendgrid. | All | Yes |
MAILGUN_API_KEY, MAILGUN_DOMAIN | none | text | Required with mailgun. | All | Yes |
MAILGUN_REGION | us | us, eu | Which Mailgun endpoint to use. | All | Yes |
POSTMARK_SERVER_TOKEN | none | text | Required with postmark. | All | Yes |
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 email | Variables |
|---|---|
| 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 mailings | ANNOUNCEMENT_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
| Variable | Default | Values | What it does | Where |
|---|---|---|---|---|
CAPTCHA_PROVIDER | none | none, turnstile, hcaptcha, recaptcha | Which check the contact form uses. An unknown name refuses every message and logs Unknown CAPTCHA_PROVIDER. | All |
CAPTCHA_SITE_KEY | none | text | The public key, given to browsers. Required when a provider is set. | All |
CAPTCHA_SECRET_KEY | none | text | The 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
| Variable | Default | Values | What it does | Where |
|---|---|---|---|---|
LICENSE_KEY | none | text | Your 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_MODE | self when started by the launcher, otherwise notify | self, notify, off | self: 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_CHANNEL | stable | stable, beta, dev | Which 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_URL | https://updates.choirmastercms.com | an address | The release server. | All |
DATA_DIR | ./data | a directory | Where 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
| Variable | Default | Values | What it does | Where |
|---|---|---|---|---|
ANTHROPIC_API_KEY | none | text | Switches 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_URL | https://api.anthropic.com | an address | Only for sending requests through a proxy or gateway of your own. | All |
AI_MODEL | claude-haiku-4-5 | a model name | The model used. | All |
AI_DAILY_LIMIT | 30 | whole number | Pieces 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.
| Variable | Default | What it does |
|---|---|---|
SECRETS_PROVIDER | env | env, vault, aws-secrets-manager, doppler or infisical. An unknown value stops the server. |
VAULT_ADDR, VAULT_TOKEN, VAULT_SECRET_PATH | none | HashiCorp Vault: its address, a token, and the path of a KV secret (for example secret/data/choir). All three are required. |
VAULT_NAMESPACE | none | Optional Vault namespace. |
AWS_SECRETS_MANAGER_SECRET_ID, AWS_REGION, AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY | none | AWS Secrets Manager: one secret holding a JSON object of NAME: value. All four are required. |
AWS_SESSION_TOKEN | none | Optional, for temporary AWS credentials. |
DOPPLER_TOKEN | none | Doppler: a service token for one config. |
INFISICAL_TOKEN, INFISICAL_PROJECT_ID, INFISICAL_ENVIRONMENT | none | Infisical: a service token, the project and the environment (such as prod). All three are required. |
INFISICAL_URL | https://app.infisical.com | Optional, 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.
| Variable | Default | What it does |
|---|---|---|
VITE_API_URL | none | Only if the front end is hosted away from the server: the server's address. |
VITE_API_PROXY | http://localhost:3001 | Development 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.
| Variable | Default | What it does |
|---|---|---|
HTTP_PORT | 8080 | The port on the host that reaches the site. |
POSTGRES_USER | choir | The PostgreSQL user the Compose file creates and connects as. |
POSTGRES_PASSWORD | none, required | Its password. Compose refuses to start without it. |
POSTGRES_DB | choir | The database name. |
MINIO_ROOT_USER | choir | The MinIO administrator, which the site also uses as its storage key. |
MINIO_ROOT_PASSWORD | none, required | Its password. Compose refuses to start without it. |
S3_PUBLIC_BUCKET, S3_PRIVATE_BUCKET | choir-public, choir-private | The bucket names Compose creates in MinIO and gives the site. |
SESSION_STORE | database | Passed through to the site. |
DOMAIN | none, required with the tls profile | The 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.
| Variable | Default | What it is for on the hosted service |
|---|---|---|
PLATFORM_TOKEN | none | The secret the hosted service's account system presents to the server. |
PLATFORM_BILLING_URL | none | Where a site's admin is sent to change its plan. |
PLATFORM_SUPPORT_URL | none | Where the service's default page sends someone who needs help. Must start https:// or mailto:. |
HOSTED_SECRETS_KEY | none | The key that a site's own secrets, such as its mail server's password, are sealed under. |
HOSTED_STRIPE_KEY | none | The service's Stripe key, used with each choir's own connected account. |
HOSTED_EMAIL_FROM | none | The pattern for the From line of a site's email. |
HOSTED_EMAIL_PER_SECOND | 10 | How many announcement emails a second the shared queue may send. |
HOSTED_EMAIL_PER_DAY | 50000 | How many it may send in 24 hours. |
HOSTED_EMAIL_RESERVE | 5000 | How much of the day's allowance is kept back for login links. |