Documentation
Configuration Reference
Every environment variable, what lives in config.json vs secrets.json, file paths, volumes, and ports — the single page a self-hoster needs.
Configuration Reference
This is the complete reference for configuring a Briter instance: every environment variable, the two state files (config.json and secrets.json), the file paths and Docker volumes Briter uses, and the network port it listens on.
Briter resolves its runtime configuration in three layers, lowest priority first:
- Built-in defaults — hardcoded in
lib/config/runtime-config.ts. storage/config.json— written by the setup wizard and the settings UI.- Environment variables — override both of the above for any key they set.
A value set in config.json overrides the default; an environment variable overrides config.json. Unset environment variables are ignored (they never clobber a config.json value with undefined).
For most installs you do not need to set any environment variables — the setup wizard writes everything into config.json. Environment variables exist for container orchestration, headless/agent installs, and overriding paths in non-standard layouts.
Environment variables
Every variable below is read in lib/config/env.ts / lib/config/runtime-config.ts (runtime config) or in the worker entrypoint scripts/run-git-sync-worker.mjs (worker-only). Names, defaults, and effects are taken directly from those files.
Core / paths
| Variable | Default | What it does |
|---|---|---|
BRITER_APP_ROOT |
current working directory (/app in Docker) |
Base directory all other relative paths resolve against. Do not change inside the official Docker image. |
BRITER_CONTENT_ROOT |
<appRoot>/content/posts |
Directory holding post .mdx/.md files. |
BRITER_SECTIONS_ROOT |
<appRoot>/content/sections |
Directory holding section definition JSON files. |
BRITER_UPLOAD_ROOT |
<appRoot>/storage/uploads/posts |
Directory where processed image variants and manifests are stored. |
BRITER_QUEUE_ROOT |
<appRoot>/storage/queue |
Directory where the web app writes git-sync job files and the worker reads them. |
PORT |
3075 |
TCP port the Next.js server listens on. |
NODE_ENV |
(set by the runtime) | When production, the session cookie gets Secure, HSTS is sent, and the CSP drops 'unsafe-eval'. See Security. |
Site identity
These have no effect when the git-target adapter is active and the external site renders its own metadata; they drive the built-in reader.
| Variable | Default | What it does |
|---|---|---|
BRITER_SITE_NAME |
Briter |
Site name shown in the reader header and RSS title. |
BRITER_SITE_DESCRIPTION |
A small, opinionated writing app. |
Meta description and RSS feed description. |
BRITER_SITE_AUTHOR |
Writer |
Author name on post pages and in RSS. |
BRITER_PUBLIC_BASE_URL |
http://localhost:3075 |
Full public URL (scheme + host). Used for canonical links, OpenGraph, RSS <link>, and as a trusted origin for the CSRF same-origin check (see Security). Set this to your real domain in production. |
BRITER_PUBLISHED_HREF_TEMPLATE |
"" (empty) |
Optional template for the public URL of a published post, e.g. https://<your-domain>/notes/{slug}. Used to build "view published" links. |
Adapter / git-target
See Adapters for how these are used.
| Variable | Default | What it does |
|---|---|---|
BRITER_SITE_ADAPTER |
local-mdx |
Active adapter key: local-mdx (built-in reader) or git-target (push to external repo). |
BRITER_GIT_WORKTREE |
<appRoot> |
Path to the git worktree the worker stages/commits/pushes from. For git-target, this is the external site repo. |
BRITER_GIT_BRANCH |
main |
Branch the worker commits to and pushes. |
BRITER_GIT_REMOTE |
origin |
Git remote the worker pushes to. |
BRITER_GIT_AUTHOR_NAME |
Briter |
git author name on sync commits. |
BRITER_GIT_AUTHOR_EMAIL |
briter@example.invalid |
git author email on sync commits. |
BRITER_UPLOAD_ROUTE_BASE |
/uploads |
URL path prefix the built-in reader serves uploaded assets under. |
Worker tuning
See Worker & queue for the full model.
| Variable | Default | What it does |
|---|---|---|
BRITER_WORKER_DRY_RUN |
true |
When truthy, the worker performs every step except the real git push. Set to false (or 0/no) to publish for real. Booleans parse 0/false/no as false; everything else is true. |
BRITER_WORKER_POLL_INTERVAL_MS |
5000 |
Interval (ms) between worker polls of the queue. |
BRITER_WORKER_MAX_ATTEMPTS |
8 |
Maximum delivery attempts before a job is dead-lettered (status failed) instead of retried forever. |
BRITER_WORKER_LOCK_STALE_MS |
60000 |
Age (ms) after which storage/worker.lock is treated as abandoned and reclaimed, even if its PID still appears alive (guards against PID reuse). Read by the worker entrypoint. |
BRITER_WORKER_RUN_ONCE |
unset | When 1, the worker drains the queue once and exits instead of looping. Used by bun run worker:once. |
Storage budget
See Storage & uploads.
| Variable | Default | What it does |
|---|---|---|
BRITER_UPLOAD_STORAGE_LIMIT_BYTES |
524288000 (500 MB) |
Provisioned upload cap in bytes. Uploads are rejected at 100% and when a new file would cross 95% of this cap (UploadLimitError). |
Setup state
| Variable | Default | What it does |
|---|---|---|
BRITER_CONFIGURED |
unset | Read as a fallback configured flag ("1" means configured). In normal operation the authoritative flag is configured inside config.json; this env var is only a resolution input. |
Intentionally omitted from this table: none. Every key in
resolveBriterEnv()(lib/config/env.ts) and every env var the worker entrypoint reads is listed above. The single derived fieldappRootis the resolved value ofBRITER_APP_ROOT, not a separate variable.
State files
Briter keeps exactly two structured-state files. Everything else (posts, images, sections) is plain content files. There is no database — see Architecture.
storage/config.json (safe to read)
Written by the setup wizard and the settings API via saveRuntimeConfig(). It holds the same keys as the runtime config (site identity, adapter selection and git settings, worker tuning, upload cap, and the configured flag). It contains no secrets and is readable (mode 0644 is fine).
storage/secrets.json (0600, never logged)
Written by saveSecrets() (lib/config/secrets.ts), which chmods the file to 0600 on POSIX systems after every write. It holds only:
| Key | Contents |
|---|---|
adminPasswordHash |
bcrypt hash (cost factor 12) of the admin password. |
sessionSecret |
Per-instance HMAC secret used to sign session cookies. |
totpSecret |
TOTP seed, present only if two-factor is enabled. |
The plaintext password and the TOTP seed are never written to config.json, environment variables, or logs. This separation is mandated by the Constitution ("Secrets isolation"). On non-POSIX hosts where chmod is a no-op (e.g. some Docker-on-Windows setups), make sure the volume holding secrets.json is not world-readable.
Both files live under storage/, which is gitignored. Never commit storage/.
File paths and Docker volumes
Paths are resolved by resolveBriterPaths() (lib/config/paths.ts) relative to BRITER_APP_ROOT (default /app in Docker):
<appRoot>/
├── content/
│ ├── posts/ # post .mdx/.md files (BRITER_CONTENT_ROOT)
│ └── sections/ # section definition JSON (BRITER_SECTIONS_ROOT)
└── storage/
├── config.json # runtime config (0644)
├── secrets.json # secrets (0600)
├── queue/ # git-sync job files (BRITER_QUEUE_ROOT)
├── worker.lock # single-flight worker lock (PID + mtime)
└── uploads/
└── posts/ # image variants + manifests (BRITER_UPLOAD_ROOT)
Two volumes hold all persistent state — mount both outside the image so updates never touch your data:
volumes:
- ./storage:/app/storage # config, secrets, queue, lock, uploads
- ./content:/app/content # posts and sections
When using the git-target adapter you also mount the external repo and SSH credentials — see Docker and Adapters.
Ports
Briter listens on a single HTTP port, PORT (default 3075). Put a TLS-terminating reverse proxy (nginx, Caddy) in front of it; only the proxy should be able to reach 3075. See the production checklist in Docker.
Headless / agent configuration
Every value the wizard collects can be pre-seeded through environment variables (for the runtime-config keys above) and the /api/setup endpoint (for the password and TOTP, which become secrets.json). This is what makes Briter agent-installable per the Constitution. See Setup Wizard for the wizard flow.