Skip to main content

Give the server its settings

Everything about a self-hosted site that is not edited in the admin panel is set with environment variables: the site's address, the admin login, the database, where files are kept, how email is sent. The names are the same on every platform. This page is for whoever installs the server, and explains how to get those variables to it.

The server does not read .env by itself​

Neither the server nor its helper scripts open a .env file. They read process.env and nothing else. If you write your settings in a file, something has to load that file into the environment before Node starts. Any of these does it:

Where you run itHow the settings get in
Plain Node, by handnode --env-file=.env server/launcher.js
systemdEnvironmentFile=/opt/choirmaster/.env in the unit
Docker Composeenv_file: .env on the service (both Compose files in the source do this)
docker run--env-file .env
A shellexport NAME=value before npm start
A platform service (Fly.io, Railway, Render and the like)The platform's own settings or secrets screen
Cloudflare Workers[vars] in wrangler.toml and wrangler secret put

The download has no .env file and no example of one. The settings every site needs has a template to copy.

Plain Node​

npm start runs node server/launcher.js. The launcher starts the real server as a child process and hands it its whole environment, so loading the file into the launcher is enough:

cd /opt/choirmaster
node --env-file=.env server/launcher.js

--env-file is built into Node (the server needs Node 22.13 or newer in any case). A value may be wrapped in double quotes, which Node removes:

EMAIL_FROM="Harmony Community Choir <noreply@example.org>"

systemd​

[Service]
WorkingDirectory=/opt/choirmaster
EnvironmentFile=/opt/choirmaster/.env
ExecStart=/usr/bin/node server/launcher.js

The full unit is in Run it as a systemd service.

Docker​

docker run -p 8080:3001 --env-file .env -v choir-data:/app/data choir-manager
Quotes in a file given to docker run --env-file

docker run --env-file does not remove quotes: EMAIL_FROM="Harmony <noreply@example.org>" arrives with the quotation marks as part of the value. Write the line without them there. Compose's env_file, Node's --env-file and systemd's EnvironmentFile all remove them.

In Compose, the environment: block of the service wins over env_file. Both Compose files in the source set NODE_ENV, PORT, the database and the storage that way, so those lines in your .env are ignored there. See Run it in Docker.

Cloudflare Workers​

A Worker has no environment file. Values that are not secret go in wrangler.toml:

[vars]
NODE_ENV = "production"
SITE_URL = "https://choir.example.org"
EMAIL_PROVIDER = "resend"
EMAIL_FROM = "Harmony Community Choir <noreply@example.org>"

Secrets are stored encrypted with Wrangler and reach the Worker the same way as a variable:

npx wrangler secret put ADMIN_USERNAME
npx wrangler secret put ADMIN_PASSWORD
npx wrangler secret put RESEND_API_KEY

[vars] take effect at the next npx wrangler deploy. The database, the two file buckets and the session store are not variables on Cloudflare but bindings; see wrangler.toml explained.

The helper scripts need the same settings​

The scripts in scripts/ read the environment exactly as the server does, so a script run without your settings works on the defaults: a new, empty SQLite file in ./data, not your database. Give them the same file:

node --env-file=.env scripts/check-config.js
node --env-file=.env scripts/add-admin.js conductor@example.org "Alex Example"
node --env-file=.env scripts/migrate.js

npm run config:check, npm run admin:add and npm run db:migrate are the same scripts without the file, which is right when the variables are already in the environment. That is the case inside a container:

docker compose exec app npm run config:check
docker compose exec app npm run admin:add -- conductor@example.org "Alex Example"

On Cloudflare the scripts are not used at all. They open a database from Node, and a Worker's database is reached with wrangler d1 execute instead.

How values are read​

Kind of settingHow the value is read
On or off (DATABASE_SSL, DB_AUTO_MIGRATE, SMTP_SECURE, S3_FORCE_PATH_STYLE)1, true, yes and on mean on, in any mix of capitals. Anything else means off. Empty or unset gives the default.
Whole number (PORT, the session lengths, the upload limits, EMAIL_BATCH_SIZE)Read as a whole number: 1.5 is read as 1. Something that is not a number gives the default, with no warning.
Address (SITE_URL, PUBLIC_FILES_URL, S3_ENDPOINT, UPDATE_URL)Slashes at the end are removed.
List (ALLOWED_ORIGINS)Split at commas; spaces around each item are removed.
A choice from a list (ADMIN_AUTH, UPDATE_MODE, UPDATE_CHANNEL, SECRETS_PROVIDER)Capitals and spaces around the value do not matter.
A choice from a list (DB_PROVIDER, STORAGE_PROVIDER, SESSION_STORE, EMAIL_PROVIDER, CAPTCHA_PROVIDER)Must be written exactly, in lower case. Postgres is not postgres.

A variable set to an empty value counts as not set for the on-or-off settings, and for the purposes of NAME_FILE and a secrets manager. For the other settings an empty value is not the same as leaving the line out: DB_PROVIDER= is read as an empty name and stops the server, and CAPTCHA_PROVIDER= is not read as none. Delete the line, or put a # in front of it, rather than leaving it empty. (The email settings and ADMIN_AUTH do fall back to their defaults when empty.)

PORT and DATA_DIR must be real environment variables​

The launcher reads PORT and DATA_DIR itself, before the server starts and before any secret is loaded. It uses PORT to check that a newly installed version answers, and DATA_DIR to find downloaded versions. A value that arrives only through PORT_FILE, DATA_DIR_FILE or a secrets manager reaches the server but not the launcher, and the two then disagree. Set these two in the environment itself.

Check what the server sees​

node --env-file=.env scripts/check-config.js

It prints the configuration the server would run with, with secrets hidden, and lists anything wrong. Check your configuration explains the output.

Every variable, with its default, is listed in Environment variables.