docs(self-hosting): reconcile DATABASE_URL/POSTGRES_PASSWORD guidance with the compose contract

The bundled compose constructs DATABASE_URL inside the app service from
POSTGRES_PASSWORD, so the getting-started 'edit CHANGE-ME' step was a
no-op and .env.example's literal POSTGRES_PASSWORD collided with the
quick-start's appended one. Comment both out in .env.example so the
append pattern yields a single key, and rewrite step 2 to state compose
derives the URL. Add a first-user note to close public registration on
an internet-exposed host and to save the password (no-SMTP CLI reset).
Refresh the stale onboarding step-2 source comment.
This commit is contained in:
Marc-André Bombeck committed 2026-07-08 23:34:50 +02:00
1 parent 6ad2dc858a
commit ced4e45272
3 files changed
+50 -22

No files matched your search

+17 -8
View File
@@ -18,15 +18,24 @@
# -----------------------------------------------------------------------------
# Database
# -----------------------------------------------------------------------------
# When using docker-compose.yml: the app container reaches Postgres via the
# service name "db", so the hostname is "db" — NOT "localhost".
# When developing without Docker (`pnpm dev` against a host-installed Postgres):
# change the hostname to "localhost" and adjust the credentials.
DATABASE_URL="postgresql://healthlog:CHANGE-ME@db:5432/healthlog?schema=public"
# Bundled docker-compose.yml path (the common case): you do NOT set DATABASE_URL
# here. Compose CONSTRUCTS it inside the `app` service from POSTGRES_PASSWORD
# (see docker-compose.yml — the `environment:` block wins over any value in this
# file), so a DATABASE_URL line here is ignored for that stack. Set only
# POSTGRES_PASSWORD below and compose wires the connection string for you.
#
# Bare-Node / custom-compose path (`pnpm dev` against a host Postgres, or your
# own compose that does NOT derive DATABASE_URL): uncomment DATABASE_URL and set
# it explicitly. Hostname is "db" on the bundled compose network, or
# "localhost" for a host-installed Postgres.
# DATABASE_URL="postgresql://healthlog:YOUR_PASSWORD@localhost:5432/healthlog?schema=public"
# Postgres password — used both by the db service AND by DATABASE_URL above.
# Pick a strong random password, e.g. `openssl rand -hex 32`.
POSTGRES_PASSWORD="CHANGE-ME"
# Postgres password for the bundled `db` service — and the value compose splices
# into DATABASE_URL. The quick-start appends a generated one:
# echo "POSTGRES_PASSWORD=$(openssl rand -hex 32)" >> .env
# Left commented here so that append does not create a duplicate key. Uncomment
# and set it directly instead if you prefer to edit the file by hand.
# POSTGRES_PASSWORD="generate-with-openssl-rand-hex-32"
# -----------------------------------------------------------------------------
+24 -5
View File
@@ -53,11 +53,14 @@ echo "API_TOKEN_HMAC_KEY=$(openssl rand -hex 32)" >> .env
| `ENCRYPTION_KEY` | AES-256-GCM key for at-rest secrets (Withings tokens, AI provider keys, VAPID secrets) | Coordinated; see `docs/ops/encryption-key-rotation.md` |
| `API_TOKEN_HMAC_KEY` | HMAC-SHA256 key for hashing Bearer API tokens before storage | Invalidates every issued `hlk_*` token |
Then open `.env` and confirm `DATABASE_URL` includes the password you
just generated. The default template (`postgresql://healthlog:CHANGE-ME@db:5432/healthlog`)
references the literal string `CHANGE-ME`; replace it with the value
you put in `POSTGRES_PASSWORD`, or rewrite the URL to substitute
explicitly.
You do **not** edit `DATABASE_URL` on the bundled `docker-compose.yml`
stack: compose constructs the connection string inside the `app`
service from `${POSTGRES_PASSWORD}` (its `environment:` block overrides
any `DATABASE_URL` in `.env`), so the `openssl` line above is the only
password step. `DATABASE_URL` only matters if you run a **bare Node**
process (`pnpm dev` against a host Postgres) or a **custom compose** that
does not derive it — in that case uncomment and set `DATABASE_URL`
explicitly in `.env` (see the commented example there).
## 3. Bring the stack up
@@ -92,6 +95,22 @@ After the first user lands, you can:
- Open `/measurements` and add a weight or blood pressure to confirm
the data path is wired end-to-end.
> **Close public registration if you expose the instance to the
> internet.** Registration stays **open** after the first admin so you
> can invite the rest of your household — but on a publicly reachable
> host that means strangers can still sign up. Once everyone who needs
> an account has one, turn it off in **`/admin` → registration toggle**
> (or keep it open and hand out invite links instead). The admin
> dashboard shows a reminder while registration is open and you are the
> only user.
> **No email is configured out of the box** — a deliberate first-boot
> simplicity win, but it also means password reset has no self-service
> path until you set SMTP (see `docs/self-hosting/notifications.md`). If
> you are the sole admin, **save your password in a manager now**: a
> locked-out solo admin with no SMTP recovers only via the CLI script in
> `docs/ops/password-reset.md`.
## 5. Point the instance at a public hostname
Local-only is fine for a single-user trial. For anything more — and
+9 -9
View File
@@ -13,18 +13,16 @@ import { getServerTranslator } from "@/lib/i18n/server-translator";
import { resolveServerLocale } from "@/lib/i18n/server-locale";
/**
* v1.4.25 W14b — onboarding wizard step page (foundation scaffold).
* Onboarding wizard step page.
*
* Routes:
* /onboarding/0 welcome → carousel / value-prop intro
* /onboarding/1 goals → "what do you want to track?"
* /onboarding/2 source → Withings / Apple Health / manual cards
* /onboarding/2 source → device/import source cards (see
* source-card-grid.tsx for the shipped set)
* /onboarding/3 baseline → first measurement or sync confirmation
* /onboarding/4 done → success screen + return to dashboard
*
* This page ships the scaffold only — the step body is a placeholder
* `<div>` that the W14b-Content agent replaces with real step UI.
*
* Step gating:
* - Unauthenticated → `/auth/login` (the proxy also enforces this,
* but the server-side check keeps the contract explicit).
@@ -139,10 +137,12 @@ export default async function OnboardingStepPage({ params }: PageProps) {
);
}
// Source (step 2) — four-card grid. Manual is the implicit default;
// Withings opens OAuth in a new tab; Apple Health is rendered as a
// "coming with v1.5" announce card; Garmin is omitted per the
// W14b-Content brief (not on the roadmap yet).
// Source (step 2) — card grid. Manual is the implicit default; the
// connectable sources (Apple Health, WHOOP, Oura, Polar, Nightscout,
// Fitbit/Pixel via Google Health) live in `source-card-grid.tsx`'s
// SOURCE_CARDS / MORE_SOURCES lists — refer to that component for the
// current shipped set rather than duplicating it here. The selection
// is non-binding: nothing here auto-configures a sync.
if (requested === 2) {
return (
<OnboardingShell step={2} userLocale={user.locale ?? null}>