18 KiB
FamilySync — Configuration Reference
All runtime configuration is supplied via environment variables. There are no JSON or YAML config files beyond Docker Compose. Copy .env.example to .env at the repo root and fill in the values before starting any service.
Environment Variables
Database
| Variable | Required | Default | Description |
|---|---|---|---|
DB_HOST |
Yes | localhost |
MariaDB hostname. Use mariadb inside Docker Compose; use localhost (or 127.0.0.1) for host-side dev runs. |
DB_PORT |
No | 3306 |
MariaDB port. |
DB_USER |
No | familysync |
Database user. |
DB_PASSWORD |
Required | (none) | Database password. Also used by the mariadb service as MARIADB_PASSWORD. |
DB_NAME |
No | familysync |
Database name. |
DB_ROOT_PASSWORD |
Required | (none) | MariaDB root password. Used only by the mariadb Docker service (MARIADB_ROOT_PASSWORD). Not read by the API process. |
Five of these variables — DB_HOST, DB_USER, DB_PASSWORD, DB_NAME, DB_PORT — are read by drizzle.config.ts when running migrations (db:generate / db:migrate) and by the API process to build its connection pool. DB_ROOT_PASSWORD is not read by either; it is consumed only by the mariadb Docker service.
Important: Do not use drizzle-kit push against this MariaDB. The mysql dialect mis-reads MariaDB 11.x metadata and schedules false destructive operations. Always use db:generate + db:migrate.
OIDC / Authelia Authentication
| Variable | Required | Default | Description |
|---|---|---|---|
OIDC_AUTH_SECRET |
Required | placeholder_change_me (Docker default) |
32+ character random string used to sign the oidc-auth session JWT cookie. Generate with openssl rand -base64 32. |
OIDC_ISSUER |
Required | (none) | Authelia base URL, e.g. https://auth.DOMAIN. The middleware fetches /.well-known/openid-configuration from this URL. |
OIDC_CLIENT_ID |
No | familysync |
Registered client ID in Authelia. |
OIDC_CLIENT_SECRET |
Required | (none) | Plaintext client secret matching the pbkdf2 hash stored in Authelia's configuration.yml. Do not use the hash here. |
OIDC_REDIRECT_URI |
Required | (none) | Full callback URL registered in Authelia, e.g. https://familysync.DOMAIN/callback. |
OIDC_AUTH_EXTERNAL_URL |
Required | (none) | Public-facing base URL of the app, e.g. https://familysync.DOMAIN. Mandatory behind the Pangolin/Newt tunnel. Without it, @hono/oidc-auth builds the redirect URI from the internal container hostname, which will not match the registered URI and breaks the OIDC flow. |
OIDC_SCOPES |
No | openid profile email offline_access |
Space-separated list of OIDC scopes to request. offline_access is required for refresh-token session persistence. Authelia rejects unknown scopes, so do not add scopes that are not configured on the Authelia client. |
OIDC_AUTH_EXPIRES |
No | 86400 |
Session cookie Max-Age in seconds (default 1 day). Governs the persistent session cookie set by persistSessionCookie middleware. |
OIDC_COOKIE_NAME |
No | oidc-auth |
Name of the session cookie. Override only if you need to run multiple instances under the same domain. |
OIDC_COOKIE_PATH |
No | / |
Cookie Path attribute. |
OIDC_COOKIE_DOMAIN |
No | (not set) | Cookie Domain attribute. Set this when the API and PWA are served from different subdomains under the same apex domain. Must match the Authelia and app domains (same-parent-domain requirement). |
Locked Authelia client parameters (these are fixed by the project; do not change):
response_types: [code]grant_types: [authorization_code, refresh_token]require_pkce: true,pkce_challenge_method: S256token_endpoint_auth_method: client_secret_basic
Broker Encryption
| Variable | Required | Default | Description |
|---|---|---|---|
APP_PASSWORD_ENCRYPTION_KEY |
Required | (none) | 64-character hex string (32 bytes) used as the AES-256-GCM key for encrypting Fastmail app passwords at rest. Generate with: node -e "console.log(require('crypto').randomBytes(32).toString('hex'))". Startup throws if this is missing or the wrong length. |
Web Push (VAPID)
| Variable | Required | Default | Description |
|---|---|---|---|
VAPID_PUBLIC_KEY |
No* | (none) | URL-safe base64 VAPID public key. Served to the PWA via GET /api/push/vapid-public-key. Non-secret. |
VAPID_PRIVATE_KEY |
No* | (none) | URL-safe base64 VAPID private key. Used server-side to sign push requests. Never expose to clients. |
VAPID_SUBJECT |
No* | (none) | Contact URI identifying the operator, e.g. mailto:admin@example.com or https://familysync.DOMAIN. Must be a mailto: or https: URL. |
*All three VAPID variables are optional in the sense that the server starts without them, but push notifications will not function. A startup warning is logged if any are missing. Generate a key pair with:
npx web-push generate-vapid-keys --json
Runtime Mode
| Variable | Required | Default | Description |
|---|---|---|---|
NODE_ENV |
No | (not set) | Set to production in the production Docker Compose. When production, the dev-auth bypass is unconditionally disabled regardless of DEV_AUTH_BYPASS. |
DEV_AUTH_BYPASS |
No | (not set) | Set to true to bypass OIDC authentication for local development without a live Authelia instance. Only active when NODE_ENV !== 'production'. The production docker-compose.yml must never include this variable. |
Defaults Summary
Variables with non-empty defaults do not cause startup failure if absent, but should be reviewed for production:
| Variable | Default | Source |
|---|---|---|
DB_HOST |
localhost |
apps/api/src/db/client.ts |
DB_PORT |
3306 |
apps/api/src/db/client.ts |
DB_USER |
familysync |
apps/api/src/db/client.ts |
DB_NAME |
familysync |
apps/api/src/db/client.ts |
OIDC_CLIENT_ID |
familysync |
docker-compose.yml |
OIDC_SCOPES |
openid profile email offline_access |
docker-compose.yml |
OIDC_AUTH_EXPIRES |
86400 |
apps/api/src/auth/persistSessionCookie.ts |
OIDC_COOKIE_NAME |
oidc-auth |
apps/api/src/auth/persistSessionCookie.ts |
OIDC_COOKIE_PATH |
/ |
apps/api/src/auth/persistSessionCookie.ts |
Per-Environment Configuration
Production (Docker Compose)
docker-compose.yml sets NODE_ENV=production and injects all secrets via ${VAR} interpolation from a .env file on the Docker host. No .env file is committed to the repository.
Minimum production .env:
# Database
DB_PASSWORD=<strong-password>
DB_ROOT_PASSWORD=<strong-root-password>
# OIDC
OIDC_AUTH_SECRET=<openssl rand -base64 32>
OIDC_ISSUER=https://auth.DOMAIN
OIDC_CLIENT_SECRET=<plaintext secret>
OIDC_REDIRECT_URI=https://familysync.DOMAIN/callback
OIDC_AUTH_EXTERNAL_URL=https://familysync.DOMAIN
# Broker encryption
APP_PASSWORD_ENCRYPTION_KEY=<64-hex-chars>
# VAPID push (optional but recommended)
VAPID_PUBLIC_KEY=<base64>
VAPID_PRIVATE_KEY=<base64>
VAPID_SUBJECT=mailto:admin@example.com
Local Development (host-side)
The dev Docker Compose override (docker-compose.dev.yml) exposes MariaDB on localhost:3306 and Redis on localhost:6379. To run the API and PWA directly on the host:
# Build the API first (dev script runs compiled output)
pnpm --filter @familysync/api build
# Source .env, then override DB_HOST and activate the bypass
set -a; source .env; set +a && DEV_AUTH_BYPASS=true DB_HOST=localhost pnpm --filter @familysync/api dev
# PWA (separate terminal)
pnpm --filter @familysync/pwa dev
The DB_HOST override is necessary because .env sets DB_HOST=mariadb (the Docker network hostname), which does not resolve on the host. The set -a; source .env; set +a idiom exports all variables; the DB_HOST=localhost prefix on the same line overrides that one variable for the child process.
DEV_AUTH_BYPASS=true is a local-only option. The API hard-checks NODE_ENV === 'production' before reading DEV_AUTH_BYPASS — the bypass has zero effect in a production container even if the variable is present.
Test
Integration tests targeting the real database require the dev MariaDB running with the host port exposed and the following overrides:
DB_HOST=127.0.0.1 DB_PORT=3306 DB_USER=familysync DB_NAME=familysync DB_PASSWORD=<value>
See docs/deployment.md for the full drizzle-kit migrate command used to prepare the test database.
Config File Reference
Application Config Files
There are no application-level JSON/YAML config files. The two config files that read environment variables at dev/build time are:
| File | Purpose |
|---|---|
apps/api/drizzle.config.ts |
Drizzle Kit migration config — reads DB_* variables |
apps/pwa/vite.config.ts |
Vite build config — no env var reads; proxy rules for dev server |
The PWA Vite dev server proxies /health, /api, and /callback to http://localhost:3000 so the frontend and API can be developed without CORS configuration.
Lint and Format Config Files
These files live at the repo root and apply to both apps/api and apps/pwa.
eslint.config.js
ESLint 9 flat config (ESM). Pinned to ESLint 9.39.4 — do not upgrade to ESLint 10 until eslint-plugin-react resolves the getFilename is not a function incompatibility.
Key layers (in order):
- Global ignores —
**/dist/**,**/node_modules/**,apps/api/src/db/migrations/**,pnpm-lock.yaml. - Base TS/TSX (
apps/**/*.{ts,tsx}) —js.configs.recommended+tseslint.configs.recommendedTypeCheckedwithprojectService: true(auto-discovers alltsconfig.jsonfiles).@typescript-eslint/no-unused-varsallows_-prefixed names. - React + Hooks (
apps/pwa/**/*.{ts,tsx}only) —eslint-plugin-reactflat recommended +eslint-plugin-react-hooksflat recommended. React Compiler rules (immutability, purity, refs, etc.) are disabled — this codebase does not use the React Compiler. disableTypeCheckedoverride — applied to tool config files and test/e2e directories that are outside anytsconfigproject (apps/api/drizzle.config.ts,apps/api/vitest.config.ts,apps/pwa/vite.config.ts,apps/pwa/vitest.config.ts,apps/pwa/playwright.config.ts,apps/api/tests/**/*.ts,apps/pwa/e2e/**/*.ts,eslint.config.js). Type-aware rules are disabled for these files; non-type-aware rules still apply.eslint-config-prettier(last) — disables all ESLint formatting rules that conflict with Prettier.
Run lint: pnpm lint (delegates to pnpm -r --if-present lint across all workspaces).
.prettierrc
{
"semi": true,
"singleQuote": true,
"tabWidth": 2,
"trailingComma": "all",
"printWidth": 100
}
Run formatter: pnpm format (write) or pnpm format:check (CI check, no writes).
.prettierignore
Excludes dist/, node_modules/, .pnpm-store/, pnpm-lock.yaml, apps/api/src/db/migrations/, *.html, and .planning/ from formatting. The .pnpm-store/ exclusion covers CI runners that have no persistent global pnpm store and land the content-addressable store inside the workspace.
CI Secrets
The Gitea Actions workflows in .gitea/workflows/ require one repository secret.
| Secret | Scope | Description |
|---|---|---|
REGISTRY_PAT |
publish.yml only |
A Gitea user PAT with write:package scope. Used to authenticate docker login against the Gitea container registry before pushing the API image. Must be named REGISTRY_PAT — Gitea reserves the GITEA_ prefix for built-in variables, so any GITEA_-prefixed secret name is rejected. GITEA_TOKEN and GITHUB_TOKEN do not have package-push permissions. |
CI Database Credentials
The ci.yml api and harness jobs spin up a throwaway MariaDB service container with hardcoded credentials (familysync / testpass). These are ephemeral — scoped to a single job container — and are not production secrets. Do not reuse them outside CI.