Skip to content

Configuration

Everything is an environment variable. There is nothing to configure in a file, and no settings page for any of these — config is validated at startup and fails loudly if wrong.

Variable What it’s for
PUBLIC_BASE_URL The URL this instance is reachable at. It’s baked into your QR codes, so it must be the URL a phone can reach.
ADMIN_EMAIL Your login.
ADMIN_PASSWORD Your password. Applied on every start — change it in .env and restart to rotate.
SESSION_SECRET Signs the login cookie. openssl rand -hex 32
VOTE_SALT Salts the anonymous-vote hash. openssl rand -hex 32

There is deliberately no first-run setup page: the single admin account is (re)applied from ADMIN_EMAIL / ADMIN_PASSWORD every time the container starts.

Variable Default What it’s for
DATA_DIR /data Where the SQLite database and photos live. One volume, everything in it.
SITE_TITLE the instance’s hostname Your site name, shown in the persistent nav on every public page.
SITE_TAGLINE The line under it on the public index. Write your own.
TZ UTC Set it — lot codes use your local month.
TRUST_PROXY unset Set to 1 when Mash sits behind your reverse proxy: rate limits then key on the client address your proxy reports (the last X-Forwarded-For entry) instead of the proxy’s own — otherwise every visitor shares one rate-limit bucket. Leave unset when the app is directly reachable: the header would be forgeable.
ANALYTICS_SCRIPT_URL unset Loads an analytics snippet on public bottle pages. Unset = Mash phones nobody.
ANALYTICS_WEBSITE_ID unset Rendered as data-website-id (Umami).
ANALYTICS_DOMAIN unset Rendered as data-domain (Plausible).
SMTP_URL unset smtp:// or smtps:// endpoint for outbound email. Unset = no email, and every email feature hides itself.
MAIL_FROM The From address, e.g. Mash <mash@example.com>. Required together with SMTP_URL.
BACKUP_TOKEN unset Bearer token for scripted backup downloads (GET /api/backup). Unset = backups are admin-only. openssl rand -hex 32
PHOTO_S3_ENDPOINT unset S3-compatible endpoint for the photo mirror. All four PHOTO_S3_* credentials must be set together; all unset = no mirror.
PHOTO_S3_BUCKET The bucket the mirror writes to.
PHOTO_S3_ACCESS_KEY / PHOTO_S3_SECRET_KEY Credentials. Put/delete on objects is all the mirror ever does.
PHOTO_S3_REGION auto Region, if your provider cares.
PHOTO_S3_PREFIX photos/ Key prefix inside the bucket.
MIGRATIONS_DIR ./drizzle Where the bundled database migrations live, relative to the working directory. Only needed if you start the app from somewhere other than its own directory.
  • PUBLIC_BASE_URL matters twice: it’s printed into QR codes and used for CSRF origin checks, so it must match what browsers actually see. Behind a reverse proxy, set it to the public URL (Operations).
  • Analytics load only on public bottle pages, never in the admin, and degrade to nothing if blocked. Details in Public pages & sharing.
  • Email is optional and queued through an outbox; if SMTP_URL is unset the features that would send mail simply don’t appear.
  • The mail queue shows up on Admin → Settings, beside the photo mirror, once email is configured. It reports how many messages are queued, sent, and given up, and lists any message that has failed to send along with its error — each can be retried or discarded from there. A Run queue now button sends any due mail immediately instead of waiting for the background drainer. Sent history is kept for 90 days and pruned automatically after that.
  • The photo mirror copies every photo to an S3-compatible bucket (AWS, MinIO, R2, B2) as it is saved, and removes it when you delete it — a durability copy, never used for serving. It is queued like the email outbox, so a bucket outage never blocks an upload. Enabling it on an instance that already has photos? Press Reconcile on Admin → Settings once. Restore after a disaster with aws s3 sync s3://your-bucket/photos/ ./data/photos/.