From 3a09d485beea7ba4d0f903851bde196384bfb9ad Mon Sep 17 00:00:00 2001 From: Eric Holscher Date: Mon, 5 Oct 2026 11:45:56 +0200 Subject: [PATCH 1/7] Docker: run community and business side by side Make every host port configurable with RTDDEV_PORT_* variables (defaults unchanged), and add an opt-in front proxy (inv docker.front) that owns port 80 and routes *.devthedocs.com to business and everything else to community. --- dockerfiles/docker-compose-front.yml | 23 +++++++++++++++++ dockerfiles/docker-compose-search.yml | 2 +- dockerfiles/docker-compose.yml | 14 +++++++---- dockerfiles/nginx-front/default.conf.template | 25 +++++++++++++++++++ dockerfiles/tasks.py | 19 ++++++++++++++ 5 files changed, 77 insertions(+), 6 deletions(-) create mode 100644 dockerfiles/docker-compose-front.yml create mode 100644 dockerfiles/nginx-front/default.conf.template diff --git a/dockerfiles/docker-compose-front.yml b/dockerfiles/docker-compose-front.yml new file mode 100644 index 0000000..065e441 --- /dev/null +++ b/dockerfiles/docker-compose-front.yml @@ -0,0 +1,23 @@ +# Front proxy to run the community and business environments side by side. +# +# Both environments publish NGINX on port 80 by default. +# Start each one with a different `RTDDEV_PORT_NGINX` and this proxy takes port 80, +# routing `*.devthedocs.com` to business and everything else to community. +# See `inv docker.front`. +name: rtd-front + +services: + front: + image: nginx:1.22 + ports: + - "80:80" + environment: + - RTDDEV_FRONT_COMMUNITY_PORT=${RTDDEV_FRONT_COMMUNITY_PORT:-10080} + - RTDDEV_FRONT_BUSINESS_PORT=${RTDDEV_FRONT_BUSINESS_PORT:-10081} + # Required on Linux; Docker Desktop resolves it already. + extra_hosts: + - "host.docker.internal:host-gateway" + volumes: + - ${PWD}/common/dockerfiles/nginx-front:/etc/nginx/templates + logging: + driver: "none" diff --git a/dockerfiles/docker-compose-search.yml b/dockerfiles/docker-compose-search.yml index 68810d9..8657723 100644 --- a/dockerfiles/docker-compose-search.yml +++ b/dockerfiles/docker-compose-search.yml @@ -28,7 +28,7 @@ services: - xpack.security.transport.ssl.enabled=false - 'ES_JAVA_OPTS=-Xms128m -Xmx128m' ports: - - "9200:9200" + - "${RTDDEV_PORT_SEARCH:-9200}:9200" links: - web - celery diff --git a/dockerfiles/docker-compose.yml b/dockerfiles/docker-compose.yml index 0f603c8..8a8c499 100644 --- a/dockerfiles/docker-compose.yml +++ b/dockerfiles/docker-compose.yml @@ -20,7 +20,7 @@ services: stop_grace_period: 1s image: nginx:1.22 ports: - - "80:80" + - "${RTDDEV_PORT_NGINX:-80}:80" links: - web - proxito @@ -70,7 +70,7 @@ services: tty: true working_dir: /usr/src/app/checkouts/addons/ ports: - - "8000:8000" + - "${RTDDEV_PORT_ADDONS:-8000}:8000" command: [ "npm", "run", @@ -98,6 +98,8 @@ services: - RTD_DJANGO_DEBUG - RTD_LOGGING_SUPPRESS_TRACEBACKS - RTD_EXT_THEME_DEV_SERVER_ENABLED + - RTDDEV_PORT_WEBPACK + - RTDDEV_PORT_STORAGE - RTD_FILETREEDIFF_ALL # AWS related settings. - RTD_S3_PROVIDER @@ -141,6 +143,8 @@ services: - INIT=${INIT:-} - DOCKER_NO_RELOAD - RTD_EXT_THEME_DEV_SERVER_ENABLED + - RTDDEV_PORT_WEBPACK + - RTDDEV_PORT_STORAGE - RTD_PRODUCTION_DOMAIN - RTD_PUBLIC_DOMAIN - RUSTFS_ROOT_USER=admin @@ -187,7 +191,7 @@ services: dockerfile: ${PWD}/common/dockerfiles/Dockerfile.webpack working_dir: /usr/src/app/checkouts/ext-theme ports: - - "10001:10001" + - "${RTDDEV_PORT_WEBPACK:-10001}:10001" volumes: - ${PWD}/${RTDDEV_PATH_EXT_THEME:-../ext-theme}/src:/usr/src/app/checkouts/ext-theme/src - ${PWD}/${RTDDEV_PATH_EXT_THEME:-../ext-theme}/readthedocsext:/usr/src/app/checkouts/ext-theme/readthedocsext @@ -446,8 +450,8 @@ services: storage: image: rustfs/rustfs:1.0.0-alpha.83 ports: - - "9000:9000" - - "9001:9001" + - "${RTDDEV_PORT_STORAGE:-9000}:9000" + - "${RTDDEV_PORT_STORAGE_CONSOLE:-9001}:9001" environment: - RUSTFS_ADDRESS=:9000 - RUSTFS_ACCESS_KEY=admin diff --git a/dockerfiles/nginx-front/default.conf.template b/dockerfiles/nginx-front/default.conf.template new file mode 100644 index 0000000..7a94fbd --- /dev/null +++ b/dockerfiles/nginx-front/default.conf.template @@ -0,0 +1,25 @@ +server { + listen 80; + server_name .devthedocs.com; + client_max_body_size 0; + + location / { + proxy_pass http://host.docker.internal:${RTDDEV_FRONT_BUSINESS_PORT}; + proxy_set_header Host $http_host; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_read_timeout 300s; + } +} + +# Community also serves custom domains, so it gets everything else. +server { + listen 80 default_server; + client_max_body_size 0; + + location / { + proxy_pass http://host.docker.internal:${RTDDEV_FRONT_COMMUNITY_PORT}; + proxy_set_header Host $http_host; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_read_timeout 300s; + } +} diff --git a/dockerfiles/tasks.py b/dockerfiles/tasks.py index a94f93a..99be86e 100644 --- a/dockerfiles/tasks.py +++ b/dockerfiles/tasks.py @@ -9,6 +9,7 @@ DOCKER_COMPOSE_SEARCH = "common/dockerfiles/docker-compose-search.yml" DOCKER_COMPOSE_ASSETS = "dockerfiles/docker-compose-assets.yml" DOCKER_COMPOSE_OVERRIDE = "docker-compose.override.yml" +DOCKER_COMPOSE_FRONT = "common/dockerfiles/docker-compose-front.yml" DOCKER_COMPOSE_COMMAND = f"docker compose --project-directory=. -f {DOCKER_COMPOSE} -f {DOCKER_COMPOSE_OVERRIDE} -f {DOCKER_COMPOSE_SEARCH}" @@ -66,6 +67,24 @@ def down(c, volumes=False): c.run(f"{DOCKER_COMPOSE_COMMAND} down", pty=True) +@task( + help={ + "down": "Stop and remove the proxy instead of starting it (default: False)", + } +) +def front(c, down=False): + """ + Start the proxy that runs community and business side by side. + + The proxy takes port 80, so start each environment on its own port first: + `RTDDEV_PORT_NGINX=10080` for community and `RTDDEV_PORT_NGINX=10081` for business. + """ + if down: + c.run(f"docker compose -f {DOCKER_COMPOSE_FRONT} down", pty=True) + else: + c.run(f"docker compose -f {DOCKER_COMPOSE_FRONT} up --detach", pty=True) + + @task( help={ "search": "Start search container (default: True)", From 682adb44a4a2e87595f9f0e3f62dea4338191feb Mon Sep 17 00:00:00 2001 From: Eric Holscher Date: Mon, 5 Oct 2026 12:12:19 +0200 Subject: [PATCH 2/7] Docker: drop the front proxy The community NGINX routes *.devthedocs.com to business instead (readthedocs/readthedocs.org#13365). --- dockerfiles/docker-compose-front.yml | 23 ----------------- dockerfiles/nginx-front/default.conf.template | 25 ------------------- dockerfiles/tasks.py | 19 -------------- 3 files changed, 67 deletions(-) delete mode 100644 dockerfiles/docker-compose-front.yml delete mode 100644 dockerfiles/nginx-front/default.conf.template diff --git a/dockerfiles/docker-compose-front.yml b/dockerfiles/docker-compose-front.yml deleted file mode 100644 index 065e441..0000000 --- a/dockerfiles/docker-compose-front.yml +++ /dev/null @@ -1,23 +0,0 @@ -# Front proxy to run the community and business environments side by side. -# -# Both environments publish NGINX on port 80 by default. -# Start each one with a different `RTDDEV_PORT_NGINX` and this proxy takes port 80, -# routing `*.devthedocs.com` to business and everything else to community. -# See `inv docker.front`. -name: rtd-front - -services: - front: - image: nginx:1.22 - ports: - - "80:80" - environment: - - RTDDEV_FRONT_COMMUNITY_PORT=${RTDDEV_FRONT_COMMUNITY_PORT:-10080} - - RTDDEV_FRONT_BUSINESS_PORT=${RTDDEV_FRONT_BUSINESS_PORT:-10081} - # Required on Linux; Docker Desktop resolves it already. - extra_hosts: - - "host.docker.internal:host-gateway" - volumes: - - ${PWD}/common/dockerfiles/nginx-front:/etc/nginx/templates - logging: - driver: "none" diff --git a/dockerfiles/nginx-front/default.conf.template b/dockerfiles/nginx-front/default.conf.template deleted file mode 100644 index 7a94fbd..0000000 --- a/dockerfiles/nginx-front/default.conf.template +++ /dev/null @@ -1,25 +0,0 @@ -server { - listen 80; - server_name .devthedocs.com; - client_max_body_size 0; - - location / { - proxy_pass http://host.docker.internal:${RTDDEV_FRONT_BUSINESS_PORT}; - proxy_set_header Host $http_host; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_read_timeout 300s; - } -} - -# Community also serves custom domains, so it gets everything else. -server { - listen 80 default_server; - client_max_body_size 0; - - location / { - proxy_pass http://host.docker.internal:${RTDDEV_FRONT_COMMUNITY_PORT}; - proxy_set_header Host $http_host; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_read_timeout 300s; - } -} diff --git a/dockerfiles/tasks.py b/dockerfiles/tasks.py index 99be86e..a94f93a 100644 --- a/dockerfiles/tasks.py +++ b/dockerfiles/tasks.py @@ -9,7 +9,6 @@ DOCKER_COMPOSE_SEARCH = "common/dockerfiles/docker-compose-search.yml" DOCKER_COMPOSE_ASSETS = "dockerfiles/docker-compose-assets.yml" DOCKER_COMPOSE_OVERRIDE = "docker-compose.override.yml" -DOCKER_COMPOSE_FRONT = "common/dockerfiles/docker-compose-front.yml" DOCKER_COMPOSE_COMMAND = f"docker compose --project-directory=. -f {DOCKER_COMPOSE} -f {DOCKER_COMPOSE_OVERRIDE} -f {DOCKER_COMPOSE_SEARCH}" @@ -67,24 +66,6 @@ def down(c, volumes=False): c.run(f"{DOCKER_COMPOSE_COMMAND} down", pty=True) -@task( - help={ - "down": "Stop and remove the proxy instead of starting it (default: False)", - } -) -def front(c, down=False): - """ - Start the proxy that runs community and business side by side. - - The proxy takes port 80, so start each environment on its own port first: - `RTDDEV_PORT_NGINX=10080` for community and `RTDDEV_PORT_NGINX=10081` for business. - """ - if down: - c.run(f"docker compose -f {DOCKER_COMPOSE_FRONT} down", pty=True) - else: - c.run(f"docker compose -f {DOCKER_COMPOSE_FRONT} up --detach", pty=True) - - @task( help={ "search": "Start search container (default: True)", From dacd8359b90e0c38742b396b524d5afd3784a205 Mon Sep 17 00:00:00 2001 From: Eric Holscher Date: Mon, 5 Oct 2026 15:44:00 +0200 Subject: [PATCH 3/7] Docker: forward the other environment's domains from NGINX Both environments mount the same side-by-side template, so whichever one owns port 80 forwards the other one's domains to it. --- dockerfiles/docker-compose.yml | 4 ++++ dockerfiles/nginx/side-by-side.conf.template | 17 +++++++++++++++++ 2 files changed, 21 insertions(+) create mode 100644 dockerfiles/nginx/side-by-side.conf.template diff --git a/dockerfiles/docker-compose.yml b/dockerfiles/docker-compose.yml index 8a8c499..1935b20 100644 --- a/dockerfiles/docker-compose.yml +++ b/dockerfiles/docker-compose.yml @@ -30,6 +30,10 @@ services: readthedocs: volumes: - ${PWD}/dockerfiles/nginx:/etc/nginx/templates + - ${PWD}/common/dockerfiles/nginx/side-by-side.conf.template:/etc/nginx/templates/side-by-side.conf.template + # Required on Linux by `side-by-side.conf.template`; Docker Desktop resolves it already. + extra_hosts: + - "host.docker.internal:host-gateway" # Disable logs for NGINX by default because they are too noisy and we have # better logs in our application code logging: diff --git a/dockerfiles/nginx/side-by-side.conf.template b/dockerfiles/nginx/side-by-side.conf.template new file mode 100644 index 0000000..8628e4d --- /dev/null +++ b/dockerfiles/nginx/side-by-side.conf.template @@ -0,0 +1,17 @@ +# Forward the other environment's domains (community <-> business) to its NGINX, +# so both can run side by side: whichever one owns port 80 forwards to the other, +# which publishes its NGINX on another host port (`RTDDEV_PORT_NGINX`). +# Returns a 502 for those domains when the other environment isn't running. +server { + listen 80; + server_name ${NGINX_SIDE_BY_SIDE_SERVER_NAME}; + access_log off; + client_max_body_size 0; + + location / { + proxy_pass http://host.docker.internal:${NGINX_SIDE_BY_SIDE_PORT}; + proxy_set_header Host $http_host; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_read_timeout 300s; + } +} From e2f0c20321939072baa3e3eb184af2971bb8f0c6 Mon Sep 17 00:00:00 2001 From: Eric Holscher Date: Mon, 5 Oct 2026 15:45:11 +0200 Subject: [PATCH 4/7] Docker: mount the side-by-side template without nesting A file mount inside the bind-mounted templates directory makes Docker create an empty mountpoint file in the repository. --- dockerfiles/docker-compose.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/dockerfiles/docker-compose.yml b/dockerfiles/docker-compose.yml index 1935b20..b11f1fd 100644 --- a/dockerfiles/docker-compose.yml +++ b/dockerfiles/docker-compose.yml @@ -30,7 +30,9 @@ services: readthedocs: volumes: - ${PWD}/dockerfiles/nginx:/etc/nginx/templates - - ${PWD}/common/dockerfiles/nginx/side-by-side.conf.template:/etc/nginx/templates/side-by-side.conf.template + # Each repository symlinks `side-by-side.conf.template` from its templates to this directory, + # mounted at the path the relative symlink resolves to inside the container. + - ${PWD}/common/dockerfiles/nginx:/etc/common/dockerfiles/nginx:ro # Required on Linux by `side-by-side.conf.template`; Docker Desktop resolves it already. extra_hosts: - "host.docker.internal:host-gateway" From 10d9d4ee796419bb8cc187fcbeb1b57142f923a0 Mon Sep 17 00:00:00 2001 From: Eric Holscher Date: Mon, 5 Oct 2026 16:09:07 +0200 Subject: [PATCH 5/7] Docker: serve development services by hostname Only NGINX is published on the host now. It serves webpack, addons, storage (API and console) and Elasticsearch on assets., addons., storage., storage-console. and search., so the side-by-side forward covers them too and only RTDDEV_PORT_NGINX is needed. --- dockerfiles/docker-compose-search.yml | 2 - dockerfiles/docker-compose.yml | 13 +--- dockerfiles/nginx/services.conf.template | 92 ++++++++++++++++++++++++ 3 files changed, 93 insertions(+), 14 deletions(-) create mode 100644 dockerfiles/nginx/services.conf.template diff --git a/dockerfiles/docker-compose-search.yml b/dockerfiles/docker-compose-search.yml index 8657723..9523027 100644 --- a/dockerfiles/docker-compose-search.yml +++ b/dockerfiles/docker-compose-search.yml @@ -27,8 +27,6 @@ services: # Disable HTTPS on development. - xpack.security.transport.ssl.enabled=false - 'ES_JAVA_OPTS=-Xms128m -Xmx128m' - ports: - - "${RTDDEV_PORT_SEARCH:-9200}:9200" links: - web - celery diff --git a/dockerfiles/docker-compose.yml b/dockerfiles/docker-compose.yml index b11f1fd..5752b85 100644 --- a/dockerfiles/docker-compose.yml +++ b/dockerfiles/docker-compose.yml @@ -30,7 +30,7 @@ services: readthedocs: volumes: - ${PWD}/dockerfiles/nginx:/etc/nginx/templates - # Each repository symlinks `side-by-side.conf.template` from its templates to this directory, + # Each repository symlinks the templates in this directory from its own templates, # mounted at the path the relative symlink resolves to inside the container. - ${PWD}/common/dockerfiles/nginx:/etc/common/dockerfiles/nginx:ro # Required on Linux by `side-by-side.conf.template`; Docker Desktop resolves it already. @@ -75,8 +75,6 @@ services: - ${PWD}/${RTDDEV_PATH_ADDONS:-../addons}/webpack.config.js:/usr/src/app/checkouts/addons/webpack.config.js tty: true working_dir: /usr/src/app/checkouts/addons/ - ports: - - "${RTDDEV_PORT_ADDONS:-8000}:8000" command: [ "npm", "run", @@ -104,8 +102,6 @@ services: - RTD_DJANGO_DEBUG - RTD_LOGGING_SUPPRESS_TRACEBACKS - RTD_EXT_THEME_DEV_SERVER_ENABLED - - RTDDEV_PORT_WEBPACK - - RTDDEV_PORT_STORAGE - RTD_FILETREEDIFF_ALL # AWS related settings. - RTD_S3_PROVIDER @@ -149,8 +145,6 @@ services: - INIT=${INIT:-} - DOCKER_NO_RELOAD - RTD_EXT_THEME_DEV_SERVER_ENABLED - - RTDDEV_PORT_WEBPACK - - RTDDEV_PORT_STORAGE - RTD_PRODUCTION_DOMAIN - RTD_PUBLIC_DOMAIN - RUSTFS_ROOT_USER=admin @@ -196,8 +190,6 @@ services: context: ${PWD}/${RTDDEV_PATH_EXT_THEME:-../ext-theme} dockerfile: ${PWD}/common/dockerfiles/Dockerfile.webpack working_dir: /usr/src/app/checkouts/ext-theme - ports: - - "${RTDDEV_PORT_WEBPACK:-10001}:10001" volumes: - ${PWD}/${RTDDEV_PATH_EXT_THEME:-../ext-theme}/src:/usr/src/app/checkouts/ext-theme/src - ${PWD}/${RTDDEV_PATH_EXT_THEME:-../ext-theme}/readthedocsext:/usr/src/app/checkouts/ext-theme/readthedocsext @@ -455,9 +447,6 @@ services: storage: image: rustfs/rustfs:1.0.0-alpha.83 - ports: - - "${RTDDEV_PORT_STORAGE:-9000}:9000" - - "${RTDDEV_PORT_STORAGE_CONSOLE:-9001}:9001" environment: - RUSTFS_ADDRESS=:9000 - RUSTFS_ACCESS_KEY=admin diff --git a/dockerfiles/nginx/services.conf.template b/dockerfiles/nginx/services.conf.template new file mode 100644 index 0000000..cfdcbb0 --- /dev/null +++ b/dockerfiles/nginx/services.conf.template @@ -0,0 +1,92 @@ +# Development services, served by hostname instead of their own host ports, +# so both environments can run side by side (see `side-by-side.conf.template`). +# +# Upstreams are resolved per request (`resolver` + variable), +# since these containers may start after NGINX, or not at all (`--no-search`). + +map $http_upgrade $connection_upgrade { + default upgrade; + '' close; +} + +# ext-theme webpack development server, including its live reload websocket. +server { + listen 80; + server_name assets.${NGINX_WEB_SERVER_NAME}; + access_log off; + resolver 127.0.0.11 valid=10s; + + location / { + set $upstream http://webpack:10001; + proxy_pass $upstream; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $connection_upgrade; + proxy_set_header Host $http_host; + } +} + +# Addons development server. +server { + listen 80; + server_name addons.${NGINX_WEB_SERVER_NAME}; + access_log off; + resolver 127.0.0.11 valid=10s; + + location / { + set $upstream http://addons:8000; + proxy_pass $upstream; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $connection_upgrade; + proxy_set_header Host $http_host; + } +} + +# S3 API, used by presigned upload URLs. +server { + listen 80; + server_name storage.${NGINX_WEB_SERVER_NAME}; + access_log off; + resolver 127.0.0.11 valid=10s; + client_max_body_size 0; + proxy_request_buffering off; + + location / { + set $upstream http://storage:9000; + proxy_pass $upstream; + # Keep path-style buckets: RustFS could read a bucket from the hostname otherwise. + proxy_set_header Host storage:9000; + } +} + +# S3 web console. +server { + listen 80; + server_name storage-console.${NGINX_WEB_SERVER_NAME}; + access_log off; + resolver 127.0.0.11 valid=10s; + + location / { + set $upstream http://storage:9001; + proxy_pass $upstream; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $connection_upgrade; + proxy_set_header Host $http_host; + } +} + +# Elasticsearch. +server { + listen 80; + server_name search.${NGINX_WEB_SERVER_NAME}; + access_log off; + resolver 127.0.0.11 valid=10s; + + location / { + set $upstream http://search:9200; + proxy_pass $upstream; + proxy_set_header Host $http_host; + } +} From f65bd450a7bba2970ec476e572a69b52b0847c44 Mon Sep 17 00:00:00 2001 From: Eric Holscher Date: Mon, 5 Oct 2026 16:15:34 +0200 Subject: [PATCH 6/7] Docker: websockets through the side-by-side forward, console root redirect Pass the Host header to storage so SigV4 clients work, and send / on the storage console to /rustfs/console/. --- dockerfiles/nginx/services.conf.template | 9 +++++++-- dockerfiles/nginx/side-by-side.conf.template | 4 ++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/dockerfiles/nginx/services.conf.template b/dockerfiles/nginx/services.conf.template index cfdcbb0..6c60867 100644 --- a/dockerfiles/nginx/services.conf.template +++ b/dockerfiles/nginx/services.conf.template @@ -55,8 +55,8 @@ server { location / { set $upstream http://storage:9000; proxy_pass $upstream; - # Keep path-style buckets: RustFS could read a bucket from the hostname otherwise. - proxy_set_header Host storage:9000; + # S3 clients sign the Host header. + proxy_set_header Host $http_host; } } @@ -67,6 +67,11 @@ server { access_log off; resolver 127.0.0.11 valid=10s; + location = / { + return 302 /rustfs/console/; + } + + # The console calls the S3 and admin APIs on its own origin, signing the Host header. location / { set $upstream http://storage:9001; proxy_pass $upstream; diff --git a/dockerfiles/nginx/side-by-side.conf.template b/dockerfiles/nginx/side-by-side.conf.template index 8628e4d..de5aaec 100644 --- a/dockerfiles/nginx/side-by-side.conf.template +++ b/dockerfiles/nginx/side-by-side.conf.template @@ -11,6 +11,10 @@ server { location / { proxy_pass http://host.docker.internal:${NGINX_SIDE_BY_SIDE_PORT}; proxy_set_header Host $http_host; + # Websockets, like webpack's live reload. + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $http_connection; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_read_timeout 300s; } From e82f88c90a6324701435680d77b538f5bc057058 Mon Sep 17 00:00:00 2001 From: Eric Holscher Date: Mon, 5 Oct 2026 18:26:07 +0200 Subject: [PATCH 7/7] Docker: pick the NGINX host port automatically inv docker.up and docker.compose publish NGINX on port 80, unless another container already publishes it: then they use the environment's side-by-side port, so the second environment needs no configuration. --- dockerfiles/tasks.py | 45 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/dockerfiles/tasks.py b/dockerfiles/tasks.py index a94f93a..194bfd3 100644 --- a/dockerfiles/tasks.py +++ b/dockerfiles/tasks.py @@ -11,6 +11,48 @@ DOCKER_COMPOSE_OVERRIDE = "docker-compose.override.yml" DOCKER_COMPOSE_COMMAND = f"docker compose --project-directory=. -f {DOCKER_COMPOSE} -f {DOCKER_COMPOSE_OVERRIDE} -f {DOCKER_COMPOSE_SEARCH}" +# Host port for NGINX when the other environment already owns port 80. +# The other environment forwards our domains here (see `nginx/side-by-side.conf.template`). +NGINX_SIDE_BY_SIDE_PORTS = { + "community": "10080", + "commercial": "10081", +} + + +def compose_project_name(): + if os.environ.get("COMPOSE_PROJECT_NAME"): + return os.environ["COMPOSE_PROJECT_NAME"] + with open(".env") as f: + for line in f: + name, _, value = line.strip().partition("=") + if name == "COMPOSE_PROJECT_NAME": + return value + return None + + +def nginx_port(c): + """ + Host port for NGINX: 80, unless another container already publishes it. + + Run community and business at the same time without any configuration: + the second one to start gets its side-by-side port. + ``RTDDEV_PORT_NGINX`` overrides it. + """ + if os.environ.get("RTDDEV_PORT_NGINX"): + return os.environ["RTDDEV_PORT_NGINX"] + + project = compose_project_name() + side_by_side_port = NGINX_SIDE_BY_SIDE_PORTS.get(project) + if not side_by_side_port: + return "80" + + result = c.run("docker ps --filter publish=80 --format '{{.Names}}'", hide=True, warn=True) + for owner in result.stdout.split(): + if owner != f"{project}-nginx-1": + print(f"Port 80 is used by {owner}, publishing NGINX on port {side_by_side_port}.") + return side_by_side_port + return "80" + @task( help={ @@ -50,7 +92,7 @@ def build(c, cache=False): ) def compose(c, command): """Pass the command to docker compose directly.""" - c.run(f"{DOCKER_COMPOSE_COMMAND} {command}", pty=True) + c.run(f"RTDDEV_PORT_NGINX={nginx_port(c)} {DOCKER_COMPOSE_COMMAND} {command}", pty=True) @task( @@ -99,6 +141,7 @@ def up( cmd.append("INIT=t" if init else "INIT=") cmd.append("DOCKER_NO_RELOAD=t" if not reload else "DOCKER_NO_RELOAD=") cmd.append(f"RTD_LOGGING_LEVEL={log_level}") + cmd.append(f"RTDDEV_PORT_NGINX={nginx_port(c)}") cmd.append("docker compose") cmd.append("--project-directory=.")