diff --git a/CHANGELOG.md b/CHANGELOG.md index cee6c5e2b..9fb1dca82 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,30 @@ # Changelog +## [1.3.1] — 2026-04-27 + +### Fixed + +- **Compose env-var validation no longer breaks Coolify-style deploys.** + `docker-compose.yml` previously used `${VAR:?required}` shell-parameter + syntax for the four secrets and `POSTGRES_PASSWORD`. Some hosting + platforms (Coolify in particular) parse compose files eagerly and + store the _fallback error string_ (`"POSTGRES_PASSWORD is required"`) + as the literal env-var value when `POSTGRES_PASSWORD` was unset, + which then collided with `DATABASE_URL` and broke the running app + with `P1000: Authentication failed`. Compose now uses plain `${VAR}` + interpolation; validation moved into `docker-entrypoint.sh`, which + fails fast with a clear stderr message listing the unset variables. + +### Notes + +If you upgraded an existing Compose stack from 1.2.x → 1.3.0 and hit +the `POSTGRES_PASSWORD is required` literal-as-value bug, set +`POSTGRES_PASSWORD` in your environment to whatever your existing +Postgres data volume was originally initialised with (likely +`healthlog` if you started from a pre-1.2.1 release), then redeploy. +Postgres only honours `POSTGRES_PASSWORD` on first volume init — the +existing user keeps the original password regardless of env changes. + ## [1.3.0] — 2026-04-27 ### Added — Body composition + targeted hardening diff --git a/docker-compose.yml b/docker-compose.yml index f5405eae6..243e95ccf 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -14,11 +14,16 @@ services: ports: - "3000:3000" environment: - DATABASE_URL: "postgresql://healthlog:${POSTGRES_PASSWORD:?POSTGRES_PASSWORD is required}@db:5432/healthlog?schema=public" + # Plain ${VAR} interpolation only — no ${VAR:?error} validation + # syntax. Some hosting platforms (e.g. Coolify) parse compose files + # eagerly and store the *fallback error string* as the literal value + # when the env var is unset, which then breaks the deployment. + # Validation is enforced by docker-entrypoint.sh before the app starts. + DATABASE_URL: "postgresql://healthlog:${POSTGRES_PASSWORD}@db:5432/healthlog?schema=public" NODE_ENV: production - SESSION_SECRET: "${SESSION_SECRET:?SESSION_SECRET is required}" - ENCRYPTION_KEY: "${ENCRYPTION_KEY:?ENCRYPTION_KEY is required}" - API_TOKEN_HMAC_KEY: "${API_TOKEN_HMAC_KEY:?API_TOKEN_HMAC_KEY is required}" + SESSION_SECRET: "${SESSION_SECRET}" + ENCRYPTION_KEY: "${ENCRYPTION_KEY}" + API_TOKEN_HMAC_KEY: "${API_TOKEN_HMAC_KEY}" NEXT_PUBLIC_APP_URL: "${NEXT_PUBLIC_APP_URL:-http://localhost:3000}" APP_URL: "${APP_URL:-http://localhost:3000}" depends_on: @@ -31,7 +36,7 @@ services: container_name: healthlog-db environment: POSTGRES_USER: healthlog - POSTGRES_PASSWORD: "${POSTGRES_PASSWORD:?POSTGRES_PASSWORD is required}" + POSTGRES_PASSWORD: "${POSTGRES_PASSWORD}" POSTGRES_DB: healthlog volumes: - pgdata:/var/lib/postgresql/data diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh index 22ef0bc5d..748318f21 100644 --- a/docker-entrypoint.sh +++ b/docker-entrypoint.sh @@ -1,6 +1,26 @@ #!/bin/sh set -e +# ── Validate required env vars ─────────────────────────────── +# We do this in the entrypoint instead of in docker-compose.yml's +# environment block (which used to use the ${VAR:?error} syntax) +# because some hosting platforms parse compose files eagerly and +# end up storing the fallback error string as the literal value. +# Validating here guarantees a clear, deploy-platform-agnostic +# failure message before we touch the database. +missing="" +for var in DATABASE_URL SESSION_SECRET ENCRYPTION_KEY API_TOKEN_HMAC_KEY; do + eval "value=\$$var" + if [ -z "$value" ]; then + missing="$missing $var" + fi +done +if [ -n "$missing" ]; then + echo "HealthLog: ERROR — required env vars not set:$missing" >&2 + echo "HealthLog: see .env.example for the full list and how to generate each value." >&2 + exit 1 +fi + # ── Wait for PostgreSQL ────────────────────────────────────── MAX_RETRIES=30 RETRY=0 @@ -15,7 +35,7 @@ until node -e " " 2>/dev/null; do RETRY=$((RETRY + 1)) if [ "$RETRY" -ge "$MAX_RETRIES" ]; then - echo "HealthLog: ERROR — Database not reachable after ${MAX_RETRIES}s, aborting." + echo "HealthLog: ERROR — Database not reachable after ${MAX_RETRIES}s, aborting." >&2 exit 1 fi sleep 1 diff --git a/package.json b/package.json index 5a79166db..5db739a38 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "healthlog", - "version": "1.3.0", + "version": "1.3.1", "private": true, "packageManager": "pnpm@10.31.0", "scripts": {