The ${VAR:?error} syntax in docker-compose.yml broke Coolify-style
deploys: that platform parses the compose file eagerly and stores the
fallback error string as the env-var value when the var is unset, which
then propagates into DATABASE_URL and breaks the running app with
P1000 auth failures.
Switched to plain ${VAR} interpolation. Validation moves into
docker-entrypoint.sh, which checks DATABASE_URL, SESSION_SECRET,
ENCRYPTION_KEY, API_TOKEN_HMAC_KEY before connecting to the database
and exits with a deploy-platform-agnostic error listing the unset vars.
Issue #88's biggest blocker was that docs and README pointed at
ghcr.io/mbombeck/healthlog:latest, but no workflow ever pushed an
image — every self-hoster trying to follow the docs hit "manifest
unknown".
This adds .github/workflows/docker-publish.yml. The workflow:
- Triggers on push to main, on v* tags, on PRs (build only, no push),
and via workflow_dispatch
- Builds linux/amd64 + linux/arm64 multi-arch images via buildx
- Tags with `latest` (main only), semver (`1.2.0`, `1.2`), branch name,
PR number, and short SHA — using docker/metadata-action defaults
- Attaches SLSA build provenance and an SBOM to every image
- Caches build layers in GitHub Actions cache for fast iterations
- Uses concurrency group + cancel-in-progress so superseded runs stop
The bundled docker-compose.yml now references
`ghcr.io/mbombeck/healthlog:latest` as the primary `image:` and keeps
`build:` as a fallback so contributors can still
`docker compose up --build` against their working tree.
SECURITY.md gains a Supply Chain section explaining where images come
from, how to verify provenance and SBOMs, and how to pin a specific
version. README's Quick Start updated to reflect that no build is
needed for self-hosters.
The first run of this workflow on main will publish the image; until
then `docker compose up` will fall back to the `build:` block.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Issue #88 (kevinpapst): the bundled docker-compose.yml didn't actually
work. Three concrete blockers:
- expose:3000 → ports:"3000:3000" so the host can reach the app
- POSTGRES_PASSWORD was hard-coded to "healthlog" — now required via
env var, and DATABASE_URL inside the compose service interpolates
the same variable so the two cannot drift apart
- .env.example DATABASE_URL pointed at localhost:5432, which never
resolves inside the app container; replaced with db:5432 plus a
note explaining the local-dev variant
.env.example is also rewritten as proper documentation: each variable
explains what it does, why it matters, and how to generate it
(openssl rand -hex 32 for the three 32-byte secrets, openssl rand
-base64 24 for the Postgres password). Required vs optional are now
clearly grouped.
README Quick Start gives a realistic time estimate, generates the four
secrets in one block straight into .env, and points reverse-proxy
users at the docs site.
Pre-built GHCR images and a docs-vs-repo single-source-of-truth for
docker-compose are tracked separately in Phase 2 of the audit plan
(see docs/audit/2026-04-26-PLAN.md).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
HealthLog — personal health-tracking PWA with weight, blood pressure,
pulse, mood tracking, medication compliance, Withings integration,
doctor report PDF export, and Dracula-themed UI.