feat(qa): add the QA environment stack definition (#25)

A disposable stack for reviewing merged-but-undeployed changes online,
running alongside production on the same NAS.

Every value that could collide with production is changed: container
names, host port (32751), volume paths, database name, and image tag.
The Postgres data directory is the critical one — sharing production's
would mean QA writing into production's database files.

QA deliberately differs from production in three ways. DEMO_MODE=true
with no PayPal credentials, so the full cart and checkout path is
exercisable with no route to live PayPal. No SMTP configuration, so a QA
run cannot email anyone; the mailer already degrades gracefully when
unset. And restart: "no", so a NAS reboot does not quietly bring QA back
up and leave it running.

The stack must be named redefined-designs-qa in Portainer: the stack name
becomes the compose project name, and reusing production's would make
compose reconcile the two against each other and remove the production
containers.

README covers the one-time NAS directory setup, the review workflow, and
how to reset QA data.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-17 11:17:52 -05:00
co-authored by Claude Opus 5
parent e40a3d5e1a
commit 47f00a3485
2 changed files with 144 additions and 1 deletions
+61 -1
View File
@@ -162,4 +162,64 @@ Gitea Actions runs two workflows on every push to `main` and on pull requests:
Production runs as a single Docker image (multi-stage build — the frontend is built to static files and served directly by the backend), deployed via Portainer behind Nginx Proxy Manager, with authentik forward-auth gating `/admin`. That infrastructure is homelab-specific and documented separately outside this repo.
The container applies pending migrations before starting the server, so deployed code can never be ahead of the database schema. A failed migration stops the container rather than letting it serve against a schema it doesn't match — check `docker logs` on the app container if it doesn't come up.
The container applies pending migrations before starting the server, so deployed code can never be ahead of the database schema. A failed migration stops the container rather than letting it serve against a schema it doesn't match — check `docker logs` on the app container if it doesn't come up.
## QA environment
`docker-compose.qa.yml` defines a disposable QA stack for reviewing merged-but-undeployed changes online. It runs alongside production on the same NAS with its own containers, database, volumes, and host port, and is started only when a review is needed.
It differs from production deliberately: `DEMO_MODE=true` with no PayPal credentials (so checkout is exercisable but cannot reach live PayPal), no SMTP configuration (so it cannot email anyone), and `restart: "no"` (so a NAS reboot doesn't quietly bring it back up).
### One-time setup on the NAS
Create the directory structure. These paths are deliberately **not** under `/volume1/configs/redefined-designs` — sharing production's Postgres directory would mean QA writing into production's database files.
```bash
sudo mkdir -p /volume1/configs/redefined-designs-qa/postgres
sudo mkdir -p /volume1/configs/redefined-designs-qa/uploads
# The official postgres image runs as uid/gid 999. Without this the DB
# container exits immediately with a data-directory permissions error.
sudo chown -R 999:999 /volume1/configs/redefined-designs-qa/postgres
sudo chmod 700 /volume1/configs/redefined-designs-qa/postgres
# Uploads are written by the app container, which runs as root.
sudo chown -R root:root /volume1/configs/redefined-designs-qa/uploads
sudo chmod 755 /volume1/configs/redefined-designs-qa/uploads
# Confirm the two environments are separate before going further.
ls -la /volume1/configs/redefined-designs-qa/
ls -la /volume1/configs/redefined-designs/
```
Then, in Portainer, create a stack **named `redefined-designs-qa`** from `docker-compose.qa.yml`, with one environment variable `QA_DB_PASSWORD`. The stack name matters: it becomes the compose project name, and reusing production's name would make compose reconcile the two stacks against each other and remove the production containers.
Finally, add an Nginx Proxy Manager host for `qa-redefined-designs` pointing at the NAS on port `32751`, with authentik forward-auth on `location /` — the whole site, not just `/admin`, so nothing unreleased is publicly reachable.
### Reviewing a change
```bash
cd /volume1/docker/redefined-designs
gitc() { sudo docker run --rm -it -v /volume1/docker/redefined-designs:/repo -v ~/.gitconfig-docker/.gitconfig:/root/.gitconfig -w /repo alpine/git "$@"; }
gitc pull origin main
gitc log --oneline -3
sudo docker build --no-cache -t redefined-designs:qa /volume1/docker/redefined-designs
# start the redefined-designs-qa stack in Portainer
# Migrations run as the container starts — confirm before reviewing.
sudo docker logs redefined-designs-qa-syn | head -30
```
Stop the stack in Portainer when finished.
### Resetting QA data
QA data is disposable. To start from an empty database, stop the stack first, then:
```bash
# Check the path before running this. It must contain -qa.
sudo rm -rf /volume1/configs/redefined-designs-qa/postgres/pgdata
```
Restarting the stack recreates the database and re-runs every migration from scratch.