docker: make the stack multi-instance so a test.escapepage.com copy can run alongside prod
compose.yaml / compose.override.yaml:
- container_name is now ${STACK_NAME:-escapepage}-* (STACK_NAME is a plain var,
not COMPOSE_PROJECT_NAME, so prod keeps its escapepage-* names with no config change)
- every published host port is ${*_PORT:-<current default>}, so prod is unchanged
and a second stack can bind its own (localhost-only) ports
- nginx joins the external nginx_default network so Nginx Proxy Manager can
forward to <stack>-nginx by name
restart.sh:
- scoped to STACK_NAME / COMPOSE_PROJECT_NAME read from docker/.env, so running it
from the test checkout can't touch the prod stack
- host-wide `docker system prune` / `docker builder prune` moved behind --prune-all
Adds docker/.env.test.example and doc/test-environment.md (separate checkout,
env layers, NPM proxy host + Access List IP allowlist, Mercure on test).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,75 @@
|
||||
# =============================================================================
|
||||
# docker/.env for a TEST / STAGING stack (e.g. test.escapepage.com)
|
||||
# =============================================================================
|
||||
# Copy this to docker/.env inside the *test* checkout and fill in the blanks.
|
||||
# docker/.env is git-ignored, so it never leaves the server.
|
||||
#
|
||||
# Compose reads this file automatically. Anything unique per stack is derived
|
||||
# from COMPOSE_PROJECT_NAME + the *_PORT vars below, so the same compose.yaml
|
||||
# serves both production and this copy.
|
||||
#
|
||||
# Two config layers, don't mix them up:
|
||||
# - THIS file -> consumed by `docker compose` (container names, ports, and the
|
||||
# runtime env it injects into the php containers).
|
||||
# - <checkout>/.env(.local) -> consumed by Symfony itself (APP_SECRET,
|
||||
# TRUSTED_PROXIES, messenger DSN, CLI database access, ...).
|
||||
# =============================================================================
|
||||
|
||||
## --- Instance identity -------------------------------------------------------
|
||||
# Both must be unique on the host.
|
||||
# STACK_NAME -> prefix for every container_name (escapepage-test-php …)
|
||||
# COMPOSE_PROJECT_NAME -> compose project: isolates networks, volumes and the
|
||||
# labels that `docker compose down` / restart.sh act on
|
||||
STACK_NAME=escapepage-test
|
||||
COMPOSE_PROJECT_NAME=escapepage-test
|
||||
|
||||
## --- Published host ports ---------------------------------------------------
|
||||
# Bound to localhost only: the sole public entrypoint is Nginx Proxy Manager,
|
||||
# which reaches the containers over the shared `nginx_default` network by name.
|
||||
# Pick ports that don't clash with the production stack (8080/8443/3306/8090/8025/1025).
|
||||
NGINX_HTTP_PORT=127.0.0.1:8081
|
||||
NGINX_HTTPS_PORT=127.0.0.1:8444
|
||||
DB_HOST_PORT=127.0.0.1:3307
|
||||
MERCURE_HTTP_PORT=127.0.0.1:8091
|
||||
MAILPIT_UI_PORT=127.0.0.1:8026
|
||||
MAILPIT_SMTP_PORT=127.0.0.1:1026
|
||||
|
||||
## --- PHP image build ------------------------------------------------------
|
||||
USER_ID=1000
|
||||
GROUP_ID=1000
|
||||
|
||||
## --- Symfony runtime (injected into php / php-worker / php-cron) ------------
|
||||
APP_ENV=prod
|
||||
SITE_BASE_URL=https://test.escapepage.com
|
||||
|
||||
# Staging should NOT send real mail. Point at the bundled Mailpit and read it
|
||||
# at http://127.0.0.1:8026 on the server (or via an NPM host if you expose it).
|
||||
MAILER_DSN=smtp://mailer:1026
|
||||
MAILER_FROM=mailer@test.escapepage.com
|
||||
|
||||
## --- Database -------------------------------------------------------------
|
||||
# `database` is the compose *service* name and resolves inside this stack's
|
||||
# own network - keep it as-is. Use its own name + fresh credentials so a mistake
|
||||
# here can never point at the production database.
|
||||
DB_NAME=escapepage_test
|
||||
DB_USER=escapepage
|
||||
DB_PASSWORD=CHANGE_ME_test_db_password
|
||||
MYSQL_ROOT_PASSWORD=CHANGE_ME_test_root_password
|
||||
DATABASE_URL=pdo_mysql://escapepage:CHANGE_ME_test_db_password@database:3306/escapepage_test?serverVersion=8.0.32&charset=utf8mb4
|
||||
|
||||
## --- Mercure -----------------------------------------------------------------
|
||||
# Internal hub URL (service name, stays the same). Public URL + CORS must be the
|
||||
# test domain. Add a `mercure-test.escapepage.com` proxy host in NPM ->
|
||||
# <project>-mercure:80.
|
||||
MERCURE_URL=http://mercure/.well-known/mercure
|
||||
MERCURE_PUBLIC_URL=https://mercure-test.escapepage.com/.well-known/mercure
|
||||
MERCURE_JWT_SECRET=CHANGE_ME_generate_with_openssl_rand_hex_32
|
||||
MERCURE_CORS_ALLOWED_ORIGINS="https://test.escapepage.com"
|
||||
MERCURE_TOPIC_BASE=https://test.escapepage.com
|
||||
|
||||
## --- reCAPTCHA v3 ----------------------------------------------------------
|
||||
# Register test.escapepage.com in the reCAPTCHA admin console and paste its keys,
|
||||
# or leave the placeholders and expect the contact / "suggest a room" forms to
|
||||
# fail captcha validation on staging.
|
||||
RECAPTCHA3_KEY=CHANGE_ME_or_reuse_prod_if_domain_added
|
||||
RECAPTCHA3_SECRET=CHANGE_ME_or_reuse_prod_if_domain_added
|
||||
@@ -17,8 +17,8 @@ services:
|
||||
mailer:
|
||||
image: axllent/mailpit
|
||||
ports:
|
||||
- "1025:1025"
|
||||
- "8025:8025"
|
||||
- "${MAILPIT_SMTP_PORT:-1025}:1025"
|
||||
- "${MAILPIT_UI_PORT:-8025}:8025"
|
||||
environment:
|
||||
MP_SMTP_AUTH_ACCEPT_ANY: 1
|
||||
MP_SMTP_AUTH_ALLOW_INSECURE: 1
|
||||
|
||||
+26
-15
@@ -1,5 +1,14 @@
|
||||
version: '3.7'
|
||||
|
||||
# This stack can run more than once on the same host (e.g. production + a
|
||||
# test.escapepage.com staging copy). Everything that must be unique per instance
|
||||
# comes from docker/.env:
|
||||
# STACK_NAME -> container name prefix (default: escapepage)
|
||||
# COMPOSE_PROJECT_NAME -> compose project / network namespace
|
||||
# NGINX_HTTP_PORT etc. -> published host ports
|
||||
# With no docker/.env overrides it behaves exactly as before: containers
|
||||
# escapepage-*, ports 8080/8443/3306/8090/8025.
|
||||
|
||||
services:
|
||||
php:
|
||||
build:
|
||||
@@ -8,7 +17,7 @@ services:
|
||||
args:
|
||||
USER_ID: ${USER_ID}
|
||||
GROUP_ID: ${GROUP_ID}
|
||||
container_name: escapepage-php
|
||||
container_name: ${STACK_NAME:-escapepage}-php
|
||||
volumes:
|
||||
- ../:/var/www/html:delegated
|
||||
- /etc/hosts:/etc/hosts:ro
|
||||
@@ -40,7 +49,7 @@ services:
|
||||
args:
|
||||
USER_ID: ${USER_ID}
|
||||
GROUP_ID: ${GROUP_ID}
|
||||
container_name: escapepage-php-worker
|
||||
container_name: ${STACK_NAME:-escapepage}-php-worker
|
||||
volumes:
|
||||
- ../:/var/www/html:delegated
|
||||
- /etc/hosts:/etc/hosts:ro
|
||||
@@ -73,7 +82,7 @@ services:
|
||||
args:
|
||||
USER_ID: ${USER_ID}
|
||||
GROUP_ID: ${GROUP_ID}
|
||||
container_name: escapepage-php-cron
|
||||
container_name: ${STACK_NAME:-escapepage}-php-cron
|
||||
volumes:
|
||||
- ../:/var/www/html:delegated
|
||||
- /etc/hosts:/etc/hosts:ro
|
||||
@@ -101,10 +110,10 @@ services:
|
||||
|
||||
nginx:
|
||||
image: nginx:1.29.4-alpine
|
||||
container_name: escapepage-nginx
|
||||
container_name: ${STACK_NAME:-escapepage}-nginx
|
||||
ports:
|
||||
- "8080:80"
|
||||
- "8443:443"
|
||||
- "${NGINX_HTTP_PORT:-8080}:80"
|
||||
- "${NGINX_HTTPS_PORT:-8443}:443"
|
||||
volumes:
|
||||
- ../:/var/www/html:ro
|
||||
- ./nginx/default.conf:/etc/nginx/conf.d/default.conf:ro
|
||||
@@ -112,16 +121,18 @@ services:
|
||||
- /etc/hosts:/etc/hosts:ro
|
||||
depends_on:
|
||||
- php
|
||||
# networks:
|
||||
# backend:
|
||||
# ipv4_address: 172.23.0.12
|
||||
# Joined to the Nginx Proxy Manager network so NPM can forward straight to
|
||||
# "<project>-nginx" without going back out to a published host port.
|
||||
networks:
|
||||
- default
|
||||
- nginx_proxy
|
||||
restart: unless-stopped
|
||||
|
||||
mailer:
|
||||
image: axllent/mailpit:latest
|
||||
container_name: escapepage-mailer
|
||||
container_name: ${STACK_NAME:-escapepage}-mailer
|
||||
ports:
|
||||
- "8025:8025"
|
||||
- "${MAILPIT_UI_PORT:-8025}:8025"
|
||||
volumes:
|
||||
- /etc/hosts:/etc/hosts:ro
|
||||
networks:
|
||||
@@ -131,7 +142,7 @@ services:
|
||||
|
||||
mercure:
|
||||
image: dunglas/mercure:v0.21
|
||||
container_name: escapepage-mercure
|
||||
container_name: ${STACK_NAME:-escapepage}-mercure
|
||||
environment:
|
||||
SERVER_NAME: "http://:80"
|
||||
MERCURE_PUBLISHER_JWT_KEY: ${MERCURE_JWT_SECRET}
|
||||
@@ -143,7 +154,7 @@ services:
|
||||
publish_origins ${MERCURE_CORS_ALLOWED_ORIGINS}
|
||||
anonymous
|
||||
ports:
|
||||
- "8090:80"
|
||||
- "${MERCURE_HTTP_PORT:-8090}:80"
|
||||
volumes:
|
||||
- /etc/hosts:/etc/hosts:ro
|
||||
networks:
|
||||
@@ -154,7 +165,7 @@ services:
|
||||
###> doctrine/doctrine-bundle ###
|
||||
database:
|
||||
image: mysql:8.0
|
||||
container_name: escapepage-db
|
||||
container_name: ${STACK_NAME:-escapepage}-db
|
||||
environment:
|
||||
MYSQL_DATABASE: ${DB_NAME}
|
||||
MYSQL_USER: ${DB_USER}
|
||||
@@ -173,7 +184,7 @@ services:
|
||||
- /etc/hosts:/etc/hosts:ro
|
||||
# Uncomment the two lines below if you need to access MySQL from your host (workbench, etc.)
|
||||
ports:
|
||||
- "3306:3306"
|
||||
- "${DB_HOST_PORT:-3306}:3306"
|
||||
# networks:
|
||||
# backend:
|
||||
# ipv4_address: 172.23.0.15
|
||||
|
||||
+39
-11
@@ -1,22 +1,50 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Script to completely restart the project as requested
|
||||
# Can be run from any directory
|
||||
# Completely restart ONE project stack (prod or a test/staging copy).
|
||||
# Can be run from any directory. Everything is scoped to the Compose project
|
||||
# name so running this from the test checkout never touches the prod stack.
|
||||
#
|
||||
# ./docker/restart.sh # rebuild-less restart of this checkout's stack
|
||||
# ./docker/restart.sh --prune-all # also run host-wide `docker system/builder prune`
|
||||
# # (old behaviour; skip it when another stack shares the host)
|
||||
|
||||
DOCKER_DIR=$(cd "$(dirname "$0")" && pwd)
|
||||
ROOT_DIR=$(cd "$DOCKER_DIR/.." && pwd)
|
||||
|
||||
echo "Stopping and removing containers..."
|
||||
(cd "$DOCKER_DIR" && docker compose -f compose.yaml -f compose.override.yaml down -v --remove-orphans) || true
|
||||
docker network rm escapepage_network || true
|
||||
docker network rm $(docker network ls -q --filter name=escapepage) || true
|
||||
docker network prune -f || true
|
||||
docker rm -f escapepage-db escapepage-php escapepage-nginx escapepage-mercure escapepage-mailer escapepage-php-worker escapepage-php-cron || true
|
||||
docker system prune -f || true
|
||||
PRUNE_ALL=0
|
||||
for arg in "$@"; do
|
||||
case "$arg" in
|
||||
--prune-all) PRUNE_ALL=1 ;;
|
||||
*) echo "Unknown option: $arg" >&2; exit 1 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
echo "Clearing Docker build cache..."
|
||||
docker builder prune -af
|
||||
# Read the identifiers from docker/.env (same file Compose uses). STACK_NAME is
|
||||
# the container-name prefix; COMPOSE_PROJECT_NAME is the compose project.
|
||||
read_env() { [ -f "$DOCKER_DIR/.env" ] && grep -E "^$1=" "$DOCKER_DIR/.env" | tail -n1 | cut -d= -f2- | tr -d "\"'" || true; }
|
||||
STACK="$(read_env STACK_NAME)"; STACK="${STACK:-escapepage}"
|
||||
PROJECT="$(read_env COMPOSE_PROJECT_NAME)"; PROJECT="${PROJECT:-$STACK}"
|
||||
echo "Restarting stack: $STACK (compose project: $PROJECT)"
|
||||
|
||||
echo "Stopping and removing containers..."
|
||||
(cd "$DOCKER_DIR" && docker compose -p "$PROJECT" -f compose.yaml -f compose.override.yaml down -v --remove-orphans) || true
|
||||
|
||||
# Belt-and-suspenders: drop anything still lingering for THIS stack only.
|
||||
docker rm -f \
|
||||
"${STACK}-db" "${STACK}-php" "${STACK}-nginx" "${STACK}-mercure" \
|
||||
"${STACK}-mailer" "${STACK}-php-worker" "${STACK}-php-cron" 2>/dev/null || true
|
||||
for net in $(docker network ls -q --filter "name=^${PROJECT}_" 2>/dev/null); do
|
||||
docker network rm "$net" || true
|
||||
done
|
||||
|
||||
if [ "$PRUNE_ALL" -eq 1 ]; then
|
||||
echo "Host-wide prune (containers, networks, build cache)..."
|
||||
docker system prune -f || true
|
||||
docker builder prune -af || true
|
||||
else
|
||||
echo "Skipping host-wide prune (pass --prune-all to force it)."
|
||||
fi
|
||||
|
||||
echo "Setting permissions for var/volumes/db and var directories..."
|
||||
sudo chown -R 1000:1000 "$ROOT_DIR/var/volumes/db" || true
|
||||
|
||||
Reference in New Issue
Block a user