diff --git a/README.md b/README.md index 3334a21..d05f41d 100755 --- a/README.md +++ b/README.md @@ -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. \ No newline at end of file +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. \ No newline at end of file diff --git a/docker-compose.qa.yml b/docker-compose.qa.yml new file mode 100644 index 0000000..be79894 --- /dev/null +++ b/docker-compose.qa.yml @@ -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"