Skip to main content

The settings every site needs

A site starts with no settings at all, but it is not usable: nobody can log in, no email is sent and emailed links cannot be built. This page lists the minimum for a real site and gives a template to start from. It is for whoever installs the server.

The minimum​

SettingWhat it is forDefault
SITE_URLThe public address of the site, such as https://choir.example.org. Every link the site puts in an email is built from it: login links for members and admins, links to tickets, unsubscribe links. So is the address each public page gives as its own to search engines and link previews.none
ADMIN_USERNAME, ADMIN_PASSWORDThe login for the admin panel.none
LICENSE_KEYYour licence key. With it, the admin panel can tell you when a new version is out, and install it where that is possible.none
EMAIL_PROVIDERHow email is sent. The default, log, sends nothing: it writes each email to the server log.log
EMAIL_FROMThe name and address email comes from, such as Harmony Community Choir <noreply@example.org>.none
The provider's own keysFor example RESEND_API_KEY, or SMTP_HOST and its companions.none

Without a database or storage setting the site keeps everything in a data folder beside the code: a SQLite database and the uploaded files. That is a sound choice for one server. The alternatives start at Choose a database and How files are stored.

SITE_URL​

Write the address people type, with https:// and without a path. A slash at the end is removed.

In production the site never works the address out from the request, because whoever sends a request could then decide where an emailed link leads. So without SITE_URL, anything that needs to email a link fails. The site still starts, and the configuration check says:

SITE_URL is not set: emailed login links need the site address

From version 1.6.12 every public page is sent with its own proper address in it (<link rel="canonical"> and og:url), for search engines and for the preview shown when a link is shared. That address is SITE_URL's scheme and host followed by the page's path, so a path written in SITE_URL is not used for it. For the same reason as with emailed links, it is never taken from the request: behind a proxy the address a request arrives on is not the site's, and its Host header is whatever the caller wrote. Without SITE_URL pages go out with no address of their own, and a background photo uploaded to the site cannot be offered as the preview picture. If SITE_URL is the wrong address (http:// where the site is https://, or an old domain), search engines are told the wrong one. See How your site looks when a link is shared.

The host name in SITE_URL, and nothing else of the address, is also what the site sends with your licence key when it checks for updates. See Your licence key.

ADMIN_USERNAME and ADMIN_PASSWORD​

One login shared by everyone who runs the site, unless you change how admins sign in. Until both are set nobody can log in.

Use a long random password. In production the configuration check warns about one shorter than 12 characters; the site still accepts it. Capitals in the username do not matter when logging in. They do in the password.

LICENSE_KEY​

Every self-hosted site has a key, including the free Community edition. Without one the site runs, but never contacts the release server and cannot tell you about new versions. Get a licence key and the download explains where it comes from.

Email​

Members log in with a link sent by email, so a site without working email has no member portal. Choose a provider and set EMAIL_PROVIDER, EMAIL_FROM and that provider's keys: Email: what the site sends and how to choose a provider.

Settings you may not need​

SettingWhat it doesDefault
NODE_ENVproduction or development. See below.production
PORTThe port the Node server listens on. Not used on Cloudflare.3001
ALLOWED_ORIGINSOther origins whose pages may call this site's API with a visitor's login.none
LOG_LEVELAccepted, and does nothing.info

NODE_ENV​

Leave it unset, or set it to production, on a real site. Any other value counts as not production, and relaxes these things for working on a laptop:

  • Session cookies are not marked Secure, so logging in works over plain http://.
  • Emailed links follow the address of the page that asked for them when SITE_URL is not set.
  • Pages served from http://localhost or http://127.0.0.1, on any port, may call the API.
  • The configuration check no longer reports a missing SITE_URL, a short password, EMAIL_PROVIDER=log or SESSION_STORE=memory.

In production the cookies are Secure, so the browser only sends them back over HTTPS. A production site reached over plain HTTP lets nobody stay logged in. See Put it behind HTTPS.

PORT​

Set it in the environment itself, not through a file or a secrets manager: the launcher reads it too. In Docker, leave it at 3001 and publish whichever host port you want.

ALLOWED_ORIGINS​

The site and its API normally share one address, and this stays empty. It is only for a frontend served from a different origin, which the download does not do by itself. Give a comma-separated list of complete origins:

ALLOWED_ORIGINS=https://www.example.org,https://choir.example.org

Each one is matched exactly: scheme, host and port, with no path and no slash at the end. There are no wildcards.

LOG_LEVEL​

The server reads LOG_LEVEL and uses it for nothing. Version 1.6.12 has one level of logging, and no setting changes how much is written.

A template to copy​

The download has no .env file. Save this as .env beside package.json, fill in the first two blocks, and remove the # from any other line you need. Then make sure the file actually reaches the server.

# Choir Master CMS settings (Node and Docker).
# On Cloudflare the same names go in wrangler.toml [vars] and `wrangler secret put`.

# ---- Required ---------------------------------------------------------------
# The public address of the site. Every emailed link is built from it.
SITE_URL=https://choir.example.org
# The admin panel login. Use a long random password (12 characters or more).
ADMIN_USERNAME=admin
ADMIN_PASSWORD=change-me-to-something-long-and-random
# Your licence key.
LICENSE_KEY=

# ---- Email ------------------------------------------------------------------
# none | log | ses | smtp | resend | sendgrid | mailgun | postmark
# log sends nothing: emails are written to the server log.
EMAIL_PROVIDER=log
EMAIL_FROM="Harmony Community Choir <noreply@example.org>"
# EMAIL_REPLY_TO=board@example.org
# Where contact-form messages go. Defaults to the public contact email in Site Settings.
# CONTACT_FORM_TO=board@example.org
# -- smtp (Node only)
# SMTP_HOST=smtp.example.org
# SMTP_PORT=587
# SMTP_SECURE=false
# SMTP_USER=
# SMTP_PASS=
# -- ses
# AWS_REGION=us-east-1
# AWS_ACCESS_KEY_ID=
# AWS_SECRET_ACCESS_KEY=
# EMAIL_BOUNCE_ADDRESS=bounce@example.org
# -- resend / sendgrid / mailgun / postmark
# RESEND_API_KEY=
# SENDGRID_API_KEY=
# MAILGUN_API_KEY=
# MAILGUN_DOMAIN=mg.example.org
# MAILGUN_REGION=us
# POSTMARK_SERVER_TOKEN=
# Any email setting can be given again for one kind of email, with
# TRANSACTIONAL_ or ANNOUNCEMENT_ in front.
# ANNOUNCEMENT_EMAIL_PROVIDER=smtp
# ANNOUNCEMENT_EMAIL_FROM="Harmony Community Choir <news@example.org>"
# EMAIL_BATCH_SIZE=15
# EMAIL_PER_SECOND=8

# ---- Admin sign-in ----------------------------------------------------------
# env: the one username and password above.
# table: each admin has an account and logs in by emailed link.
# ADMIN_AUTH=env

# ---- Database ---------------------------------------------------------------
# sqlite | postgres | mysql
# DB_PROVIDER=sqlite
# SQLITE_PATH=./data/choir.sqlite
# DATABASE_URL=postgres://choir:password@db.example.org:5432/choir
# DATABASE_SSL=false
# DB_AUTO_MIGRATE=true

# ---- File storage -----------------------------------------------------------
# local | s3
# STORAGE_PROVIDER=local
# STORAGE_LOCAL_DIR=./data/storage
# S3_ENDPOINT=
# S3_REGION=auto
# S3_ACCESS_KEY_ID=
# S3_SECRET_ACCESS_KEY=
# S3_PUBLIC_BUCKET=choir-public
# S3_PRIVATE_BUCKET=choir-private
# S3_FORCE_PATH_STYLE=true
# Leave unset and the site serves public files itself at /files/<key>.
# PUBLIC_FILES_URL=https://files.example.org
# UPLOAD_MAX_IMAGE_MB=10
# UPLOAD_MAX_GALLERY_MB=100
# UPLOAD_MAX_FILE_MB=50
# UPLOAD_MAX_TRACK_MB=50

# ---- Sessions ---------------------------------------------------------------
# database | redis | memory
# SESSION_STORE=database
# REDIS_URL=redis://redis:6379
# ADMIN_SESSION_HOURS=24
# MEMBER_SESSION_DAYS=30

# ---- Bot check on public forms ----------------------------------------------
# none | turnstile | hcaptcha | recaptcha
# CAPTCHA_PROVIDER=none
# CAPTCHA_SITE_KEY=
# CAPTCHA_SECRET_KEY=

# ---- Updates ----------------------------------------------------------------
# self | notify | off
# UPDATE_MODE=self
# stable | beta | dev
# UPDATE_CHANNEL=stable
# DATA_DIR=./data

# ---- Writing assistant in the guided setup (optional) -----------------------
# ANTHROPIC_API_KEY=
# AI_DAILY_LIMIT=30

# ---- Secrets ----------------------------------------------------------------
# env | vault | aws-secrets-manager | doppler | infisical
# SECRETS_PROVIDER=env
# Any setting can be read from a file instead:
# ADMIN_PASSWORD_FILE=/run/secrets/admin_password

# ---- Server -----------------------------------------------------------------
# PORT=3001
# ALLOWED_ORIGINS=

# ---- Read by docker-compose.yml only, not by the site ------------------------
# HTTP_PORT=8080
# POSTGRES_USER=choir
# POSTGRES_PASSWORD=change-me
# POSTGRES_DB=choir
# MINIO_ROOT_USER=choir
# MINIO_ROOT_PASSWORD=change-me-too
# DOMAIN=choir.example.org
Keep the file private

This file holds the admin password and your email provider's key. On a server, make it readable only by the account the site runs as (chmod 600 .env), and never commit it to a repository.

If you pass the file to docker run --env-file, remove the quotation marks around EMAIL_FROM: that command keeps them as part of the value.

Afterwards​

  1. Run the configuration check and clear every problem it lists.
  2. Start the site and log in at /admin/login.

Every variable, including the ones this page leaves out, is in Environment variables.