Both found by checking before running, not by running.
1. release.sh would have shipped a site whose contact form cannot be submitted.
.dockerignore excludes .env from the build context, and the script passed
--build-arg VITE_RECAPTCHA_SITE_KEY=${VITE_RECAPTCHA_SITE_KEY:-} without ever
loading .env — and the variable is unset in every shell. The `:-` made empty
silently acceptable. An empty key makes RecaptchaPlaceholder render "Security
verification is not configured." and produce no token, and the server has
RECAPTCHA_ENABLED=true, so every submission is rejected. Lead capture stops.
Now: loads VITE_* from .env, refuses an empty key outright, and greps the
BUILT image's bundle for it before pushing — ask the artifact, do not trust
the wiring, the same move already used for the version label.
2. deploy.sh's default would have rolled production back two months. The newest
published NUMBERED tag is v0.8.3, built 2026-05-28; the running :dev image
was built 2026-08-01. v0.8.3 has no privacy policy and no prerendered routes
at all. Now: the target's build date is read from the registry without
pulling it, compared against what is running, and refused if older unless
--rollback is passed.
Also in deploy.sh:
- --fix-cors removes the trailing slash from CORS_ORIGIN (#212), and
--watchtower-off adds com.centurylinklabs.watchtower.enable=false. Both ride
in the same PUT so production restarts once, not three times.
- The stack file is now read to a temp file instead of $( ), which was stripping
its trailing newline — a change beyond the lines the script claims to touch.
- The env-line count is asserted before sending. Verified with `docker compose
config` why that matters: without the Env array the ${VAR:-false} defaults
resolve reCAPTCHA and Zoho WebToLead to false, so the container would come
back healthy and quietly stop capturing leads.
- Post-deploy it checks health, both origins, AND that the live bundle carries
the site key the stack declares — the failure no health check can see.
- Any of those failing triggers an automatic rollback to the original stack
bytes, once. If the rollback also fails it stops and says so rather than
retrying, because a script retrying an outage is how a short one becomes long.
The resulting stack file was validated with `docker compose config`.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>