# Test / staging environment (`test.escapepage.com`) Runs a second copy of the full Docker stack on the same server as production, behind the same Nginx Proxy Manager (NPM). Production is untouched: with no `docker/.env` overrides, `compose.yaml` behaves exactly as before. ## How isolation works `docker/compose.yaml` derives everything instance-specific from `docker/.env`: | Concern | Mechanism | prod (no overrides) | test | |---|---|---|---| | Container names | `${STACK_NAME:-escapepage}-*` | `escapepage-php` … | `escapepage-test-php` … | | Compose project (networks, labels) | `COMPOSE_PROJECT_NAME` | `docker` (unchanged) | `escapepage-test` | | Published ports | `${NGINX_HTTP_PORT:-8080}` etc. | `0.0.0.0:8080/8443/3306/8090/8025` | `127.0.0.1:8081/8444/3307/8091/8026` | | Database files | bind mount `../var/volumes/db` | per checkout | per checkout | | Web reachability | `nginx` joined to external `nginx_default` | via NPM | via NPM | `STACK_NAME` is a plain variable (not the Compose-managed `COMPOSE_PROJECT_NAME`), so its `:-escapepage` default is reliable and **production needs no `docker/.env` change** to keep its `escapepage-*` container names. `nginx`, `mercure` and `mailer` all sit on the external `nginx_default` network, so NPM forwards straight to `escapepage-test-nginx` / `-mercure` by name — no public host port needed. ## Production checkout — optional Nothing is required. Two optional tidy-ups, each needing a `docker compose up -d` to recreate the affected container: - The `nginx` service now also attaches to `nginx_default`. If you'd rather have NPM forward to `escapepage-nginx` by name instead of `host:8080`, recreate it and repoint the NPM proxy host. Otherwise the published `8080/8443` still work as before and you can ignore this. - Add `STACK_NAME=escapepage` and `COMPOSE_PROJECT_NAME=escapepage` to the production `docker/.env` to move it off the implicit `docker` project name. Cosmetic; do it only if you want the two stacks named symmetrically. ## Standing up the test stack ### 1. DNS `test.escapepage.com` → server IP. (`mercure-test.escapepage.com` too if you want live game features.) On Cloudflare, DNS-only ("grey cloud") keeps it low-profile. ### 2. Separate checkout ```bash git clone /opt/escapepage-test cd /opt/escapepage-test git checkout ``` Use a **separate clone**, not `git worktree` — the Docker build context is the checkout directory and full isolation avoids surprises. ### 3. Compose env ```bash cp docker/.env.test.example docker/.env # edit: real passwords, fresh MERCURE_JWT_SECRET (openssl rand -hex 32), reCAPTCHA keys ``` ### 4. Symfony env overrides Create `/opt/escapepage-test/.env.local` in the checkout (git-ignored): ``` APP_SECRET= # Behind NPM the forwarded client IP lands from a Docker-private range; trust them # all on staging so URL generation / HTTPS detection work. TRUSTED_PROXIES=127.0.0.1,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16 ``` `APP_ENV=prod`, `DATABASE_URL`, `SITE_BASE_URL`, `MERCURE_*` and `MAILER_DSN` are already supplied to the containers by `docker/.env`. ### 5. Build & start ```bash ./docker/setup.sh ``` Builds `escapepage-test-*` images, starts the containers, creates + migrates `escapepage_test`, builds assets. Re-run any time; `--no-build` skips the image rebuild. ### 6. Nginx Proxy Manager — proxy host - Domain: `test.escapepage.com` - Forward to: `escapepage-test-nginx`, port **443**, scheme **https** (the app nginx force-redirects 80→443 and serves a self-signed cert; leave "Verify SSL" off — same arrangement as production) - SSL: request a Let's Encrypt cert, Force SSL, HTTP/2 - Optional: add response header `X-Robots-Tag: noindex` If you want live game screens, add a second proxy host `mercure-test.escapepage.com` → `escapepage-test-mercure` port `80` (http). ### 7. Restrict access to your IP — NPM Access List A host firewall on 80/443 can't help: prod and test share those ports on NPM. Restrict at the proxy instead. - **NPM → Access Lists → Add Access List** - Name: e.g. `staging-allowlist` - Authorisation: leave empty - Access: `Allow ` then a final `Deny all` - Satisfy: **Any** - Edit the `test.escapepage.com` proxy host → **Access List** → select it. - Do the same on `mercure-test.escapepage.com` if you added it. Everyone else gets `403` at the proxy. Update the IP in one place when it changes. Defence in depth (optional): in `/opt/escapepage-test/docker/nginx/default.conf`, inside the `server { listen 443 ... }` block: ```nginx set_real_ip_from 172.16.0.0/12; # NPM's docker network real_ip_header X-Forwarded-For; allow ; deny all; ``` ## Deploying a new version to test ```bash cd /opt/escapepage-test git fetch && git checkout && git pull docker compose -f docker/compose.yaml -f docker/compose.override.yaml \ exec php composer install docker compose ... exec php php bin/console doctrine:migrations:migrate -n docker compose ... exec php php bin/console cache:clear docker compose ... exec php npm ci && ... npm run build docker restart escapepage-test-php-worker ``` (Or just `./docker/setup.sh --no-build`.) ## Safety notes - **`docker/restart.sh` is now scoped to `STACK_NAME` / `COMPOSE_PROJECT_NAME`** from `docker/.env` — running it in the test checkout only touches `escapepage-test-*`. The host-wide `docker system prune` / `docker builder prune` it used to always run are now opt-in via `./docker/restart.sh --prune-all`; don't use that flag while the other stack shares the host. - The test `docker/.env` uses its own `DB_NAME` and passwords so a config slip can't reach the production database. - `MAILER_DSN=smtp://mailer:1026` keeps staging mail inside Mailpit instead of sending through Mailgun.