The frontend healthcheck ran 'wget --spider', but the image is distroless: no
shell, no wget, no curl, and node itself is only reachable at an absolute path
(/nodejs/bin/node, off PATH). The probe therefore ENOENT'd on every run, the
container stayed permanently unhealthy, and Traefik -- which will not route to
an unhealthy container -- returned 404 to the customer even though the app was
serving 200 on :3000 the whole time.
Replaced with the Node-exec family using the absolute interpreter path.
Live-verified on the demo box.
Campaign 7 catalog sweep.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Nn3VgQk9iwEGgyx6QJ2NvE
SECRET_KEY ("Titkosítási kulcs") is a data-encrypting key — regenerating it on
restore would render stored data unreadable. data_key:true makes the controller
recover it from the guest's own app.yaml (live/PBS) and fail closed if it can't,
rather than ever regenerating. Pairs with controller v0.53.0.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Each app template now declares a healthcheck: section in .felhom.yml
with appropriate probe type (http, api, or tcp) and endpoint based on
the app's known health endpoints. The controller uses these to verify
services are actually responding, not just that containers are running.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Backend: wget missing from image, use python urllib instead.
Frontend: localhost resolves to IPv6, use 127.0.0.1.
Backend: add SECRET_KEY env var (v0.11.0 reads it directly).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
All 51 docker-compose.yml: replaced hardcoded subdomain.${DOMAIN}
with ${SUBDOMAIN}.${DOMAIN} in Traefik labels, app env vars, and
comments.
All 51 .felhom.yml: added SUBDOMAIN deploy field (type: subdomain)
with default matching existing subdomain metadata value.
Works with felhom-controller v0.27.0 which validates and stores the
user-chosen subdomain in app.yaml. Existing deployed apps get
SUBDOMAIN auto-injected via InjectMissingFields() on next sync.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>