Index: .env.example
===================================================================
--- .env.example	(revision 45271b73a1802af5f10c136c911bbb0cc6569af2)
+++ .env.example	(revision 09ee2bd19e715e798f3d2b165d1d90f9c37eba7d)
@@ -1,12 +1,17 @@
 NODE_ENV=development
 PORT=3000
-SESSION_SECRET=change-me-to-a-strong-random-string-min-32-chars
+# Secret used to sign login-session cookies. Leave EMPTY to auto-generate a strong
+# one on first start (saved to storage/.session-secret, stays stable across
+# restarts/updates). Or set your own: openssl rand -hex 32
+SESSION_SECRET=
 DATABASE_PATH=./storage/database.sqlite
 MEDIA_PATH=./storage/media
 
-# Canonical public URL of this site (scheme + host, no path/slash). Used for
-# links in emails (password reset) instead of request headers — prevents
-# host-header spoofing. Not set? Then it falls back to the request host (dev).
-PUBLIC_BASE_URL=https://example.com
+# Canonical public URL of this site (scheme + host, no trailing slash), e.g.
+# https://yourdomain.com . Used to build correct links in emails (password reset)
+# and OAuth redirects instead of trusting request headers (anti-spoofing).
+# Optional: leave empty and it falls back to the request host (fine for local/dev);
+# set it for production so email/login links point at the right place.
+PUBLIC_BASE_URL=
 
 # ── Administrator ───────────────────────────────────────────────────
Index: README.md
===================================================================
--- README.md	(revision 45271b73a1802af5f10c136c911bbb0cc6569af2)
+++ README.md	(revision 09ee2bd19e715e798f3d2b165d1d90f9c37eba7d)
@@ -58,8 +58,9 @@
 git clone https://github.com/roboburr/klonkt.git
 cd klonkt
-cp .env.example .env          # then edit .env: set SESSION_SECRET + PUBLIC_BASE_URL
+cp .env.example .env          # works as-is; optionally set PUBLIC_BASE_URL to your domain
 docker compose up -d
 ```
 
+`SESSION_SECRET` is auto-generated on first start, so the defaults work as-is.
 Klonkt runs on port 3000 — put your own reverse proxy in front for HTTPS (see
 step 5 of Option C). Data (database + media) stays in the `klonkt-data` volume,
@@ -79,10 +80,12 @@
 ```
 
-**2. Create your config** — copy the example and edit it; at minimum set a long
-random `SESSION_SECRET` and your `PUBLIC_BASE_URL` (e.g. `https://yourdomain.com`):
+**2. Create your config.** `SESSION_SECRET` (the key that signs login cookies) is
+auto-generated on first start, so this works as-is. For production, set
+`PUBLIC_BASE_URL` to your site address (e.g. `https://yourdomain.com`) so email &
+login links are correct:
 
 ```bash
 cp .env.example .env
-nano .env
+nano .env          # optional: PUBLIC_BASE_URL, plus SMTP / Google if you want them
 ```
 
Index: docker-compose.yml
===================================================================
--- docker-compose.yml	(revision 45271b73a1802af5f10c136c911bbb0cc6569af2)
+++ docker-compose.yml	(revision 09ee2bd19e715e798f3d2b165d1d90f9c37eba7d)
@@ -1,10 +1,12 @@
-# Klonkt — zelf-host met Docker Compose.
+# Klonkt — self-host with Docker Compose.
 #
-#   1. cp .env.example .env   en vul SESSION_SECRET + PUBLIC_BASE_URL in
+#   1. cp .env.example .env     (works as-is: SESSION_SECRET is auto-generated;
+#                                optionally set PUBLIC_BASE_URL to your domain so
+#                                email/login links are correct)
 #   2. docker compose up -d
-#   3. open je site en maak via /auth/register je beheerdersaccount aan
+#   3. open your site and create your admin account at /auth/register
 #
-# Standaard luistert de app op poort 3000. Zet er een reverse-proxy (Caddy/
-# nginx) vóór voor HTTPS op je domein — zie README.
+# The app listens on port 3000. Put a reverse proxy (Caddy/nginx) in front for
+# HTTPS on your domain — see README.
 services:
   klonkt:
Index: src/server.js
===================================================================
--- src/server.js	(revision 45271b73a1802af5f10c136c911bbb0cc6569af2)
+++ src/server.js	(revision 09ee2bd19e715e798f3d2b165d1d90f9c37eba7d)
@@ -13,4 +13,5 @@
 import path from 'path';
 import fs from 'fs';
+import crypto from 'crypto';
 import { fileURLToPath } from 'url';
 import http from 'http';
@@ -63,11 +64,22 @@
 import changelogRoutes from './routes/changelog.js';
 
+// SESSION_SECRET: use the env var if set. Otherwise auto-generate a strong one
+// and persist it next to the database, so it stays stable across restarts and
+// updates. This lets Docker / bare-Node installs run with zero manual config.
 if (!process.env.SESSION_SECRET) {
-  console.error('❌ FATAL: SESSION_SECRET is required');
-  process.exit(1);
+  const dataDir = path.dirname(process.env.DATABASE_PATH || './storage/database.sqlite');
+  const secretFile = path.join(dataDir, '.session-secret');
+  try { process.env.SESSION_SECRET = fs.readFileSync(secretFile, 'utf8').trim(); } catch { /* not yet generated */ }
+  if (!process.env.SESSION_SECRET) {
+    fs.mkdirSync(dataDir, { recursive: true });
+    process.env.SESSION_SECRET = crypto.randomBytes(32).toString('hex');
+    fs.writeFileSync(secretFile, process.env.SESSION_SECRET, { mode: 0o600 });
+    console.log(`🔑 Generated a SESSION_SECRET (stored in ${secretFile})`);
+  }
 }
 
+// A SESSION_SECRET that was explicitly set in the env must still be strong in prod.
 if (process.env.NODE_ENV === 'production' && process.env.SESSION_SECRET.length < 32) {
-  console.error('❌ FATAL: SESSION_SECRET too weak for production');
+  console.error('❌ FATAL: SESSION_SECRET is too weak for production (set a longer, random one in .env)');
   process.exit(1);
 }
