Skip to content

fix(deploy): map POSTGRES_PASSWORD and APNS_* into the api service env#2522

Merged
ToddHebebrand merged 2 commits into
mainfrom
fix/env-template-gaps
Jul 15, 2026
Merged

fix(deploy): map POSTGRES_PASSWORD and APNS_* into the api service env#2522
ToddHebebrand merged 2 commits into
mainfrom
fix/env-template-gaps

Conversation

@ToddHebebrand

Copy link
Copy Markdown
Collaborator

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 .env alone 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:

Production requires DATABASE_URL_APP, BREEZE_APP_DB_PASSWORD, or POSTGRES_PASSWORD to configure the unprivileged request database role.

But deploy/docker-compose.prod.yml mapped only the first two. 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.

2. APNS_* — silent no-op

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() !== ''), 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

  • Both compose files render clean under docker compose config.
  • POSTGRES_PASSWORD now reaches the prod api service — it previously did not appear at all.
  • Every APNS_* renders as "" when unset, so push stays disabled by default and no boot failure is introduced.
  • check-supply-chain-hardening.sh passes.

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

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>
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jul 15, 2026

Copy link
Copy Markdown

Deploying breeze with  Cloudflare Pages  Cloudflare Pages

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

View logs

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
ToddHebebrand merged commit 2f78e4b into main Jul 15, 2026
41 checks passed
@ToddHebebrand
ToddHebebrand deleted the fix/env-template-gaps branch July 15, 2026 07:01
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant