fix(deploy): map POSTGRES_PASSWORD and APNS_* into the api service env#2522
Merged
Conversation
Two env-template gaps found while auditing the upgrade path for the next release. Both are the same trap: compose only interpolates variables listed in a service's `environment:` block, so a value in .env alone is never seen by the API. POSTGRES_PASSWORD (the important one). #2368 made production refuse to boot without DATABASE_URL_APP, BREEZE_APP_DB_PASSWORD, or POSTGRES_PASSWORD, and the validator's own error message names all three. But deploy/docker-compose.prod.yml mapped only the first two, so an operator who followed that advice and set POSTGRES_PASSWORD in .env still failed to boot — with an error telling them to do the thing they had already done. APNS_* were absent from every template and compose block, so a configured push relay silently never delivered. Mapped with empty defaults, which the validator treats as unset (`v.trim() !== ''`), keeping "unset = push disabled" intact and avoiding the partial-set boot failure. Documented the all-or-none rule in .env.example. Verified: both compose files render clean with `docker compose config`; POSTGRES_PASSWORD now reaches the prod api service (it previously did not) and every APNS_* renders as "" when unset. Supply-chain hardening guard passes. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Deploying breeze with
|
| Latest commit: |
a769d1b
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://1f8ca3fe.breeze-9te.pages.dev |
| Branch Preview URL: | https://fix-env-template-gaps.breeze-9te.pages.dev |
Mapping APNS_* into the compose api service (previous commit) broke the smoke
test: compose renders `${APNS_ENVIRONMENT:-}` as "" and always injects the key,
but the field is `z.enum([...]).optional()`, which accepts undefined and not "".
Every stock deploy would have failed to boot with:
APNS_ENVIRONMENT: Invalid option: expected one of "sandbox"|"production"
The four credential fields are `z.string().optional()`, so "" passes them and
the all-or-none block already discounts it via trim(). Only the enum needed to
agree. Preprocess "" to undefined so the schema matches the semantics the rest
of the block already uses.
Giving the var a real compose default instead would be worse: any non-empty
value opts into APNS and makes the four credentials required, so `:-production`
would fail boot for everyone who doesn't use push.
Tests: an all-empty set reads as unset; an empty ENVIRONMENT with credentials
set reads as unset; an invalid value still refuses boot; and a partial set still
refuses boot even when the unset keys are present as "". The pre-existing
"only APNS_ENVIRONMENT is set" case still fails closed — the preprocess
neutralizes empty strings, not real opt-in values.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
ToddHebebrand
added a commit
that referenced
this pull request
Jul 15, 2026
…2526) ## Problem v0.95.0 makes `DATABASE_URL_APP` the **preferred** way to point the request pool at the unprivileged `breeze_app` role, and the **only** option for multi-host/HA URLs — `requestDatabaseConfig.ts` throws rather than derive one: > `Cannot derive the request database URL from DATABASE_URL. Set an explicit DATABASE_URL_APP for postgres.js multi-host/HA URLs.` The base `docker-compose.yml` never mapped it into the `api` service `environment:` block. Compose only interpolates vars listed there, so a self-hoster setting `DATABASE_URL_APP` in `.env` had it **silently dropped**. This is the same failure mode #2522 fixed for `deploy/docker-compose.prod.yml` — which already carries a comment spelling out why all three must be mapped, while the base file it mirrors omitted one of them. ## Fix Map `DATABASE_URL_APP: ${DATABASE_URL_APP:-}` alongside the existing `POSTGRES_PASSWORD` / `BREEZE_APP_DB_PASSWORD`. ## Verification `docker compose config` against an env file, base compose: | | `DATABASE_URL_APP` occurrences in rendered `api` | |---|---| | main today | **0** — silently dropped | | with this fix | **1** — value intact, incl. comma-separated HA hosts | | fix + var unset | renders `""`, config still valid (rc=0) | Confirmed via `awk` that the var lands in the `api` service specifically. ## Why now Blocks cutting v0.95.0 — this is the release that introduces the variable, so shipping it inert on the default compose file would strand base-compose HA users on the exact footgun the release is meant to close. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Todd Hebebrand <todd@lanternops.io> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Two env-template gaps found while auditing the upgrade path for the next release. Both are the same trap: compose only interpolates variables listed in a service's
environment:block, so a value in.envalone is never seen by the API.1.
POSTGRES_PASSWORD— the important one#2368 made production refuse to boot without one of
DATABASE_URL_APP/BREEZE_APP_DB_PASSWORD/POSTGRES_PASSWORD, and the validator's error message names all three:But
deploy/docker-compose.prod.ymlmapped only the first two. An operator who followed that advice and setPOSTGRES_PASSWORDin.envstill failed to boot — with an error telling them to do the thing they had already done.2.
APNS_*— silent no-opAbsent from every template and compose block, so a configured push relay silently never delivered. Mapped with empty defaults, which the validator treats as unset (
v.trim() !== ''), so "unset = push disabled" is preserved and the partial-set boot failure isn't triggered. The all-or-none rule is now documented in.env.example.Verification
docker compose config.POSTGRES_PASSWORDnow reaches the prod api service — it previously did not appear at all.APNS_*renders as""when unset, so push stays disabled by default and no boot failure is introduced.check-supply-chain-hardening.shpasses.Why now
This lands ahead of the release because #2368's new boot requirement is the headline self-hoster breaking change, and this gap turns a clear error message into a misleading one at exactly the moment operators hit it.
🤖 Generated with Claude Code