mirror of
https://github.com/temetro/temetro.git
synced 2026-07-26 11:58:14 +00:00
backend: zero-setup Docker — auto-generate & persist secrets
`docker compose up` now works with no .env editing. A new entrypoint (docker-entrypoint.sh) generates any missing secret (BETTER_AUTH_SECRET, AI_CREDENTIALS_KEY) on first start and persists it to the temetro_secrets volume, so values stay stable across restarts (rotating them would log users out / invalidate stored AI keys). Real values passed via .env/compose still win. This also fixes the boot failure where the compose backend ran with NODE_ENV=production but never passed AI_CREDENTIALS_KEY through, tripping the production guard. The secret is now an optional pass-through and BETTER_AUTH_SECRET no longer hard-fails when unset. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+5
-1
@@ -18,6 +18,10 @@ COPY package*.json ./
|
|||||||
RUN npm ci --omit=dev
|
RUN npm ci --omit=dev
|
||||||
COPY --from=build /app/dist ./dist
|
COPY --from=build /app/dist ./dist
|
||||||
COPY --from=build /app/drizzle ./drizzle
|
COPY --from=build /app/drizzle ./drizzle
|
||||||
|
COPY docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh
|
||||||
|
RUN chmod +x /usr/local/bin/docker-entrypoint.sh
|
||||||
EXPOSE 4000
|
EXPOSE 4000
|
||||||
# Apply migrations, then start the API.
|
# The entrypoint auto-generates any missing secrets, then we apply migrations
|
||||||
|
# and start the API.
|
||||||
|
ENTRYPOINT ["/usr/local/bin/docker-entrypoint.sh"]
|
||||||
CMD ["sh", "-c", "node dist/migrate.js && node dist/index.js"]
|
CMD ["sh", "-c", "node dist/migrate.js && node dist/index.js"]
|
||||||
|
|||||||
+7
-4
@@ -9,12 +9,15 @@ shape exactly.
|
|||||||
## Quick start (Docker)
|
## Quick start (Docker)
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cp .env.example .env
|
docker compose up # db + backend + frontend — no setup needed
|
||||||
# set a strong secret:
|
|
||||||
# openssl rand -base64 32 -> paste into BETTER_AUTH_SECRET
|
|
||||||
docker compose up # db + backend + frontend
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
No `.env` or manual secret generation is required: on first start the backend
|
||||||
|
generates any missing secrets (`BETTER_AUTH_SECRET`, `AI_CREDENTIALS_KEY`) and
|
||||||
|
persists them to a Docker volume, so they stay stable across restarts. Create a
|
||||||
|
`.env` (from `.env.example`) only if you want to **override** something — your
|
||||||
|
own auth secret, SMTP, etc.
|
||||||
|
|
||||||
- Frontend → http://localhost:3000
|
- Frontend → http://localhost:3000
|
||||||
- Backend → http://localhost:4000 (health: `GET /health`, auth health: `GET /api/auth/ok`)
|
- Backend → http://localhost:4000 (health: `GET /health`, auth health: `GET /api/auth/ok`)
|
||||||
- Postgres → localhost:5432
|
- Postgres → localhost:5432
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
# Full-stack dev/run orchestration for temetro.
|
# Full-stack dev/run orchestration for temetro.
|
||||||
#
|
#
|
||||||
# cp .env.example .env # then set BETTER_AUTH_SECRET
|
# docker compose up # db + backend + frontend — that's it.
|
||||||
# docker compose up # db + backend + frontend
|
#
|
||||||
|
# No .env or secret setup is required: the backend generates any missing
|
||||||
|
# secrets on first start and persists them (see docker-entrypoint.sh). Create a
|
||||||
|
# .env only if you want to override something (SMTP, a real auth secret, etc.).
|
||||||
#
|
#
|
||||||
# Frontend -> http://localhost:3000
|
# Frontend -> http://localhost:3000
|
||||||
# Backend -> http://localhost:4000
|
# Backend -> http://localhost:4000
|
||||||
@@ -41,7 +44,10 @@ services:
|
|||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
environment:
|
environment:
|
||||||
DATABASE_URL: postgres://temetro:temetro@db:5432/temetro
|
DATABASE_URL: postgres://temetro:temetro@db:5432/temetro
|
||||||
BETTER_AUTH_SECRET: ${BETTER_AUTH_SECRET:?set BETTER_AUTH_SECRET in .env (openssl rand -base64 32)}
|
# Secrets are optional: if unset, the entrypoint generates and persists
|
||||||
|
# them (to the temetro_secrets volume). Pass real values here to override.
|
||||||
|
BETTER_AUTH_SECRET: ${BETTER_AUTH_SECRET:-}
|
||||||
|
AI_CREDENTIALS_KEY: ${AI_CREDENTIALS_KEY:-}
|
||||||
BETTER_AUTH_URL: http://localhost:4000
|
BETTER_AUTH_URL: http://localhost:4000
|
||||||
FRONTEND_URL: http://localhost:3000
|
FRONTEND_URL: http://localhost:3000
|
||||||
PORT: "4000"
|
PORT: "4000"
|
||||||
@@ -51,6 +57,9 @@ services:
|
|||||||
SMTP_USER: ${SMTP_USER:-}
|
SMTP_USER: ${SMTP_USER:-}
|
||||||
SMTP_PASS: ${SMTP_PASS:-}
|
SMTP_PASS: ${SMTP_PASS:-}
|
||||||
SMTP_FROM: ${SMTP_FROM:-temetro <no-reply@temetro.local>}
|
SMTP_FROM: ${SMTP_FROM:-temetro <no-reply@temetro.local>}
|
||||||
|
volumes:
|
||||||
|
# Persists auto-generated secrets so they stay stable across restarts.
|
||||||
|
- temetro_secrets:/var/lib/temetro
|
||||||
ports:
|
ports:
|
||||||
- "4000:4000"
|
- "4000:4000"
|
||||||
|
|
||||||
@@ -78,3 +87,4 @@ services:
|
|||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
temetro_pgdata:
|
temetro_pgdata:
|
||||||
|
temetro_secrets:
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# Auto-provision the secrets temetro needs, so `docker compose up` works with
|
||||||
|
# zero manual setup. Any secret not supplied via the environment is generated
|
||||||
|
# and persisted to a Docker volume, so it stays stable across restarts (which
|
||||||
|
# matters: rotating BETTER_AUTH_SECRET logs everyone out, and rotating
|
||||||
|
# AI_CREDENTIALS_KEY invalidates stored AI provider keys).
|
||||||
|
#
|
||||||
|
# Precedence for each secret: a real value you pass in (.env / compose) wins;
|
||||||
|
# otherwise a previously generated one is reused; otherwise a new one is made.
|
||||||
|
set -e
|
||||||
|
|
||||||
|
SECRETS_FILE="${SECRETS_FILE:-/var/lib/temetro/secrets.env}"
|
||||||
|
mkdir -p "$(dirname "$SECRETS_FILE")"
|
||||||
|
touch "$SECRETS_FILE"
|
||||||
|
|
||||||
|
# 32 random bytes, base64 — no openssl dependency (busybox has base64).
|
||||||
|
random_secret() {
|
||||||
|
head -c 32 /dev/urandom | base64 | tr -d '\n'
|
||||||
|
}
|
||||||
|
|
||||||
|
ensure_secret() {
|
||||||
|
name="$1"
|
||||||
|
current="$(printenv "$name" || true)"
|
||||||
|
case "$current" in
|
||||||
|
"" | dev-insecure-* | replace-me-*)
|
||||||
|
saved="$(grep "^${name}=" "$SECRETS_FILE" 2>/dev/null | tail -n1 | cut -d= -f2-)"
|
||||||
|
if [ -n "$saved" ]; then
|
||||||
|
export "$name=$saved"
|
||||||
|
else
|
||||||
|
val="$(random_secret)"
|
||||||
|
export "$name=$val"
|
||||||
|
printf '%s=%s\n' "$name" "$val" >>"$SECRETS_FILE"
|
||||||
|
echo "🔑 Generated $name (persisted; stable across restarts)"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
ensure_secret BETTER_AUTH_SECRET
|
||||||
|
ensure_secret AI_CREDENTIALS_KEY
|
||||||
|
|
||||||
|
exec "$@"
|
||||||
Reference in New Issue
Block a user