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:
@@ -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.
|
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.
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
# QA stack — a disposable copy of the app for reviewing merged-but-undeployed
|
||||||
|
# changes online. See issue #25.
|
||||||
|
#
|
||||||
|
# Deployed as its own Portainer stack, separate from production. Every value
|
||||||
|
# that could collide with production has been changed: container names, host
|
||||||
|
# port, volume paths, database name, and image tag. Do not copy a path or port
|
||||||
|
# back from the production stack — a shared Postgres data directory would mean
|
||||||
|
# QA writing into production's database files.
|
||||||
|
#
|
||||||
|
# Name the Portainer stack `redefined-designs-qa`, NOT `redefined-designs`.
|
||||||
|
# The stack name becomes the compose project name. Reusing production's name
|
||||||
|
# would make compose treat this as the same project and reconcile the two
|
||||||
|
# against each other — it would happily remove the production containers
|
||||||
|
# because they are not declared in this file.
|
||||||
|
#
|
||||||
|
# Required stack environment variable:
|
||||||
|
# QA_DB_PASSWORD — deliberately not named DB_PASSWORD, so pasting the
|
||||||
|
# production stack's variables here does nothing silently.
|
||||||
|
#
|
||||||
|
# Build the image on the NAS before starting the stack:
|
||||||
|
# sudo docker build --no-cache -t redefined-designs:qa /volume1/docker/redefined-designs
|
||||||
|
|
||||||
|
services:
|
||||||
|
redefined-designs-qa:
|
||||||
|
image: redefined-designs:qa
|
||||||
|
container_name: redefined-designs-qa-syn
|
||||||
|
environment:
|
||||||
|
- TZ=America/Chicago
|
||||||
|
- PORT=3000
|
||||||
|
- PGHOST=redefined-designs-qa-db-syn
|
||||||
|
- PGPORT=5432
|
||||||
|
- PGUSER=redefined_qa
|
||||||
|
- PGPASSWORD=${QA_DB_PASSWORD}
|
||||||
|
- PGDATABASE=redefined_qa
|
||||||
|
|
||||||
|
# No PayPal credentials at all. DEMO_MODE lets the full cart and checkout
|
||||||
|
# flow run without them, so QA can exercise the whole purchase path with
|
||||||
|
# no way to reach live PayPal. Never set PAYPAL_ENV=live here. To test a
|
||||||
|
# real PayPal integration change, add sandbox credentials and set
|
||||||
|
# PAYPAL_ENV=sandbox — never the live ones.
|
||||||
|
- DEMO_MODE=true
|
||||||
|
|
||||||
|
# No SMTP configuration either. The mailer degrades gracefully when
|
||||||
|
# unconfigured: it logs a warning and skips sending. That is the desired
|
||||||
|
# behaviour here — a QA run must not be able to email real customers if
|
||||||
|
# a fixture ever contains a real address.
|
||||||
|
- SITE_CURRENCY=USD
|
||||||
|
- RESERVATION_MINUTES=15
|
||||||
|
- PUBLIC_URL=https://qa-redefined-designs.bermudalamb.synology.me
|
||||||
|
volumes:
|
||||||
|
# Separate uploads directory. Sharing production's would let a QA run
|
||||||
|
# write into, and a QA teardown delete, real product images.
|
||||||
|
- /volume1/configs/redefined-designs-qa/uploads:/app/uploads
|
||||||
|
ports:
|
||||||
|
# 32751, not production's 32750.
|
||||||
|
- 32751:3000
|
||||||
|
depends_on:
|
||||||
|
redefined-designs-qa-db-syn:
|
||||||
|
condition: service_healthy
|
||||||
|
# Not `unless-stopped`: QA is meant to be up only while a review is
|
||||||
|
# happening. `unless-stopped` would silently bring it back after every NAS
|
||||||
|
# reboot and leave it running indefinitely.
|
||||||
|
restart: "no"
|
||||||
|
|
||||||
|
redefined-designs-qa-db-syn:
|
||||||
|
image: postgres:16
|
||||||
|
container_name: redefined-designs-qa-db-syn
|
||||||
|
environment:
|
||||||
|
- POSTGRES_USER=redefined_qa
|
||||||
|
- POSTGRES_PASSWORD=${QA_DB_PASSWORD}
|
||||||
|
- POSTGRES_DB=redefined_qa
|
||||||
|
- PGDATA=/var/lib/postgresql/data/pgdata
|
||||||
|
volumes:
|
||||||
|
# Distinct data directory from production's
|
||||||
|
# /volume1/configs/redefined-designs/postgres. This is the single most
|
||||||
|
# important difference in this file.
|
||||||
|
- /volume1/configs/redefined-designs-qa/postgres:/var/lib/postgresql/data
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "pg_isready -U redefined_qa -d redefined_qa"]
|
||||||
|
interval: 10s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 10
|
||||||
|
restart: "no"
|
||||||
Reference in New Issue
Block a user