diff --git a/README.md b/README.md index 2b64e98..be660ab 100755 --- a/README.md +++ b/README.md @@ -118,7 +118,9 @@ mkdir -p /tmp/redefined-uploads `PUBLIC_URL` is required only alongside SMTP because its only job is building links in email; an environment that cannot send mail does not need it. `UPLOADS_DIR` has no such reprieve — its fallback of `/app/uploads` is correct inside the container and wrong everywhere else. -**Adding to the required list has reach beyond this repository.** A variable added to `ALWAYS_REQUIRED` must also be set in every environment that deploys, and there are two of those. `docker-compose.qa.yml` is checked automatically — `backend/tests/unit/composeEnvironment.test.ts` reads the validator's own list and fails if the compose file does not set something on it, which is what #107 existed to prevent from recurring. Production runs from a Portainer stack **outside this repository**, so nothing can check it: that one has to be updated by hand, before the deploy rather than during it. +**Adding to the required list has reach beyond this repository.** A variable added to `ALWAYS_REQUIRED` must also be set in every environment that deploys, and there are two of those. Both are checked automatically — `backend/tests/unit/composeEnvironment.test.ts` reads the validator's own list and fails if a compose file does not set something on it, which is what #107 existed to prevent from recurring. It runs over every deployment file in the repository and hands each one's entries to `validateEnv` itself, so a file is checked against exactly what the container checks at boot. + +Production was outside that net until #118. It ran from a stack that existed only in Portainer's web editor, which no test could read — and on 2026-08-23 it refused to boot because `UPLOADS_DIR` had no line in it, while being set in Portainer's stack variables. `docker-compose.prod.yml` is now in the repository and covered like QA's, which is also why it must be deployed as a **git repository stack** rather than pasted into the web editor: a pasted copy drifts from the checked one and the guard goes back to being decorative. Note also that setting a variable in Portainer's stack environment is not the same as giving it to the container. Stack variables are interpolated into the compose file as `${VAR}`; a service receives exactly what its own `environment:` block lists. A variable with no line there never arrives, however carefully it was set in the UI. diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index 4b9723c..f51f123 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -20,10 +20,30 @@ # Reference: refs/heads/main # Compose path: docker-compose.prod.yml # -# `pull_policy: build` matters. Without it the stack reuses whatever is already -# tagged redefined-designs:latest, which is how a redeploy can appear to succeed -# while still running old code. Leave any Portainer option that re-pulls images -# turned OFF — there is no registry to pull this image from. +# THIS STACK DOES NOT BUILD. It runs the image tagged redefined-designs:latest, +# which has to already exist on the NAS before the stack starts. A first deploy, +# or a NAS that has pruned images, fails with "image not found" rather than +# quietly building one — see #146. +# +# That is deliberate. QA builds from this repository and is reviewed; production +# then runs the image that was reviewed, promoted by hand: +# +# docker tag redefined-designs:qa redefined-designs:latest +# +# Building here instead would look simpler and would ship something else. The +# Dockerfile copies package.json without package-lock.json and installs with +# `npm install`, so two builds of the same commit can resolve different +# transitive dependencies. "Same git ref" is therefore not "same image", and the +# reviewed bytes are the only thing that is. +# +# The promotion is load-bearing, not a convenience. Forgetting it means a +# redeploy that reuses the previous redefined-designs:latest and appears to +# succeed while running old code — the same failure `pull_policy: build` guards +# against in QA, arriving by a different route. State which image is being +# promoted as part of the deploy. +# +# Leave any Portainer option that re-pulls images turned OFF — there is no +# registry to pull this image from. # # WHY MOST VALUES ARE HARDCODED HERE RATHER THAN INTERPOLATED # @@ -59,14 +79,9 @@ services: redefined-designs: - # Built from this repository by Portainer rather than pulled. The same - # Dockerfile QA uses, so the two images differ only in configuration. - build: - context: . - dockerfile: Dockerfile + # No `build:` — see the note at the top of this file. This tag is produced + # by promoting the image QA was reviewed against, not by rebuilding here. image: redefined-designs:latest - # Always build; never reuse the existing tag. - pull_policy: build container_name: redefined-designs-syn environment: - TZ=America/Chicago