From fd942afb4ea39ec022351a62ebaffd97673d0366 Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Fri, 18 Sep 2026 11:16:35 -0300 Subject: [PATCH 01/63] poc: replace nginx proxy with Traefik Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- .docker/docker-compose.proxy.yml | 70 +++++++++++++++++--------------- 1 file changed, 38 insertions(+), 32 deletions(-) diff --git a/.docker/docker-compose.proxy.yml b/.docker/docker-compose.proxy.yml index b2e36a2..708dfcd 100644 --- a/.docker/docker-compose.proxy.yml +++ b/.docker/docker-compose.proxy.yml @@ -1,38 +1,54 @@ services: - nginx-proxy: - image: nginxproxy/nginx-proxy:1.11.6-alpine@sha256:99376e95c8754547213d411222a2c4dca046d974e753ae2a1c9aef09b2c03364 + traefik: + image: traefik:v3.7.13 container_name: librecode-dev-proxy labels: coop.librecode.dev-proxy: "true" - working_dir: /dashboard - command: ["forego", "start", "-r"] + traefik.enable: "true" + traefik.docker.network: librecode-dev-proxy + traefik.http.routers.librecode-api.rule: Path(`/api/http/routers`) + traefik.http.routers.librecode-api.entrypoints: websecure + traefik.http.routers.librecode-api.priority: "200" + traefik.http.routers.librecode-api.tls: "true" + traefik.http.routers.librecode-api.service: api@internal + command: + - --api=true + - --providers.docker=true + - --providers.docker.exposedbydefault=false + - --providers.docker.network=librecode-dev-proxy + - --entrypoints.web.address=:80 + - --entrypoints.web.http.redirections.entrypoint.to=websecure + - --entrypoints.web.http.redirections.entrypoint.scheme=https + - --entrypoints.websecure.address=:443 ports: - "${PROXY_IP_BIND:-127.0.0.1}:80:80" - "${PROXY_IP_BIND:-127.0.0.1}:443:443" volumes: - - ${DOCKER_SOCKET:-/var/run/docker.sock}:/tmp/docker.sock:ro - - librecode-dev-proxy-vhost:/etc/nginx/vhost.d - - librecode-dev-proxy-assets:/dashboard:ro - - librecode-dev-proxy-log:/etc/nginx/log - - librecode-dev-proxy-certs:/etc/nginx/certs:ro - environment: - - ENABLE_IPV6=true - - VIRTUAL_HOST=localhost,*.localhost - - VIRTUAL_PORT=80 - - SELF_SIGNED_HOST=localhost,*.localhost + - ${DOCKER_SOCKET:-/var/run/docker.sock}:/var/run/docker.sock:ro networks: - proxy - ssl-companion: - image: sebastienheyd/self-signed-proxy-companion:1.1.0@sha256:dd698341f804aaffe2974931a185aa088561d76094e040e374847f3a64004d43 - container_name: librecode-dev-proxy-ssl-companion + dashboard: + image: nginx:1.31.5-alpine@sha256:72ba65eb42c10344912a84ff42408db7d34f2feb642204570ab8fc5ffd29f1d3 + container_name: librecode-dev-dashboard labels: - coop.librecode.dev-proxy-companion: "true" + coop.librecode.dev-proxy-dashboard: "true" + traefik.enable: "true" + traefik.docker.network: librecode-dev-proxy + traefik.http.routers.librecode-dashboard.rule: Host(`localhost`) + traefik.http.routers.librecode-dashboard.entrypoints: websecure + traefik.http.routers.librecode-dashboard.priority: "100" + traefik.http.routers.librecode-dashboard.tls: "true" + traefik.http.routers.librecode-dashboard.service: librecode-dashboard + traefik.http.routers.librecode-dashboard-catchall.rule: HostRegexp(`[a-z0-9-]+\\.localhost`) + traefik.http.routers.librecode-dashboard-catchall.entrypoints: websecure + traefik.http.routers.librecode-dashboard-catchall.priority: "1" + traefik.http.routers.librecode-dashboard-catchall.tls: "true" + traefik.http.routers.librecode-dashboard-catchall.service: librecode-dashboard + traefik.http.services.librecode-dashboard.loadbalancer.server.port: "80" volumes: - - ${DOCKER_SOCKET:-/var/run/docker.sock}:/var/run/docker.sock:ro - - librecode-dev-proxy-certs:/etc/nginx/certs:rw - environment: - - NGINX_PROXY_CONTAINER=librecode-dev-proxy + - ${PROXY_ASSET_DIR}/.docker/traefik/dashboard.html:/usr/share/nginx/html/index.html:ro + - ${PROXY_ASSET_DIR}/.docker/traefik/dashboard.conf:/etc/nginx/conf.d/default.conf:ro networks: - proxy @@ -40,13 +56,3 @@ networks: proxy: name: librecode-dev-proxy external: true - -volumes: - librecode-dev-proxy-vhost: - name: librecode-dev-proxy-vhost - librecode-dev-proxy-assets: - name: librecode-dev-proxy-assets - librecode-dev-proxy-log: - name: librecode-dev-proxy-log - librecode-dev-proxy-certs: - name: librecode-dev-proxy-certs From d1dcbe8875542ead751e807dde48e2451481195e Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Fri, 18 Sep 2026 11:17:42 -0300 Subject: [PATCH 02/63] poc: pin Traefik image digest Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- .docker/docker-compose.proxy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.docker/docker-compose.proxy.yml b/.docker/docker-compose.proxy.yml index 708dfcd..dd4cfa9 100644 --- a/.docker/docker-compose.proxy.yml +++ b/.docker/docker-compose.proxy.yml @@ -1,6 +1,6 @@ services: traefik: - image: traefik:v3.7.13 + image: traefik:v3.7.13@sha256:f86a2cab1b5c649070c49f883c743dd32d8485a56e3368c5f93b9e91f1e91259 container_name: librecode-dev-proxy labels: coop.librecode.dev-proxy: "true" From ddd5e787ff67ada0fabf96a65ce744f702a971b9 Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Fri, 18 Sep 2026 11:18:14 -0300 Subject: [PATCH 03/63] poc: route development services with Traefik labels Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- docker-compose.yml | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/docker-compose.yml b/docker-compose.yml index b892af8..13aea8c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -63,6 +63,13 @@ services: - VIRTUAL_HOST=${COMPOSE_PROJECT_NAME}.localhost - VIRTUAL_PORT=80 - SELF_SIGNED_HOST=${COMPOSE_PROJECT_NAME}.localhost + labels: + - "traefik.enable=true" + - "traefik.docker.network=librecode-dev-proxy" + - "traefik.http.routers.librecode-${COMPOSE_PROJECT_NAME}--nextcloud.rule=Host(\`${COMPOSE_PROJECT_NAME}.localhost\`)" + - "traefik.http.routers.librecode-${COMPOSE_PROJECT_NAME}--nextcloud.entrypoints=websecure" + - "traefik.http.routers.librecode-${COMPOSE_PROJECT_NAME}--nextcloud.tls=true" + - "traefik.http.services.librecode-${COMPOSE_PROJECT_NAME}--nextcloud.loadbalancer.server.port=80" proxy-coordinator: image: docker:29.8.0-cli@sha256:eccaacfeed644c7de222ff047483568cb988dde95476fbaaf10ea2d04921bb66 entrypoint: ["/bin/sh", "/usr/local/lib/librecode/proxy-coordinator.sh"] @@ -89,6 +96,13 @@ services: - VIRTUAL_HOST=${COMPOSE_PROJECT_NAME}-mailpit.localhost - VIRTUAL_PORT=8025 - SELF_SIGNED_HOST=${COMPOSE_PROJECT_NAME}-mailpit.localhost + labels: + - "traefik.enable=true" + - "traefik.docker.network=librecode-dev-proxy" + - "traefik.http.routers.librecode-${COMPOSE_PROJECT_NAME}--mailpit.rule=Host(\`${COMPOSE_PROJECT_NAME}-mailpit.localhost\`)" + - "traefik.http.routers.librecode-${COMPOSE_PROJECT_NAME}--mailpit.entrypoints=websecure" + - "traefik.http.routers.librecode-${COMPOSE_PROJECT_NAME}--mailpit.tls=true" + - "traefik.http.services.librecode-${COMPOSE_PROJECT_NAME}--mailpit.loadbalancer.server.port=8025" redis: image: redis:8.8.2@sha256:37227fff5638322f4ebea25d6d0dc3ee50848604e82b81426f11507b3ec7d2cc eurooffice: @@ -108,6 +122,13 @@ services: - VIRTUAL_HOST=${COMPOSE_PROJECT_NAME}-eurooffice.localhost - VIRTUAL_PORT=80 - SELF_SIGNED_HOST=${COMPOSE_PROJECT_NAME}-eurooffice.localhost + labels: + - "traefik.enable=true" + - "traefik.docker.network=librecode-dev-proxy" + - "traefik.http.routers.librecode-${COMPOSE_PROJECT_NAME}--eurooffice.rule=Host(\`${COMPOSE_PROJECT_NAME}-eurooffice.localhost\`)" + - "traefik.http.routers.librecode-${COMPOSE_PROJECT_NAME}--eurooffice.entrypoints=websecure" + - "traefik.http.routers.librecode-${COMPOSE_PROJECT_NAME}--eurooffice.tls=true" + - "traefik.http.services.librecode-${COMPOSE_PROJECT_NAME}--eurooffice.loadbalancer.server.port=80" extra_hosts: - host.docker.internal:host-gateway playwright: @@ -125,6 +146,13 @@ services: - VIRTUAL_HOST=${COMPOSE_PROJECT_NAME}-playwright.localhost - VIRTUAL_PORT=9323 - SELF_SIGNED_HOST=${COMPOSE_PROJECT_NAME}-playwright.localhost + labels: + - "traefik.enable=true" + - "traefik.docker.network=librecode-dev-proxy" + - "traefik.http.routers.librecode-${COMPOSE_PROJECT_NAME}--playwright.rule=Host(\`${COMPOSE_PROJECT_NAME}-playwright.localhost\`)" + - "traefik.http.routers.librecode-${COMPOSE_PROJECT_NAME}--playwright.entrypoints=websecure" + - "traefik.http.routers.librecode-${COMPOSE_PROJECT_NAME}--playwright.tls=true" + - "traefik.http.services.librecode-${COMPOSE_PROJECT_NAME}--playwright.loadbalancer.server.port=9323" command: sleep infinity depends_on: - nextcloud @@ -143,6 +171,13 @@ services: - VIRTUAL_HOST=${COMPOSE_PROJECT_NAME}-signal.localhost - VIRTUAL_PORT=8080 - SELF_SIGNED_HOST=${COMPOSE_PROJECT_NAME}-signal.localhost + labels: + - "traefik.enable=true" + - "traefik.docker.network=librecode-dev-proxy" + - "traefik.http.routers.librecode-${COMPOSE_PROJECT_NAME}--signal-gateway.rule=Host(\`${COMPOSE_PROJECT_NAME}-signal.localhost\`)" + - "traefik.http.routers.librecode-${COMPOSE_PROJECT_NAME}--signal-gateway.entrypoints=websecure" + - "traefik.http.routers.librecode-${COMPOSE_PROJECT_NAME}--signal-gateway.tls=true" + - "traefik.http.services.librecode-${COMPOSE_PROJECT_NAME}--signal-gateway.loadbalancer.server.port=8080" whatsapp.web: image: aldinokemal2104/go-whatsapp-web-multidevice profiles: From 413a92084f3df9fb681921a287c5d3cfbc248594 Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Fri, 18 Sep 2026 11:18:24 -0300 Subject: [PATCH 04/63] poc: preserve friendly unknown-host routing Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- .docker/docker-compose.proxy.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.docker/docker-compose.proxy.yml b/.docker/docker-compose.proxy.yml index dd4cfa9..05aea33 100644 --- a/.docker/docker-compose.proxy.yml +++ b/.docker/docker-compose.proxy.yml @@ -45,6 +45,8 @@ services: traefik.http.routers.librecode-dashboard-catchall.priority: "1" traefik.http.routers.librecode-dashboard-catchall.tls: "true" traefik.http.routers.librecode-dashboard-catchall.service: librecode-dashboard + traefik.http.routers.librecode-dashboard-catchall.middlewares: librecode-not-found + traefik.http.middlewares.librecode-not-found.replacepath.path: /__librecode_not_found__ traefik.http.services.librecode-dashboard.loadbalancer.server.port: "80" volumes: - ${PROXY_ASSET_DIR}/.docker/traefik/dashboard.html:/usr/share/nginx/html/index.html:ro From 9114a71eaf18dd44085051b7bbf318479b851654 Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Fri, 18 Sep 2026 11:19:04 -0300 Subject: [PATCH 05/63] poc: add Traefik-backed development dashboard Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- .docker/traefik/dashboard.html | 265 +++++++++++++++++++++++++++++++++ 1 file changed, 265 insertions(+) create mode 100644 .docker/traefik/dashboard.html diff --git a/.docker/traefik/dashboard.html b/.docker/traefik/dashboard.html new file mode 100644 index 0000000..38f99f0 --- /dev/null +++ b/.docker/traefik/dashboard.html @@ -0,0 +1,265 @@ + + + + + + LibreCode Nextcloud Development Environment + + + +
+

πŸ’™ LibreCode Nextcloud Development Environment

+ + +

Active environments

+

Loading active development routes…

+ + + +

This page is generated from the currently active development routes.

+ + +
+ + + + From d2791242dae16ffaf38b522df3173840aad712bd Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Fri, 18 Sep 2026 11:19:07 -0300 Subject: [PATCH 06/63] poc: add dashboard fallback configuration Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- .docker/traefik/dashboard.conf | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 .docker/traefik/dashboard.conf diff --git a/.docker/traefik/dashboard.conf b/.docker/traefik/dashboard.conf new file mode 100644 index 0000000..7680fc7 --- /dev/null +++ b/.docker/traefik/dashboard.conf @@ -0,0 +1,24 @@ +server { + listen 80; + server_name _; + + root /usr/share/nginx/html; + index index.html; + + location = /__librecode_not_found__ { + return 404; + } + + error_page 404 /index.html; + + location = /index.html { + } + + location = /runtime.json { + try_files $uri =404; + } + + location / { + try_files $uri /index.html; + } +} From eb089773c774c966bfe92ba697928bf060ae6c34 Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Fri, 18 Sep 2026 11:19:27 -0300 Subject: [PATCH 07/63] poc: reuse project directory for dashboard assets Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- .docker/docker-compose.proxy.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.docker/docker-compose.proxy.yml b/.docker/docker-compose.proxy.yml index 05aea33..0729574 100644 --- a/.docker/docker-compose.proxy.yml +++ b/.docker/docker-compose.proxy.yml @@ -49,8 +49,8 @@ services: traefik.http.middlewares.librecode-not-found.replacepath.path: /__librecode_not_found__ traefik.http.services.librecode-dashboard.loadbalancer.server.port: "80" volumes: - - ${PROXY_ASSET_DIR}/.docker/traefik/dashboard.html:/usr/share/nginx/html/index.html:ro - - ${PROXY_ASSET_DIR}/.docker/traefik/dashboard.conf:/etc/nginx/conf.d/default.conf:ro + - ${PROJECT_DIR}/.docker/traefik/dashboard.html:/usr/share/nginx/html/index.html:ro + - ${PROJECT_DIR}/.docker/traefik/dashboard.conf:/etc/nginx/conf.d/default.conf:ro networks: - proxy From 48aa19aea1a3953532893d739282a667801874c8 Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Fri, 18 Sep 2026 11:19:37 -0300 Subject: [PATCH 08/63] poc: remove nginx asset installation from coordinator Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- .docker/scripts/proxy-coordinator.sh | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.docker/scripts/proxy-coordinator.sh b/.docker/scripts/proxy-coordinator.sh index dad5f7c..f345f37 100755 --- a/.docker/scripts/proxy-coordinator.sh +++ b/.docker/scripts/proxy-coordinator.sh @@ -18,8 +18,6 @@ export COORDINATOR_CONTAINER PROJECT_NAME # analyze the complete dependency graph without file-wide suppressions. # shellcheck source=.docker/scripts/proxy/infrastructure.sh . "$proxy_lib_dir/infrastructure.sh" -# shellcheck source=.docker/scripts/proxy/assets.sh -. "$proxy_lib_dir/assets.sh" # shellcheck source=.docker/scripts/proxy/diagnostics.sh . "$proxy_lib_dir/diagnostics.sh" # shellcheck source=.docker/scripts/proxy/services.sh @@ -85,8 +83,6 @@ wait_for_shutdown() { run() { validate_environment ensure_proxy_network - install_proxy_assets - proxy_state="$(ensure_proxy_running)" install_runtime_diagnostics From 788314734ebf40feb2e5e4d071bf19e6eb79fadd Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Fri, 18 Sep 2026 11:19:48 -0300 Subject: [PATCH 09/63] poc: publish runtime diagnostics through Traefik dashboard Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- .docker/scripts/proxy/diagnostics.sh | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/.docker/scripts/proxy/diagnostics.sh b/.docker/scripts/proxy/diagnostics.sh index aaf79ea..1710e3c 100644 --- a/.docker/scripts/proxy/diagnostics.sh +++ b/.docker/scripts/proxy/diagnostics.sh @@ -79,10 +79,13 @@ runtime_diagnostics_json() { } install_runtime_diagnostics() { - proxy_container="$(compatible_proxy_container)" - [ -n "$proxy_container" ] || return 1 + dashboard_container="${PROXY_DASHBOARD_CONTAINER:-librecode-dev-dashboard}" + + if ! Docker inspect "$dashboard_container" >/dev/null 2>&1; then + return 1 + fi runtime_diagnostics_json | - Docker exec -i "$proxy_container" \ + Docker exec -i "$dashboard_container" \ sh -c 'cat > /usr/share/nginx/html/runtime.json' } From cd27ae8275ec104b0ed17fd00b3e3ab9568266d3 Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Fri, 18 Sep 2026 11:20:09 -0300 Subject: [PATCH 10/63] test: route proxy fixture through Traefik Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- tests/proxy/fixtures/compose.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/proxy/fixtures/compose.yml b/tests/proxy/fixtures/compose.yml index d1304ea..69a28f0 100644 --- a/tests/proxy/fixtures/compose.yml +++ b/tests/proxy/fixtures/compose.yml @@ -5,6 +5,13 @@ services: - VIRTUAL_HOST=${COMPOSE_PROJECT_NAME}.localhost - VIRTUAL_PORT=80 - SELF_SIGNED_HOST=${COMPOSE_PROJECT_NAME}.localhost + labels: + - "traefik.enable=true" + - "traefik.docker.network=librecode-dev-proxy" + - "traefik.http.routers.librecode-${COMPOSE_PROJECT_NAME}--nextcloud.rule=Host(\`${COMPOSE_PROJECT_NAME}.localhost\`)" + - "traefik.http.routers.librecode-${COMPOSE_PROJECT_NAME}--nextcloud.entrypoints=websecure" + - "traefik.http.routers.librecode-${COMPOSE_PROJECT_NAME}--nextcloud.tls=true" + - "traefik.http.services.librecode-${COMPOSE_PROJECT_NAME}--nextcloud.loadbalancer.server.port=80" proxy-coordinator: image: docker:29.8.0-cli@sha256:eccaacfeed644c7de222ff047483568cb988dde95476fbaaf10ea2d04921bb66 From c8b2cb3a7e9feb13d7d424494f4f572db7885a36 Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Fri, 18 Sep 2026 11:20:29 -0300 Subject: [PATCH 11/63] test: validate Traefik proxy lifecycle and route API Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- tests/proxy/integration.bats | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/tests/proxy/integration.bats b/tests/proxy/integration.bats index 6791b12..7d3652e 100644 --- a/tests/proxy/integration.bats +++ b/tests/proxy/integration.bats @@ -80,7 +80,7 @@ wait_for_https_status() { compose_test proxytesta up --detach wait_for_running librecode-dev-proxy - wait_for_running librecode-dev-proxy-ssl-companion + wait_for_running librecode-dev-dashboard http_host_ip="$(docker inspect --format '{{(index (index .NetworkSettings.Ports "80/tcp") 0).HostIp}}' librecode-dev-proxy)" https_host_ip="$(docker inspect --format '{{(index (index .NetworkSettings.Ports "443/tcp") 0).HostIp}}' librecode-dev-proxy)" @@ -95,6 +95,9 @@ wait_for_https_status() { grep -q 'Report an issue' "$BODY" grep -q 'Star on GitHub' "$BODY" + wait_for_https_path_status localhost /api/http/routers 200 + grep -q 'librecode-proxytesta--nextcloud' "$BODY" + wait_for_https_path_status localhost /runtime.json 200 grep -q '"docker":"' "$BODY" grep -q '"runc":"' "$BODY" @@ -112,7 +115,7 @@ wait_for_https_status() { compose_test proxytesta stop wait_for_absent librecode-dev-proxy - wait_for_absent librecode-dev-proxy-ssl-companion + wait_for_absent librecode-dev-dashboard } @test "Ctrl+C on attached compose stops the last shared proxy promptly" { @@ -126,7 +129,7 @@ wait_for_https_status() { compose_pid=$! wait_for_running librecode-dev-proxy - wait_for_running librecode-dev-proxy-ssl-companion + wait_for_running librecode-dev-dashboard wait_for_https_status proxytesta.localhost 200 started_at="$(date +%s)" @@ -135,7 +138,7 @@ wait_for_https_status() { finished_at="$(date +%s)" wait_for_absent librecode-dev-proxy - wait_for_absent librecode-dev-proxy-ssl-companion + wait_for_absent librecode-dev-dashboard elapsed=$((finished_at - started_at)) [ "$elapsed" -lt 10 ] @@ -157,5 +160,5 @@ wait_for_https_status() { compose_test proxytestb stop wait_for_absent librecode-dev-proxy - wait_for_absent librecode-dev-proxy-ssl-companion + wait_for_absent librecode-dev-dashboard } From e23cbd12527795424d55b993ee460811c5a443d5 Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Fri, 18 Sep 2026 11:20:47 -0300 Subject: [PATCH 12/63] test: update proxy policies for Traefik POC Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- tests/proxy/compose-policy.bats | 274 +++++++++++++++++++++++++++++++- 1 file changed, 269 insertions(+), 5 deletions(-) diff --git a/tests/proxy/compose-policy.bats b/tests/proxy/compose-policy.bats index a5d151a..3f4a4eb 100644 --- a/tests/proxy/compose-policy.bats +++ b/tests/proxy/compose-policy.bats @@ -10,13 +10,277 @@ setup() { grep -Eq '^[[:space:]]+image: redis:[0-9]+\.[0-9]+\.[0-9]+@sha256:[0-9a-f]{64}$' "$REPO_ROOT/docker-compose.yml" grep -Eq '^[[:space:]]+image: mysql:8\.4@sha256:[0-9a-f]{64}$' "$REPO_ROOT/.docker/database-services.yml" grep -Eq '^[[:space:]]+image: postgres:13-alpine@sha256:[0-9a-f]{64}$' "$REPO_ROOT/.docker/database-services.yml" - grep -Eq '^[[:space:]]+image: nginxproxy/nginx-proxy:[0-9]+\.[0-9]+\.[0-9]+-alpine@sha256:[0-9a-f]{64}$' "$REPO_ROOT/.docker/docker-compose.proxy.yml" - grep -Eq '^[[:space:]]+image: sebastienheyd/self-signed-proxy-companion:[0-9]+\.[0-9]+\.[0-9]+@sha256:[0-9a-f]{64}$' "$REPO_ROOT/.docker/docker-compose.proxy.yml" + grep -Eq '^[[:space:]]+image: traefik:v[0-9]+\.[0-9]+\.[0-9]+@sha256:[0-9a-f]{64} +@test "proxy integration fixture images are pinned by tag and digest" { + grep -Eq '^[[:space:]]+image: nginx:[0-9]+\.[0-9]+\.[0-9]+-alpine@sha256:[0-9a-f]{64}$' "$REPO_ROOT/tests/proxy/fixtures/compose.yml" + grep -Eq '^[[:space:]]+image: docker:[0-9]+\.[0-9]+\.[0-9]+-cli@sha256:[0-9a-f]{64}$' "$REPO_ROOT/tests/proxy/fixtures/compose.yml" +} + +@test "compose files do not keep shutdown workarounds from runtime debugging" { + for file in "$REPO_ROOT/docker-compose.yml" "$REPO_ROOT/tests/proxy/fixtures/compose.yml"; do + ! grep -Eq '^[[:space:]]+(stop_signal|stop_grace_period|init):' "$file" + done +} + +@test "network bind settings are scoped per infrastructure service" { + proxy_compose="$REPO_ROOT/.docker/docker-compose.proxy.yml" + database_compose="$REPO_ROOT/.docker/database-services.yml" + + grep -Fq '"${PROXY_IP_BIND:-127.0.0.1}:80:80"' "$proxy_compose" + grep -Fq '"${PROXY_IP_BIND:-127.0.0.1}:443:443"' "$proxy_compose" + grep -Fq 'host_ip: ${MYSQL_IP_BIND:-127.0.0.1}' "$database_compose" + grep -Fq 'host_ip: ${POSTGRES_IP_BIND:-127.0.0.1}' "$database_compose" + ! grep -Rq '${IP_BIND' "$proxy_compose" "$database_compose" "$REPO_ROOT/docker-compose.yml" + + run env PROXY_IP_BIND=0.0.0.0 docker compose --file "$proxy_compose" config + [ "$status" -eq 0 ] + printf '%s\n' "$output" | grep -q 'host_ip: 0.0.0.0' + + run env MYSQL_IP_BIND=0.0.0.0 docker compose --file "$database_compose" config + [ "$status" -eq 0 ] + printf '%s\n' "$output" | grep -B5 -A5 'target: 3306' | grep -q 'host_ip: 0.0.0.0' + printf '%s\n' "$output" | grep -B5 -A5 'target: 5432' | grep -q 'host_ip: 127.0.0.1' + + run env POSTGRES_IP_BIND=0.0.0.0 docker compose --file "$database_compose" config + [ "$status" -eq 0 ] + printf '%s\n' "$output" | grep -B5 -A5 'target: 3306' | grep -q 'host_ip: 127.0.0.1' + printf '%s\n' "$output" | grep -B5 -A5 'target: 5432' | grep -q 'host_ip: 0.0.0.0' +} + +@test "docker socket mounts stay limited to proxy infrastructure" { + main_socket_mounts="$(grep -Ec '^[[:space:]]+- .*DOCKER_SOCKET.*:/var/run/docker\.sock$' "$REPO_ROOT/docker-compose.yml")" + [ "$main_socket_mounts" -eq 1 ] + + proxy_socket_mounts="$(grep -Ec '^[[:space:]]+- .*DOCKER_SOCKET.*:/.*docker\.sock:ro +@test "GitHub Actions are pinned to immutable commit SHAs" { + while IFS= read -r workflow; do + while IFS= read -r uses_line; do + ref="${uses_line#*@}" + ref="${ref%% *}" + [[ "$ref" =~ ^[0-9a-f]{40}$ ]] || { + printf 'mutable action reference in %s: %s\n' "$workflow" "$uses_line" >&2 + return 1 + } + done < <(grep -E '^[[:space:]]*-?[[:space:]]*uses:[[:space:]]+[^./][^[:space:]]+@' "$workflow" || true) + done < <(find "$REPO_ROOT/.github/workflows" -type f \( -name '*.yml' -o -name '*.yaml' \)) +} + "$REPO_ROOT/.docker/docker-compose.proxy.yml" + grep -Eq '^[[:space:]]+image: nginx:[0-9]+\.[0-9]+\.[0-9]+-alpine@sha256:[0-9a-f]{64} +@test "proxy integration fixture images are pinned by tag and digest" { + grep -Eq '^[[:space:]]+image: nginx:[0-9]+\.[0-9]+\.[0-9]+-alpine@sha256:[0-9a-f]{64}$' "$REPO_ROOT/tests/proxy/fixtures/compose.yml" + grep -Eq '^[[:space:]]+image: docker:[0-9]+\.[0-9]+\.[0-9]+-cli@sha256:[0-9a-f]{64}$' "$REPO_ROOT/tests/proxy/fixtures/compose.yml" +} + +@test "compose files do not keep shutdown workarounds from runtime debugging" { + for file in "$REPO_ROOT/docker-compose.yml" "$REPO_ROOT/tests/proxy/fixtures/compose.yml"; do + ! grep -Eq '^[[:space:]]+(stop_signal|stop_grace_period|init):' "$file" + done +} + +@test "network bind settings are scoped per infrastructure service" { + proxy_compose="$REPO_ROOT/.docker/docker-compose.proxy.yml" + database_compose="$REPO_ROOT/.docker/database-services.yml" + + grep -Fq '"${PROXY_IP_BIND:-127.0.0.1}:80:80"' "$proxy_compose" + grep -Fq '"${PROXY_IP_BIND:-127.0.0.1}:443:443"' "$proxy_compose" + grep -Fq 'host_ip: ${MYSQL_IP_BIND:-127.0.0.1}' "$database_compose" + grep -Fq 'host_ip: ${POSTGRES_IP_BIND:-127.0.0.1}' "$database_compose" + ! grep -Rq '${IP_BIND' "$proxy_compose" "$database_compose" "$REPO_ROOT/docker-compose.yml" + + run env PROXY_IP_BIND=0.0.0.0 docker compose --file "$proxy_compose" config + [ "$status" -eq 0 ] + printf '%s\n' "$output" | grep -q 'host_ip: 0.0.0.0' + + run env MYSQL_IP_BIND=0.0.0.0 docker compose --file "$database_compose" config + [ "$status" -eq 0 ] + printf '%s\n' "$output" | grep -B5 -A5 'target: 3306' | grep -q 'host_ip: 0.0.0.0' + printf '%s\n' "$output" | grep -B5 -A5 'target: 5432' | grep -q 'host_ip: 127.0.0.1' + + run env POSTGRES_IP_BIND=0.0.0.0 docker compose --file "$database_compose" config + [ "$status" -eq 0 ] + printf '%s\n' "$output" | grep -B5 -A5 'target: 3306' | grep -q 'host_ip: 127.0.0.1' + printf '%s\n' "$output" | grep -B5 -A5 'target: 5432' | grep -q 'host_ip: 0.0.0.0' +} + +@test "docker socket mounts stay limited to proxy infrastructure" { + main_socket_mounts="$(grep -Ec '^[[:space:]]+- .*DOCKER_SOCKET.*:/var/run/docker\.sock$' "$REPO_ROOT/docker-compose.yml")" + [ "$main_socket_mounts" -eq 1 ] + + proxy_socket_mounts="$(grep -Ec '^[[:space:]]+- .*DOCKER_SOCKET.*:/.*docker\.sock:ro$' "$REPO_ROOT/.docker/docker-compose.proxy.yml")" + [ "$proxy_socket_mounts" -eq 2 ] +} + +@test "dashboard does not use innerHTML for Docker metadata" { + ! grep -q 'innerHTML' "$REPO_ROOT/.docker/nginx-proxy/dashboard.tmpl" + grep -q 'textContent = currentProject' "$REPO_ROOT/.docker/nginx-proxy/dashboard.tmpl" + grep -q 'textContent = link.href' "$REPO_ROOT/.docker/nginx-proxy/dashboard.tmpl" +} + +@test "GitHub Actions are pinned to immutable commit SHAs" { + while IFS= read -r workflow; do + while IFS= read -r uses_line; do + ref="${uses_line#*@}" + ref="${ref%% *}" + [[ "$ref" =~ ^[0-9a-f]{40}$ ]] || { + printf 'mutable action reference in %s: %s\n' "$workflow" "$uses_line" >&2 + return 1 + } + done < <(grep -E '^[[:space:]]*-?[[:space:]]*uses:[[:space:]]+[^./][^[:space:]]+@' "$workflow" || true) + done < <(find "$REPO_ROOT/.github/workflows" -type f \( -name '*.yml' -o -name '*.yaml' \)) +} + "$REPO_ROOT/.docker/docker-compose.proxy.yml" +} + +@test "proxy integration fixture images are pinned by tag and digest" { + grep -Eq '^[[:space:]]+image: nginx:[0-9]+\.[0-9]+\.[0-9]+-alpine@sha256:[0-9a-f]{64}$' "$REPO_ROOT/tests/proxy/fixtures/compose.yml" + grep -Eq '^[[:space:]]+image: docker:[0-9]+\.[0-9]+\.[0-9]+-cli@sha256:[0-9a-f]{64}$' "$REPO_ROOT/tests/proxy/fixtures/compose.yml" +} + +@test "compose files do not keep shutdown workarounds from runtime debugging" { + for file in "$REPO_ROOT/docker-compose.yml" "$REPO_ROOT/tests/proxy/fixtures/compose.yml"; do + ! grep -Eq '^[[:space:]]+(stop_signal|stop_grace_period|init):' "$file" + done +} + +@test "network bind settings are scoped per infrastructure service" { + proxy_compose="$REPO_ROOT/.docker/docker-compose.proxy.yml" + database_compose="$REPO_ROOT/.docker/database-services.yml" + + grep -Fq '"${PROXY_IP_BIND:-127.0.0.1}:80:80"' "$proxy_compose" + grep -Fq '"${PROXY_IP_BIND:-127.0.0.1}:443:443"' "$proxy_compose" + grep -Fq 'host_ip: ${MYSQL_IP_BIND:-127.0.0.1}' "$database_compose" + grep -Fq 'host_ip: ${POSTGRES_IP_BIND:-127.0.0.1}' "$database_compose" + ! grep -Rq '${IP_BIND' "$proxy_compose" "$database_compose" "$REPO_ROOT/docker-compose.yml" + + run env PROXY_IP_BIND=0.0.0.0 docker compose --file "$proxy_compose" config + [ "$status" -eq 0 ] + printf '%s\n' "$output" | grep -q 'host_ip: 0.0.0.0' + + run env MYSQL_IP_BIND=0.0.0.0 docker compose --file "$database_compose" config + [ "$status" -eq 0 ] + printf '%s\n' "$output" | grep -B5 -A5 'target: 3306' | grep -q 'host_ip: 0.0.0.0' + printf '%s\n' "$output" | grep -B5 -A5 'target: 5432' | grep -q 'host_ip: 127.0.0.1' + + run env POSTGRES_IP_BIND=0.0.0.0 docker compose --file "$database_compose" config + [ "$status" -eq 0 ] + printf '%s\n' "$output" | grep -B5 -A5 'target: 3306' | grep -q 'host_ip: 127.0.0.1' + printf '%s\n' "$output" | grep -B5 -A5 'target: 5432' | grep -q 'host_ip: 0.0.0.0' +} + +@test "docker socket mounts stay limited to proxy infrastructure" { + main_socket_mounts="$(grep -Ec '^[[:space:]]+- .*DOCKER_SOCKET.*:/var/run/docker\.sock$' "$REPO_ROOT/docker-compose.yml")" + [ "$main_socket_mounts" -eq 1 ] + + proxy_socket_mounts="$(grep -Ec '^[[:space:]]+- .*DOCKER_SOCKET.*:/.*docker\.sock:ro$' "$REPO_ROOT/.docker/docker-compose.proxy.yml")" + [ "$proxy_socket_mounts" -eq 2 ] +} + +@test "dashboard does not use innerHTML for Docker metadata" { + ! grep -q 'innerHTML' "$REPO_ROOT/.docker/nginx-proxy/dashboard.tmpl" + grep -q 'textContent = currentProject' "$REPO_ROOT/.docker/nginx-proxy/dashboard.tmpl" + grep -q 'textContent = link.href' "$REPO_ROOT/.docker/nginx-proxy/dashboard.tmpl" +} + +@test "GitHub Actions are pinned to immutable commit SHAs" { + while IFS= read -r workflow; do + while IFS= read -r uses_line; do + ref="${uses_line#*@}" + ref="${ref%% *}" + [[ "$ref" =~ ^[0-9a-f]{40}$ ]] || { + printf 'mutable action reference in %s: %s\n' "$workflow" "$uses_line" >&2 + return 1 + } + done < <(grep -E '^[[:space:]]*-?[[:space:]]*uses:[[:space:]]+[^./][^[:space:]]+@' "$workflow" || true) + done < <(find "$REPO_ROOT/.github/workflows" -type f \( -name '*.yml' -o -name '*.yaml' \)) +} + "$REPO_ROOT/.docker/docker-compose.proxy.yml")" + [ "$proxy_socket_mounts" -eq 1 ] +} + +@test "dashboard does not use innerHTML for Traefik route metadata" { + dashboard="$REPO_ROOT/.docker/traefik/dashboard.html" + ! grep -q 'innerHTML' "$dashboard" + grep -q 'heading.textContent = currentProject' "$dashboard" + grep -q 'link.textContent = link.href' "$dashboard" } -@test "proxy helper reuses the coordinator image instead of duplicating its version" { - grep -q 'container_image "${COORDINATOR_CONTAINER:-}"' "$REPO_ROOT/.docker/scripts/proxy/assets.sh" - ! grep -Eq 'docker:[0-9]+\.[0-9]+\.[0-9]+-cli@sha256:' "$REPO_ROOT/.docker/scripts/proxy/assets.sh" +@test "GitHub Actions are pinned to immutable commit SHAs" { + while IFS= read -r workflow; do + while IFS= read -r uses_line; do + ref="${uses_line#*@}" + ref="${ref%% *}" + [[ "$ref" =~ ^[0-9a-f]{40}$ ]] || { + printf 'mutable action reference in %s: %s\n' "$workflow" "$uses_line" >&2 + return 1 + } + done < <(grep -E '^[[:space:]]*-?[[:space:]]*uses:[[:space:]]+[^./][^[:space:]]+@' "$workflow" || true) + done < <(find "$REPO_ROOT/.github/workflows" -type f \( -name '*.yml' -o -name '*.yaml' \)) +} + "$REPO_ROOT/.docker/docker-compose.proxy.yml" + grep -Eq '^[[:space:]]+image: nginx:[0-9]+\.[0-9]+\.[0-9]+-alpine@sha256:[0-9a-f]{64} +@test "proxy integration fixture images are pinned by tag and digest" { + grep -Eq '^[[:space:]]+image: nginx:[0-9]+\.[0-9]+\.[0-9]+-alpine@sha256:[0-9a-f]{64}$' "$REPO_ROOT/tests/proxy/fixtures/compose.yml" + grep -Eq '^[[:space:]]+image: docker:[0-9]+\.[0-9]+\.[0-9]+-cli@sha256:[0-9a-f]{64}$' "$REPO_ROOT/tests/proxy/fixtures/compose.yml" +} + +@test "compose files do not keep shutdown workarounds from runtime debugging" { + for file in "$REPO_ROOT/docker-compose.yml" "$REPO_ROOT/tests/proxy/fixtures/compose.yml"; do + ! grep -Eq '^[[:space:]]+(stop_signal|stop_grace_period|init):' "$file" + done +} + +@test "network bind settings are scoped per infrastructure service" { + proxy_compose="$REPO_ROOT/.docker/docker-compose.proxy.yml" + database_compose="$REPO_ROOT/.docker/database-services.yml" + + grep -Fq '"${PROXY_IP_BIND:-127.0.0.1}:80:80"' "$proxy_compose" + grep -Fq '"${PROXY_IP_BIND:-127.0.0.1}:443:443"' "$proxy_compose" + grep -Fq 'host_ip: ${MYSQL_IP_BIND:-127.0.0.1}' "$database_compose" + grep -Fq 'host_ip: ${POSTGRES_IP_BIND:-127.0.0.1}' "$database_compose" + ! grep -Rq '${IP_BIND' "$proxy_compose" "$database_compose" "$REPO_ROOT/docker-compose.yml" + + run env PROXY_IP_BIND=0.0.0.0 docker compose --file "$proxy_compose" config + [ "$status" -eq 0 ] + printf '%s\n' "$output" | grep -q 'host_ip: 0.0.0.0' + + run env MYSQL_IP_BIND=0.0.0.0 docker compose --file "$database_compose" config + [ "$status" -eq 0 ] + printf '%s\n' "$output" | grep -B5 -A5 'target: 3306' | grep -q 'host_ip: 0.0.0.0' + printf '%s\n' "$output" | grep -B5 -A5 'target: 5432' | grep -q 'host_ip: 127.0.0.1' + + run env POSTGRES_IP_BIND=0.0.0.0 docker compose --file "$database_compose" config + [ "$status" -eq 0 ] + printf '%s\n' "$output" | grep -B5 -A5 'target: 3306' | grep -q 'host_ip: 127.0.0.1' + printf '%s\n' "$output" | grep -B5 -A5 'target: 5432' | grep -q 'host_ip: 0.0.0.0' +} + +@test "docker socket mounts stay limited to proxy infrastructure" { + main_socket_mounts="$(grep -Ec '^[[:space:]]+- .*DOCKER_SOCKET.*:/var/run/docker\.sock$' "$REPO_ROOT/docker-compose.yml")" + [ "$main_socket_mounts" -eq 1 ] + + proxy_socket_mounts="$(grep -Ec '^[[:space:]]+- .*DOCKER_SOCKET.*:/.*docker\.sock:ro$' "$REPO_ROOT/.docker/docker-compose.proxy.yml")" + [ "$proxy_socket_mounts" -eq 2 ] +} + +@test "dashboard does not use innerHTML for Docker metadata" { + ! grep -q 'innerHTML' "$REPO_ROOT/.docker/nginx-proxy/dashboard.tmpl" + grep -q 'textContent = currentProject' "$REPO_ROOT/.docker/nginx-proxy/dashboard.tmpl" + grep -q 'textContent = link.href' "$REPO_ROOT/.docker/nginx-proxy/dashboard.tmpl" +} + +@test "GitHub Actions are pinned to immutable commit SHAs" { + while IFS= read -r workflow; do + while IFS= read -r uses_line; do + ref="${uses_line#*@}" + ref="${ref%% *}" + [[ "$ref" =~ ^[0-9a-f]{40}$ ]] || { + printf 'mutable action reference in %s: %s\n' "$workflow" "$uses_line" >&2 + return 1 + } + done < <(grep -E '^[[:space:]]*-?[[:space:]]*uses:[[:space:]]+[^./][^[:space:]]+@' "$workflow" || true) + done < <(find "$REPO_ROOT/.github/workflows" -type f \( -name '*.yml' -o -name '*.yaml' \)) +} + "$REPO_ROOT/.docker/docker-compose.proxy.yml" } @test "proxy integration fixture images are pinned by tag and digest" { From c6862e647d8b43f43054913358ed4aef9a6e1849 Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Fri, 18 Sep 2026 11:21:09 -0300 Subject: [PATCH 13/63] poc: remove obsolete nginx proxy asset .docker/scripts/proxy/assets.sh Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- .docker/scripts/proxy/assets.sh | 54 --------------------------------- 1 file changed, 54 deletions(-) delete mode 100644 .docker/scripts/proxy/assets.sh diff --git a/.docker/scripts/proxy/assets.sh b/.docker/scripts/proxy/assets.sh deleted file mode 100644 index 58991c5..0000000 --- a/.docker/scripts/proxy/assets.sh +++ /dev/null @@ -1,54 +0,0 @@ -#!/bin/sh - -proxy_helper_image() { - if [ -n "${PROXY_HELPER_IMAGE:-}" ]; then - printf '%s\n' "$PROXY_HELPER_IMAGE" - return 0 - fi - - container_image "${COORDINATOR_CONTAINER:-}" -} - -copy_to_named_volume() { - volume="$1" - source="$2" - destination="$3" - helper_image="$(proxy_helper_image)" - - [ -n "$helper_image" ] || return 1 - - Docker run --rm -i \ - -v "$volume:/target" \ - "$helper_image" \ - tee "/target/$destination" \ - < "$source" >/dev/null -} - -install_proxy_assets() { - assets_volume="$(proxy_assets_volume_name)" - vhost_volume="$(proxy_vhost_volume_name)" - helper_image="$(proxy_helper_image)" - - [ -n "$helper_image" ] || return 1 - - Docker volume create "$assets_volume" >/dev/null - Docker volume create "$vhost_volume" >/dev/null - - Docker run --rm \ - -v "$assets_volume:/target" \ - "$helper_image" \ - sh -c 'rm -f /target/Procfile /target/docker-gen.cfg /target/dashboard.tmpl' - - copy_to_named_volume "$assets_volume" "${PROJECT_DIR:-}/.docker/nginx-proxy/Procfile" Procfile - copy_to_named_volume "$assets_volume" "${PROJECT_DIR:-}/.docker/nginx-proxy/docker-gen.cfg" docker-gen.cfg - copy_to_named_volume "$assets_volume" "${PROJECT_DIR:-}/.docker/nginx-proxy/dashboard.tmpl" dashboard.tmpl - - Docker run --rm \ - -v "$vhost_volume:/target" \ - "$helper_image" \ - sh -c 'rm -f /target/librecode-localhost.conf /target/localhost /target/localhost_location_override /target/\*.localhost /target/\*.localhost_location_override' - - copy_to_named_volume "$vhost_volume" "${PROJECT_DIR:-}/.docker/nginx-proxy/localhost_location_override" localhost_location_override - copy_to_named_volume "$vhost_volume" "${PROJECT_DIR:-}/.docker/nginx-proxy/*.localhost" '*.localhost' - copy_to_named_volume "$vhost_volume" "${PROJECT_DIR:-}/.docker/nginx-proxy/*.localhost_location_override" '*.localhost_location_override' -} From 46fcf639cc7f9364e8ee1e45bfa146f3565deb78 Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Fri, 18 Sep 2026 11:21:12 -0300 Subject: [PATCH 14/63] poc: remove obsolete nginx proxy asset .docker/nginx-proxy/*.localhost Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- .docker/nginx-proxy/*.localhost | 6 ------ 1 file changed, 6 deletions(-) delete mode 100644 .docker/nginx-proxy/*.localhost diff --git a/.docker/nginx-proxy/*.localhost b/.docker/nginx-proxy/*.localhost deleted file mode 100644 index b66d860..0000000 --- a/.docker/nginx-proxy/*.localhost +++ /dev/null @@ -1,6 +0,0 @@ -root /usr/share/nginx/html; -error_page 404 =404 @librecode_dashboard_404; - -location @librecode_dashboard_404 { - try_files /index.html =404; -} From f2856031556c25444c7c9415096ec201af1248bb Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Fri, 18 Sep 2026 11:21:17 -0300 Subject: [PATCH 15/63] poc: remove obsolete nginx proxy asset .docker/nginx-proxy/*.localhost_location_override Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- .docker/nginx-proxy/*.localhost_location_override | 1 - 1 file changed, 1 deletion(-) delete mode 100644 .docker/nginx-proxy/*.localhost_location_override diff --git a/.docker/nginx-proxy/*.localhost_location_override b/.docker/nginx-proxy/*.localhost_location_override deleted file mode 100644 index 421c127..0000000 --- a/.docker/nginx-proxy/*.localhost_location_override +++ /dev/null @@ -1 +0,0 @@ -return 404; From 563321755d01e148939c767d380a57f506e0abbc Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Fri, 18 Sep 2026 11:21:23 -0300 Subject: [PATCH 16/63] poc: remove obsolete nginx proxy asset .docker/nginx-proxy/Procfile Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- .docker/nginx-proxy/Procfile | 2 -- 1 file changed, 2 deletions(-) delete mode 100644 .docker/nginx-proxy/Procfile diff --git a/.docker/nginx-proxy/Procfile b/.docker/nginx-proxy/Procfile deleted file mode 100644 index c0f1ad2..0000000 --- a/.docker/nginx-proxy/Procfile +++ /dev/null @@ -1,2 +0,0 @@ -dockergen: docker-gen -config /dashboard/docker-gen.cfg -event-filter event=connect -event-filter event=disconnect -nginx: nginx -g "daemon off;" From 1e83cb1ca7882b1d52a75ce5e41a4045afd62bb9 Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Fri, 18 Sep 2026 11:21:29 -0300 Subject: [PATCH 17/63] poc: remove obsolete nginx proxy asset .docker/nginx-proxy/dashboard.tmpl Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- .docker/nginx-proxy/dashboard.tmpl | 250 ----------------------------- 1 file changed, 250 deletions(-) delete mode 100644 .docker/nginx-proxy/dashboard.tmpl diff --git a/.docker/nginx-proxy/dashboard.tmpl b/.docker/nginx-proxy/dashboard.tmpl deleted file mode 100644 index 10246d9..0000000 --- a/.docker/nginx-proxy/dashboard.tmpl +++ /dev/null @@ -1,250 +0,0 @@ -{{- $routes := list -}} -{{- range $hostname, $containers := groupByMulti . "Env.VIRTUAL_HOST" "," -}} - {{- $hostname = trim $hostname -}} - {{- if or (eq $hostname "") (eq $hostname "localhost") (eq $hostname "*.localhost") -}} - {{- continue -}} - {{- end -}} - - {{- range $container := $containers -}} - {{- $onProxyNetwork := false -}} - {{- range $network := $container.Networks -}} - {{- if eq $network.Name "librecode-dev-proxy" -}} - {{- $onProxyNetwork = true -}} - {{- end -}} - {{- end -}} - {{- if not $onProxyNetwork -}} - {{- continue -}} - {{- end -}} - - {{- $project := index $container.Labels "com.docker.compose.project" -}} - {{- $service := index $container.Labels "com.docker.compose.service" -}} - {{- if not $project -}} - {{- $project = "Docker" -}} - {{- end -}} - {{- if not $service -}} - {{- $service = "Service" -}} - {{- end -}} - - {{- $routes = append $routes (dict "host" $hostname "project" $project "service" $service) -}} - {{- break -}} - {{- end -}} -{{- end -}} - - - - - - LibreCode Nextcloud Development Environment - - - -
-

πŸ’™ LibreCode Nextcloud Development Environment

- - -

Active environments

-
- - - -

This page is generated from the currently active development routes.

- - -
- - - - - From cd7cf272f5e771457565169448d87f86f4da4896 Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Fri, 18 Sep 2026 11:21:38 -0300 Subject: [PATCH 18/63] poc: remove obsolete nginx proxy asset .docker/nginx-proxy/docker-gen.cfg Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- .docker/nginx-proxy/docker-gen.cfg | 12 ------------ 1 file changed, 12 deletions(-) delete mode 100644 .docker/nginx-proxy/docker-gen.cfg diff --git a/.docker/nginx-proxy/docker-gen.cfg b/.docker/nginx-proxy/docker-gen.cfg deleted file mode 100644 index 1516178..0000000 --- a/.docker/nginx-proxy/docker-gen.cfg +++ /dev/null @@ -1,12 +0,0 @@ -[[config]] -template = "/app/nginx.tmpl" -dest = "/etc/nginx/conf.d/default.conf" -watch = true -wait = "100ms:500ms" -notifycmd = "nginx -s reload" - -[[config]] -template = "/dashboard/dashboard.tmpl" -dest = "/usr/share/nginx/html/index.html" -watch = true -wait = "100ms:500ms" From db276d37bd39497e9f562f8986f62850ae10aee7 Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Fri, 18 Sep 2026 11:21:56 -0300 Subject: [PATCH 19/63] poc: remove obsolete nginx proxy location override Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- .docker/nginx-proxy/localhost_location_override | 2 -- 1 file changed, 2 deletions(-) delete mode 100644 .docker/nginx-proxy/localhost_location_override diff --git a/.docker/nginx-proxy/localhost_location_override b/.docker/nginx-proxy/localhost_location_override deleted file mode 100644 index b3f4e9c..0000000 --- a/.docker/nginx-proxy/localhost_location_override +++ /dev/null @@ -1,2 +0,0 @@ -root /usr/share/nginx/html; -try_files $uri /index.html =404; From e127f3866c086f29ebbe4b093a535023a97cc38b Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Fri, 18 Sep 2026 11:22:15 -0300 Subject: [PATCH 20/63] fix: use valid Traefik rule labels in docker-compose.yml Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- docker-compose.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 13aea8c..3901d02 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -66,7 +66,7 @@ services: labels: - "traefik.enable=true" - "traefik.docker.network=librecode-dev-proxy" - - "traefik.http.routers.librecode-${COMPOSE_PROJECT_NAME}--nextcloud.rule=Host(\`${COMPOSE_PROJECT_NAME}.localhost\`)" + - "traefik.http.routers.librecode-${COMPOSE_PROJECT_NAME}--nextcloud.rule=Host(`${COMPOSE_PROJECT_NAME}.localhost`)" - "traefik.http.routers.librecode-${COMPOSE_PROJECT_NAME}--nextcloud.entrypoints=websecure" - "traefik.http.routers.librecode-${COMPOSE_PROJECT_NAME}--nextcloud.tls=true" - "traefik.http.services.librecode-${COMPOSE_PROJECT_NAME}--nextcloud.loadbalancer.server.port=80" @@ -99,7 +99,7 @@ services: labels: - "traefik.enable=true" - "traefik.docker.network=librecode-dev-proxy" - - "traefik.http.routers.librecode-${COMPOSE_PROJECT_NAME}--mailpit.rule=Host(\`${COMPOSE_PROJECT_NAME}-mailpit.localhost\`)" + - "traefik.http.routers.librecode-${COMPOSE_PROJECT_NAME}--mailpit.rule=Host(`${COMPOSE_PROJECT_NAME}-mailpit.localhost`)" - "traefik.http.routers.librecode-${COMPOSE_PROJECT_NAME}--mailpit.entrypoints=websecure" - "traefik.http.routers.librecode-${COMPOSE_PROJECT_NAME}--mailpit.tls=true" - "traefik.http.services.librecode-${COMPOSE_PROJECT_NAME}--mailpit.loadbalancer.server.port=8025" @@ -125,7 +125,7 @@ services: labels: - "traefik.enable=true" - "traefik.docker.network=librecode-dev-proxy" - - "traefik.http.routers.librecode-${COMPOSE_PROJECT_NAME}--eurooffice.rule=Host(\`${COMPOSE_PROJECT_NAME}-eurooffice.localhost\`)" + - "traefik.http.routers.librecode-${COMPOSE_PROJECT_NAME}--eurooffice.rule=Host(`${COMPOSE_PROJECT_NAME}-eurooffice.localhost`)" - "traefik.http.routers.librecode-${COMPOSE_PROJECT_NAME}--eurooffice.entrypoints=websecure" - "traefik.http.routers.librecode-${COMPOSE_PROJECT_NAME}--eurooffice.tls=true" - "traefik.http.services.librecode-${COMPOSE_PROJECT_NAME}--eurooffice.loadbalancer.server.port=80" @@ -149,7 +149,7 @@ services: labels: - "traefik.enable=true" - "traefik.docker.network=librecode-dev-proxy" - - "traefik.http.routers.librecode-${COMPOSE_PROJECT_NAME}--playwright.rule=Host(\`${COMPOSE_PROJECT_NAME}-playwright.localhost\`)" + - "traefik.http.routers.librecode-${COMPOSE_PROJECT_NAME}--playwright.rule=Host(`${COMPOSE_PROJECT_NAME}-playwright.localhost`)" - "traefik.http.routers.librecode-${COMPOSE_PROJECT_NAME}--playwright.entrypoints=websecure" - "traefik.http.routers.librecode-${COMPOSE_PROJECT_NAME}--playwright.tls=true" - "traefik.http.services.librecode-${COMPOSE_PROJECT_NAME}--playwright.loadbalancer.server.port=9323" @@ -174,7 +174,7 @@ services: labels: - "traefik.enable=true" - "traefik.docker.network=librecode-dev-proxy" - - "traefik.http.routers.librecode-${COMPOSE_PROJECT_NAME}--signal-gateway.rule=Host(\`${COMPOSE_PROJECT_NAME}-signal.localhost\`)" + - "traefik.http.routers.librecode-${COMPOSE_PROJECT_NAME}--signal-gateway.rule=Host(`${COMPOSE_PROJECT_NAME}-signal.localhost`)" - "traefik.http.routers.librecode-${COMPOSE_PROJECT_NAME}--signal-gateway.entrypoints=websecure" - "traefik.http.routers.librecode-${COMPOSE_PROJECT_NAME}--signal-gateway.tls=true" - "traefik.http.services.librecode-${COMPOSE_PROJECT_NAME}--signal-gateway.loadbalancer.server.port=8080" From 9ac4b739bd540aa92c76589f86de05b43b73ff15 Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Fri, 18 Sep 2026 11:22:18 -0300 Subject: [PATCH 21/63] fix: use valid Traefik rule labels in tests/proxy/fixtures/compose.yml Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- tests/proxy/fixtures/compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/proxy/fixtures/compose.yml b/tests/proxy/fixtures/compose.yml index 69a28f0..5e4747e 100644 --- a/tests/proxy/fixtures/compose.yml +++ b/tests/proxy/fixtures/compose.yml @@ -8,7 +8,7 @@ services: labels: - "traefik.enable=true" - "traefik.docker.network=librecode-dev-proxy" - - "traefik.http.routers.librecode-${COMPOSE_PROJECT_NAME}--nextcloud.rule=Host(\`${COMPOSE_PROJECT_NAME}.localhost\`)" + - "traefik.http.routers.librecode-${COMPOSE_PROJECT_NAME}--nextcloud.rule=Host(`${COMPOSE_PROJECT_NAME}.localhost`)" - "traefik.http.routers.librecode-${COMPOSE_PROJECT_NAME}--nextcloud.entrypoints=websecure" - "traefik.http.routers.librecode-${COMPOSE_PROJECT_NAME}--nextcloud.tls=true" - "traefik.http.services.librecode-${COMPOSE_PROJECT_NAME}--nextcloud.loadbalancer.server.port=80" From 699abbd3452692b8f3a564b3e3832d43c45b4c77 Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Fri, 18 Sep 2026 11:22:58 -0300 Subject: [PATCH 22/63] fix: restore Traefik proxy policy tests Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- tests/proxy/compose-policy.bats | 272 +------------------------------- 1 file changed, 2 insertions(+), 270 deletions(-) diff --git a/tests/proxy/compose-policy.bats b/tests/proxy/compose-policy.bats index 3f4a4eb..7afafcb 100644 --- a/tests/proxy/compose-policy.bats +++ b/tests/proxy/compose-policy.bats @@ -10,62 +10,10 @@ setup() { grep -Eq '^[[:space:]]+image: redis:[0-9]+\.[0-9]+\.[0-9]+@sha256:[0-9a-f]{64}$' "$REPO_ROOT/docker-compose.yml" grep -Eq '^[[:space:]]+image: mysql:8\.4@sha256:[0-9a-f]{64}$' "$REPO_ROOT/.docker/database-services.yml" grep -Eq '^[[:space:]]+image: postgres:13-alpine@sha256:[0-9a-f]{64}$' "$REPO_ROOT/.docker/database-services.yml" - grep -Eq '^[[:space:]]+image: traefik:v[0-9]+\.[0-9]+\.[0-9]+@sha256:[0-9a-f]{64} -@test "proxy integration fixture images are pinned by tag and digest" { - grep -Eq '^[[:space:]]+image: nginx:[0-9]+\.[0-9]+\.[0-9]+-alpine@sha256:[0-9a-f]{64}$' "$REPO_ROOT/tests/proxy/fixtures/compose.yml" - grep -Eq '^[[:space:]]+image: docker:[0-9]+\.[0-9]+\.[0-9]+-cli@sha256:[0-9a-f]{64}$' "$REPO_ROOT/tests/proxy/fixtures/compose.yml" -} - -@test "compose files do not keep shutdown workarounds from runtime debugging" { - for file in "$REPO_ROOT/docker-compose.yml" "$REPO_ROOT/tests/proxy/fixtures/compose.yml"; do - ! grep -Eq '^[[:space:]]+(stop_signal|stop_grace_period|init):' "$file" - done + grep -Eq '^[[:space:]]+image: traefik:v[0-9]+\.[0-9]+\.[0-9]+@sha256:[0-9a-f]{64}$' "$REPO_ROOT/.docker/docker-compose.proxy.yml" + grep -Eq '^[[:space:]]+image: nginx:[0-9]+\.[0-9]+\.[0-9]+-alpine@sha256:[0-9a-f]{64}$' "$REPO_ROOT/.docker/docker-compose.proxy.yml" } -@test "network bind settings are scoped per infrastructure service" { - proxy_compose="$REPO_ROOT/.docker/docker-compose.proxy.yml" - database_compose="$REPO_ROOT/.docker/database-services.yml" - - grep -Fq '"${PROXY_IP_BIND:-127.0.0.1}:80:80"' "$proxy_compose" - grep -Fq '"${PROXY_IP_BIND:-127.0.0.1}:443:443"' "$proxy_compose" - grep -Fq 'host_ip: ${MYSQL_IP_BIND:-127.0.0.1}' "$database_compose" - grep -Fq 'host_ip: ${POSTGRES_IP_BIND:-127.0.0.1}' "$database_compose" - ! grep -Rq '${IP_BIND' "$proxy_compose" "$database_compose" "$REPO_ROOT/docker-compose.yml" - - run env PROXY_IP_BIND=0.0.0.0 docker compose --file "$proxy_compose" config - [ "$status" -eq 0 ] - printf '%s\n' "$output" | grep -q 'host_ip: 0.0.0.0' - - run env MYSQL_IP_BIND=0.0.0.0 docker compose --file "$database_compose" config - [ "$status" -eq 0 ] - printf '%s\n' "$output" | grep -B5 -A5 'target: 3306' | grep -q 'host_ip: 0.0.0.0' - printf '%s\n' "$output" | grep -B5 -A5 'target: 5432' | grep -q 'host_ip: 127.0.0.1' - - run env POSTGRES_IP_BIND=0.0.0.0 docker compose --file "$database_compose" config - [ "$status" -eq 0 ] - printf '%s\n' "$output" | grep -B5 -A5 'target: 3306' | grep -q 'host_ip: 127.0.0.1' - printf '%s\n' "$output" | grep -B5 -A5 'target: 5432' | grep -q 'host_ip: 0.0.0.0' -} - -@test "docker socket mounts stay limited to proxy infrastructure" { - main_socket_mounts="$(grep -Ec '^[[:space:]]+- .*DOCKER_SOCKET.*:/var/run/docker\.sock$' "$REPO_ROOT/docker-compose.yml")" - [ "$main_socket_mounts" -eq 1 ] - - proxy_socket_mounts="$(grep -Ec '^[[:space:]]+- .*DOCKER_SOCKET.*:/.*docker\.sock:ro -@test "GitHub Actions are pinned to immutable commit SHAs" { - while IFS= read -r workflow; do - while IFS= read -r uses_line; do - ref="${uses_line#*@}" - ref="${ref%% *}" - [[ "$ref" =~ ^[0-9a-f]{40}$ ]] || { - printf 'mutable action reference in %s: %s\n' "$workflow" "$uses_line" >&2 - return 1 - } - done < <(grep -E '^[[:space:]]*-?[[:space:]]*uses:[[:space:]]+[^./][^[:space:]]+@' "$workflow" || true) - done < <(find "$REPO_ROOT/.github/workflows" -type f \( -name '*.yml' -o -name '*.yaml' \)) -} - "$REPO_ROOT/.docker/docker-compose.proxy.yml" - grep -Eq '^[[:space:]]+image: nginx:[0-9]+\.[0-9]+\.[0-9]+-alpine@sha256:[0-9a-f]{64} @test "proxy integration fixture images are pinned by tag and digest" { grep -Eq '^[[:space:]]+image: nginx:[0-9]+\.[0-9]+\.[0-9]+-alpine@sha256:[0-9a-f]{64}$' "$REPO_ROOT/tests/proxy/fixtures/compose.yml" grep -Eq '^[[:space:]]+image: docker:[0-9]+\.[0-9]+\.[0-9]+-cli@sha256:[0-9a-f]{64}$' "$REPO_ROOT/tests/proxy/fixtures/compose.yml" @@ -107,93 +55,6 @@ setup() { [ "$main_socket_mounts" -eq 1 ] proxy_socket_mounts="$(grep -Ec '^[[:space:]]+- .*DOCKER_SOCKET.*:/.*docker\.sock:ro$' "$REPO_ROOT/.docker/docker-compose.proxy.yml")" - [ "$proxy_socket_mounts" -eq 2 ] -} - -@test "dashboard does not use innerHTML for Docker metadata" { - ! grep -q 'innerHTML' "$REPO_ROOT/.docker/nginx-proxy/dashboard.tmpl" - grep -q 'textContent = currentProject' "$REPO_ROOT/.docker/nginx-proxy/dashboard.tmpl" - grep -q 'textContent = link.href' "$REPO_ROOT/.docker/nginx-proxy/dashboard.tmpl" -} - -@test "GitHub Actions are pinned to immutable commit SHAs" { - while IFS= read -r workflow; do - while IFS= read -r uses_line; do - ref="${uses_line#*@}" - ref="${ref%% *}" - [[ "$ref" =~ ^[0-9a-f]{40}$ ]] || { - printf 'mutable action reference in %s: %s\n' "$workflow" "$uses_line" >&2 - return 1 - } - done < <(grep -E '^[[:space:]]*-?[[:space:]]*uses:[[:space:]]+[^./][^[:space:]]+@' "$workflow" || true) - done < <(find "$REPO_ROOT/.github/workflows" -type f \( -name '*.yml' -o -name '*.yaml' \)) -} - "$REPO_ROOT/.docker/docker-compose.proxy.yml" -} - -@test "proxy integration fixture images are pinned by tag and digest" { - grep -Eq '^[[:space:]]+image: nginx:[0-9]+\.[0-9]+\.[0-9]+-alpine@sha256:[0-9a-f]{64}$' "$REPO_ROOT/tests/proxy/fixtures/compose.yml" - grep -Eq '^[[:space:]]+image: docker:[0-9]+\.[0-9]+\.[0-9]+-cli@sha256:[0-9a-f]{64}$' "$REPO_ROOT/tests/proxy/fixtures/compose.yml" -} - -@test "compose files do not keep shutdown workarounds from runtime debugging" { - for file in "$REPO_ROOT/docker-compose.yml" "$REPO_ROOT/tests/proxy/fixtures/compose.yml"; do - ! grep -Eq '^[[:space:]]+(stop_signal|stop_grace_period|init):' "$file" - done -} - -@test "network bind settings are scoped per infrastructure service" { - proxy_compose="$REPO_ROOT/.docker/docker-compose.proxy.yml" - database_compose="$REPO_ROOT/.docker/database-services.yml" - - grep -Fq '"${PROXY_IP_BIND:-127.0.0.1}:80:80"' "$proxy_compose" - grep -Fq '"${PROXY_IP_BIND:-127.0.0.1}:443:443"' "$proxy_compose" - grep -Fq 'host_ip: ${MYSQL_IP_BIND:-127.0.0.1}' "$database_compose" - grep -Fq 'host_ip: ${POSTGRES_IP_BIND:-127.0.0.1}' "$database_compose" - ! grep -Rq '${IP_BIND' "$proxy_compose" "$database_compose" "$REPO_ROOT/docker-compose.yml" - - run env PROXY_IP_BIND=0.0.0.0 docker compose --file "$proxy_compose" config - [ "$status" -eq 0 ] - printf '%s\n' "$output" | grep -q 'host_ip: 0.0.0.0' - - run env MYSQL_IP_BIND=0.0.0.0 docker compose --file "$database_compose" config - [ "$status" -eq 0 ] - printf '%s\n' "$output" | grep -B5 -A5 'target: 3306' | grep -q 'host_ip: 0.0.0.0' - printf '%s\n' "$output" | grep -B5 -A5 'target: 5432' | grep -q 'host_ip: 127.0.0.1' - - run env POSTGRES_IP_BIND=0.0.0.0 docker compose --file "$database_compose" config - [ "$status" -eq 0 ] - printf '%s\n' "$output" | grep -B5 -A5 'target: 3306' | grep -q 'host_ip: 127.0.0.1' - printf '%s\n' "$output" | grep -B5 -A5 'target: 5432' | grep -q 'host_ip: 0.0.0.0' -} - -@test "docker socket mounts stay limited to proxy infrastructure" { - main_socket_mounts="$(grep -Ec '^[[:space:]]+- .*DOCKER_SOCKET.*:/var/run/docker\.sock$' "$REPO_ROOT/docker-compose.yml")" - [ "$main_socket_mounts" -eq 1 ] - - proxy_socket_mounts="$(grep -Ec '^[[:space:]]+- .*DOCKER_SOCKET.*:/.*docker\.sock:ro$' "$REPO_ROOT/.docker/docker-compose.proxy.yml")" - [ "$proxy_socket_mounts" -eq 2 ] -} - -@test "dashboard does not use innerHTML for Docker metadata" { - ! grep -q 'innerHTML' "$REPO_ROOT/.docker/nginx-proxy/dashboard.tmpl" - grep -q 'textContent = currentProject' "$REPO_ROOT/.docker/nginx-proxy/dashboard.tmpl" - grep -q 'textContent = link.href' "$REPO_ROOT/.docker/nginx-proxy/dashboard.tmpl" -} - -@test "GitHub Actions are pinned to immutable commit SHAs" { - while IFS= read -r workflow; do - while IFS= read -r uses_line; do - ref="${uses_line#*@}" - ref="${ref%% *}" - [[ "$ref" =~ ^[0-9a-f]{40}$ ]] || { - printf 'mutable action reference in %s: %s\n' "$workflow" "$uses_line" >&2 - return 1 - } - done < <(grep -E '^[[:space:]]*-?[[:space:]]*uses:[[:space:]]+[^./][^[:space:]]+@' "$workflow" || true) - done < <(find "$REPO_ROOT/.github/workflows" -type f \( -name '*.yml' -o -name '*.yaml' \)) -} - "$REPO_ROOT/.docker/docker-compose.proxy.yml")" [ "$proxy_socket_mounts" -eq 1 ] } @@ -204,135 +65,6 @@ setup() { grep -q 'link.textContent = link.href' "$dashboard" } -@test "GitHub Actions are pinned to immutable commit SHAs" { - while IFS= read -r workflow; do - while IFS= read -r uses_line; do - ref="${uses_line#*@}" - ref="${ref%% *}" - [[ "$ref" =~ ^[0-9a-f]{40}$ ]] || { - printf 'mutable action reference in %s: %s\n' "$workflow" "$uses_line" >&2 - return 1 - } - done < <(grep -E '^[[:space:]]*-?[[:space:]]*uses:[[:space:]]+[^./][^[:space:]]+@' "$workflow" || true) - done < <(find "$REPO_ROOT/.github/workflows" -type f \( -name '*.yml' -o -name '*.yaml' \)) -} - "$REPO_ROOT/.docker/docker-compose.proxy.yml" - grep -Eq '^[[:space:]]+image: nginx:[0-9]+\.[0-9]+\.[0-9]+-alpine@sha256:[0-9a-f]{64} -@test "proxy integration fixture images are pinned by tag and digest" { - grep -Eq '^[[:space:]]+image: nginx:[0-9]+\.[0-9]+\.[0-9]+-alpine@sha256:[0-9a-f]{64}$' "$REPO_ROOT/tests/proxy/fixtures/compose.yml" - grep -Eq '^[[:space:]]+image: docker:[0-9]+\.[0-9]+\.[0-9]+-cli@sha256:[0-9a-f]{64}$' "$REPO_ROOT/tests/proxy/fixtures/compose.yml" -} - -@test "compose files do not keep shutdown workarounds from runtime debugging" { - for file in "$REPO_ROOT/docker-compose.yml" "$REPO_ROOT/tests/proxy/fixtures/compose.yml"; do - ! grep -Eq '^[[:space:]]+(stop_signal|stop_grace_period|init):' "$file" - done -} - -@test "network bind settings are scoped per infrastructure service" { - proxy_compose="$REPO_ROOT/.docker/docker-compose.proxy.yml" - database_compose="$REPO_ROOT/.docker/database-services.yml" - - grep -Fq '"${PROXY_IP_BIND:-127.0.0.1}:80:80"' "$proxy_compose" - grep -Fq '"${PROXY_IP_BIND:-127.0.0.1}:443:443"' "$proxy_compose" - grep -Fq 'host_ip: ${MYSQL_IP_BIND:-127.0.0.1}' "$database_compose" - grep -Fq 'host_ip: ${POSTGRES_IP_BIND:-127.0.0.1}' "$database_compose" - ! grep -Rq '${IP_BIND' "$proxy_compose" "$database_compose" "$REPO_ROOT/docker-compose.yml" - - run env PROXY_IP_BIND=0.0.0.0 docker compose --file "$proxy_compose" config - [ "$status" -eq 0 ] - printf '%s\n' "$output" | grep -q 'host_ip: 0.0.0.0' - - run env MYSQL_IP_BIND=0.0.0.0 docker compose --file "$database_compose" config - [ "$status" -eq 0 ] - printf '%s\n' "$output" | grep -B5 -A5 'target: 3306' | grep -q 'host_ip: 0.0.0.0' - printf '%s\n' "$output" | grep -B5 -A5 'target: 5432' | grep -q 'host_ip: 127.0.0.1' - - run env POSTGRES_IP_BIND=0.0.0.0 docker compose --file "$database_compose" config - [ "$status" -eq 0 ] - printf '%s\n' "$output" | grep -B5 -A5 'target: 3306' | grep -q 'host_ip: 127.0.0.1' - printf '%s\n' "$output" | grep -B5 -A5 'target: 5432' | grep -q 'host_ip: 0.0.0.0' -} - -@test "docker socket mounts stay limited to proxy infrastructure" { - main_socket_mounts="$(grep -Ec '^[[:space:]]+- .*DOCKER_SOCKET.*:/var/run/docker\.sock$' "$REPO_ROOT/docker-compose.yml")" - [ "$main_socket_mounts" -eq 1 ] - - proxy_socket_mounts="$(grep -Ec '^[[:space:]]+- .*DOCKER_SOCKET.*:/.*docker\.sock:ro$' "$REPO_ROOT/.docker/docker-compose.proxy.yml")" - [ "$proxy_socket_mounts" -eq 2 ] -} - -@test "dashboard does not use innerHTML for Docker metadata" { - ! grep -q 'innerHTML' "$REPO_ROOT/.docker/nginx-proxy/dashboard.tmpl" - grep -q 'textContent = currentProject' "$REPO_ROOT/.docker/nginx-proxy/dashboard.tmpl" - grep -q 'textContent = link.href' "$REPO_ROOT/.docker/nginx-proxy/dashboard.tmpl" -} - -@test "GitHub Actions are pinned to immutable commit SHAs" { - while IFS= read -r workflow; do - while IFS= read -r uses_line; do - ref="${uses_line#*@}" - ref="${ref%% *}" - [[ "$ref" =~ ^[0-9a-f]{40}$ ]] || { - printf 'mutable action reference in %s: %s\n' "$workflow" "$uses_line" >&2 - return 1 - } - done < <(grep -E '^[[:space:]]*-?[[:space:]]*uses:[[:space:]]+[^./][^[:space:]]+@' "$workflow" || true) - done < <(find "$REPO_ROOT/.github/workflows" -type f \( -name '*.yml' -o -name '*.yaml' \)) -} - "$REPO_ROOT/.docker/docker-compose.proxy.yml" -} - -@test "proxy integration fixture images are pinned by tag and digest" { - grep -Eq '^[[:space:]]+image: nginx:[0-9]+\.[0-9]+\.[0-9]+-alpine@sha256:[0-9a-f]{64}$' "$REPO_ROOT/tests/proxy/fixtures/compose.yml" - grep -Eq '^[[:space:]]+image: docker:[0-9]+\.[0-9]+\.[0-9]+-cli@sha256:[0-9a-f]{64}$' "$REPO_ROOT/tests/proxy/fixtures/compose.yml" -} - -@test "compose files do not keep shutdown workarounds from runtime debugging" { - for file in "$REPO_ROOT/docker-compose.yml" "$REPO_ROOT/tests/proxy/fixtures/compose.yml"; do - ! grep -Eq '^[[:space:]]+(stop_signal|stop_grace_period|init):' "$file" - done -} - -@test "network bind settings are scoped per infrastructure service" { - proxy_compose="$REPO_ROOT/.docker/docker-compose.proxy.yml" - database_compose="$REPO_ROOT/.docker/database-services.yml" - - grep -Fq '"${PROXY_IP_BIND:-127.0.0.1}:80:80"' "$proxy_compose" - grep -Fq '"${PROXY_IP_BIND:-127.0.0.1}:443:443"' "$proxy_compose" - grep -Fq 'host_ip: ${MYSQL_IP_BIND:-127.0.0.1}' "$database_compose" - grep -Fq 'host_ip: ${POSTGRES_IP_BIND:-127.0.0.1}' "$database_compose" - ! grep -Rq '${IP_BIND' "$proxy_compose" "$database_compose" "$REPO_ROOT/docker-compose.yml" - - run env PROXY_IP_BIND=0.0.0.0 docker compose --file "$proxy_compose" config - [ "$status" -eq 0 ] - printf '%s\n' "$output" | grep -q 'host_ip: 0.0.0.0' - - run env MYSQL_IP_BIND=0.0.0.0 docker compose --file "$database_compose" config - [ "$status" -eq 0 ] - printf '%s\n' "$output" | grep -B5 -A5 'target: 3306' | grep -q 'host_ip: 0.0.0.0' - printf '%s\n' "$output" | grep -B5 -A5 'target: 5432' | grep -q 'host_ip: 127.0.0.1' - - run env POSTGRES_IP_BIND=0.0.0.0 docker compose --file "$database_compose" config - [ "$status" -eq 0 ] - printf '%s\n' "$output" | grep -B5 -A5 'target: 3306' | grep -q 'host_ip: 127.0.0.1' - printf '%s\n' "$output" | grep -B5 -A5 'target: 5432' | grep -q 'host_ip: 0.0.0.0' -} - -@test "docker socket mounts stay limited to proxy infrastructure" { - main_socket_mounts="$(grep -Ec '^[[:space:]]+- .*DOCKER_SOCKET.*:/var/run/docker\.sock$' "$REPO_ROOT/docker-compose.yml")" - [ "$main_socket_mounts" -eq 1 ] - - proxy_socket_mounts="$(grep -Ec '^[[:space:]]+- .*DOCKER_SOCKET.*:/.*docker\.sock:ro$' "$REPO_ROOT/.docker/docker-compose.proxy.yml")" - [ "$proxy_socket_mounts" -eq 2 ] -} - -@test "dashboard does not use innerHTML for Docker metadata" { - ! grep -q 'innerHTML' "$REPO_ROOT/.docker/nginx-proxy/dashboard.tmpl" - grep -q 'textContent = currentProject' "$REPO_ROOT/.docker/nginx-proxy/dashboard.tmpl" - grep -q 'textContent = link.href' "$REPO_ROOT/.docker/nginx-proxy/dashboard.tmpl" -} - @test "GitHub Actions are pinned to immutable commit SHAs" { while IFS= read -r workflow; do while IFS= read -r uses_line; do From c2c19396cefb7b261b3ed98e9d2a294da3268685 Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Fri, 18 Sep 2026 11:23:33 -0300 Subject: [PATCH 23/63] fix: use correct localhost regex in Traefik router Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- .docker/docker-compose.proxy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.docker/docker-compose.proxy.yml b/.docker/docker-compose.proxy.yml index 0729574..54647e9 100644 --- a/.docker/docker-compose.proxy.yml +++ b/.docker/docker-compose.proxy.yml @@ -40,7 +40,7 @@ services: traefik.http.routers.librecode-dashboard.priority: "100" traefik.http.routers.librecode-dashboard.tls: "true" traefik.http.routers.librecode-dashboard.service: librecode-dashboard - traefik.http.routers.librecode-dashboard-catchall.rule: HostRegexp(`[a-z0-9-]+\\.localhost`) + traefik.http.routers.librecode-dashboard-catchall.rule: HostRegexp(`[a-z0-9-]+\.localhost`) traefik.http.routers.librecode-dashboard-catchall.entrypoints: websecure traefik.http.routers.librecode-dashboard-catchall.priority: "1" traefik.http.routers.librecode-dashboard-catchall.tls: "true" From 53749706b722fffb2f679829a99eeddcc52c6a24 Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Fri, 18 Sep 2026 11:31:25 -0300 Subject: [PATCH 24/63] fix: avoid host-path dashboard mounts in shared proxy Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- .docker/docker-compose.proxy.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.docker/docker-compose.proxy.yml b/.docker/docker-compose.proxy.yml index 54647e9..be51369 100644 --- a/.docker/docker-compose.proxy.yml +++ b/.docker/docker-compose.proxy.yml @@ -48,9 +48,6 @@ services: traefik.http.routers.librecode-dashboard-catchall.middlewares: librecode-not-found traefik.http.middlewares.librecode-not-found.replacepath.path: /__librecode_not_found__ traefik.http.services.librecode-dashboard.loadbalancer.server.port: "80" - volumes: - - ${PROJECT_DIR}/.docker/traefik/dashboard.html:/usr/share/nginx/html/index.html:ro - - ${PROJECT_DIR}/.docker/traefik/dashboard.conf:/etc/nginx/conf.d/default.conf:ro networks: - proxy From ea687f2415214448b4a4a06bc837fffaeafefbe0 Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Fri, 18 Sep 2026 11:31:33 -0300 Subject: [PATCH 25/63] poc: install dashboard assets through Docker API Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- .docker/scripts/proxy/dashboard.sh | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 .docker/scripts/proxy/dashboard.sh diff --git a/.docker/scripts/proxy/dashboard.sh b/.docker/scripts/proxy/dashboard.sh new file mode 100644 index 0000000..c81ac15 --- /dev/null +++ b/.docker/scripts/proxy/dashboard.sh @@ -0,0 +1,9 @@ +#!/bin/sh + +install_dashboard_assets() { + dashboard_container="${PROXY_DASHBOARD_CONTAINER:-librecode-dev-dashboard}" + + Docker cp "${PROJECT_DIR:-}/.docker/traefik/dashboard.html" "$dashboard_container:/usr/share/nginx/html/index.html" + Docker cp "${PROJECT_DIR:-}/.docker/traefik/dashboard.conf" "$dashboard_container:/etc/nginx/conf.d/default.conf" + Docker exec "$dashboard_container" nginx -s reload +} From bf78d688556b854934913359dc3e0806bad0fe6c Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Fri, 18 Sep 2026 11:31:43 -0300 Subject: [PATCH 26/63] poc: install Traefik dashboard assets at startup Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- .docker/scripts/proxy-coordinator.sh | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.docker/scripts/proxy-coordinator.sh b/.docker/scripts/proxy-coordinator.sh index f345f37..fd9a3a5 100755 --- a/.docker/scripts/proxy-coordinator.sh +++ b/.docker/scripts/proxy-coordinator.sh @@ -18,6 +18,8 @@ export COORDINATOR_CONTAINER PROJECT_NAME # analyze the complete dependency graph without file-wide suppressions. # shellcheck source=.docker/scripts/proxy/infrastructure.sh . "$proxy_lib_dir/infrastructure.sh" +# shellcheck source=.docker/scripts/proxy/dashboard.sh +. "$proxy_lib_dir/dashboard.sh" # shellcheck source=.docker/scripts/proxy/diagnostics.sh . "$proxy_lib_dir/diagnostics.sh" # shellcheck source=.docker/scripts/proxy/services.sh @@ -84,6 +86,7 @@ run() { validate_environment ensure_proxy_network proxy_state="$(ensure_proxy_running)" + install_dashboard_assets install_runtime_diagnostics acquire_proxy_lease From 197ef2e11b50afb2ca25013ec14f59d0d27602bc Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Fri, 18 Sep 2026 15:15:25 -0300 Subject: [PATCH 27/63] test: bound proxy HTTP checks Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- tests/proxy/integration.bats | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/proxy/integration.bats b/tests/proxy/integration.bats index 6791b12..f3d551e 100644 --- a/tests/proxy/integration.bats +++ b/tests/proxy/integration.bats @@ -62,6 +62,8 @@ wait_for_https_path_status() { for _ in $(seq 1 60); do status="$(curl --silent --show-error --insecure \ + --connect-timeout 2 \ + --max-time 5 \ --resolve "$host:443:127.0.0.1" \ --output "$BODY" \ --write-out '%{http_code}' \ From e8734918bd43803e473b4608a3b20ab956934b9b Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Fri, 18 Sep 2026 15:15:31 -0300 Subject: [PATCH 28/63] test: bound proxy HTTP checks Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- tests/proxy/integration.bats | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/proxy/integration.bats b/tests/proxy/integration.bats index 7d3652e..43e9cd7 100644 --- a/tests/proxy/integration.bats +++ b/tests/proxy/integration.bats @@ -62,6 +62,8 @@ wait_for_https_path_status() { for _ in $(seq 1 60); do status="$(curl --silent --show-error --insecure \ + --connect-timeout 2 \ + --max-time 5 \ --resolve "$host:443:127.0.0.1" \ --output "$BODY" \ --write-out '%{http_code}' \ From f4dd4a89a5b1de9d176b15d875165d1b573d2036 Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Fri, 18 Sep 2026 15:19:47 -0300 Subject: [PATCH 29/63] test: fail fast with Traefik diagnostics Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- tests/proxy/integration.bats | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/tests/proxy/integration.bats b/tests/proxy/integration.bats index 43e9cd7..2bd139a 100644 --- a/tests/proxy/integration.bats +++ b/tests/proxy/integration.bats @@ -60,10 +60,10 @@ wait_for_https_path_status() { path="$2" expected="$3" - for _ in $(seq 1 60); do + for _ in $(seq 1 30); do status="$(curl --silent --show-error --insecure \ - --connect-timeout 2 \ - --max-time 5 \ + --connect-timeout 1 \ + --max-time 2 \ --resolve "$host:443:127.0.0.1" \ --output "$BODY" \ --write-out '%{http_code}' \ @@ -71,6 +71,10 @@ wait_for_https_path_status() { [ "$status" = "$expected" ] && return 0 sleep 0.5 done + printf 'Timed out waiting for https://%s%s to return %s\n' "$host" "$path" "$expected" >&2 + docker ps --format 'table {{.Names}}\t{{.Status}}\t{{.Image}}' >&2 || true + docker logs librecode-dev-proxy >&2 || true + docker logs librecode-dev-dashboard >&2 || true return 1 } From c78f2ea99c5e319b90d75646ae16b613a99a8edc Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Fri, 18 Sep 2026 15:21:11 -0300 Subject: [PATCH 30/63] poc: centralize Traefik TLS and entrypoint defaults Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- .docker/docker-compose.proxy.yml | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/.docker/docker-compose.proxy.yml b/.docker/docker-compose.proxy.yml index be51369..aa362d1 100644 --- a/.docker/docker-compose.proxy.yml +++ b/.docker/docker-compose.proxy.yml @@ -5,11 +5,8 @@ services: labels: coop.librecode.dev-proxy: "true" traefik.enable: "true" - traefik.docker.network: librecode-dev-proxy traefik.http.routers.librecode-api.rule: Path(`/api/http/routers`) - traefik.http.routers.librecode-api.entrypoints: websecure traefik.http.routers.librecode-api.priority: "200" - traefik.http.routers.librecode-api.tls: "true" traefik.http.routers.librecode-api.service: api@internal command: - --api=true @@ -20,6 +17,8 @@ services: - --entrypoints.web.http.redirections.entrypoint.to=websecure - --entrypoints.web.http.redirections.entrypoint.scheme=https - --entrypoints.websecure.address=:443 + - --entrypoints.websecure.asdefault=true + - --entrypoints.websecure.http.tls=true ports: - "${PROXY_IP_BIND:-127.0.0.1}:80:80" - "${PROXY_IP_BIND:-127.0.0.1}:443:443" @@ -34,11 +33,8 @@ services: labels: coop.librecode.dev-proxy-dashboard: "true" traefik.enable: "true" - traefik.docker.network: librecode-dev-proxy traefik.http.routers.librecode-dashboard.rule: Host(`localhost`) - traefik.http.routers.librecode-dashboard.entrypoints: websecure traefik.http.routers.librecode-dashboard.priority: "100" - traefik.http.routers.librecode-dashboard.tls: "true" traefik.http.routers.librecode-dashboard.service: librecode-dashboard traefik.http.routers.librecode-dashboard-catchall.rule: HostRegexp(`[a-z0-9-]+\.localhost`) traefik.http.routers.librecode-dashboard-catchall.entrypoints: websecure From b941d06a7a621e9d65f6ca5982819cbfca9a37d0 Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Fri, 18 Sep 2026 15:21:14 -0300 Subject: [PATCH 31/63] poc: reduce repeated Traefik service labels Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- docker-compose.yml | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 3901d02..316467f 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -65,10 +65,7 @@ services: - SELF_SIGNED_HOST=${COMPOSE_PROJECT_NAME}.localhost labels: - "traefik.enable=true" - - "traefik.docker.network=librecode-dev-proxy" - "traefik.http.routers.librecode-${COMPOSE_PROJECT_NAME}--nextcloud.rule=Host(`${COMPOSE_PROJECT_NAME}.localhost`)" - - "traefik.http.routers.librecode-${COMPOSE_PROJECT_NAME}--nextcloud.entrypoints=websecure" - - "traefik.http.routers.librecode-${COMPOSE_PROJECT_NAME}--nextcloud.tls=true" - "traefik.http.services.librecode-${COMPOSE_PROJECT_NAME}--nextcloud.loadbalancer.server.port=80" proxy-coordinator: image: docker:29.8.0-cli@sha256:eccaacfeed644c7de222ff047483568cb988dde95476fbaaf10ea2d04921bb66 @@ -98,10 +95,7 @@ services: - SELF_SIGNED_HOST=${COMPOSE_PROJECT_NAME}-mailpit.localhost labels: - "traefik.enable=true" - - "traefik.docker.network=librecode-dev-proxy" - "traefik.http.routers.librecode-${COMPOSE_PROJECT_NAME}--mailpit.rule=Host(`${COMPOSE_PROJECT_NAME}-mailpit.localhost`)" - - "traefik.http.routers.librecode-${COMPOSE_PROJECT_NAME}--mailpit.entrypoints=websecure" - - "traefik.http.routers.librecode-${COMPOSE_PROJECT_NAME}--mailpit.tls=true" - "traefik.http.services.librecode-${COMPOSE_PROJECT_NAME}--mailpit.loadbalancer.server.port=8025" redis: image: redis:8.8.2@sha256:37227fff5638322f4ebea25d6d0dc3ee50848604e82b81426f11507b3ec7d2cc @@ -124,10 +118,7 @@ services: - SELF_SIGNED_HOST=${COMPOSE_PROJECT_NAME}-eurooffice.localhost labels: - "traefik.enable=true" - - "traefik.docker.network=librecode-dev-proxy" - "traefik.http.routers.librecode-${COMPOSE_PROJECT_NAME}--eurooffice.rule=Host(`${COMPOSE_PROJECT_NAME}-eurooffice.localhost`)" - - "traefik.http.routers.librecode-${COMPOSE_PROJECT_NAME}--eurooffice.entrypoints=websecure" - - "traefik.http.routers.librecode-${COMPOSE_PROJECT_NAME}--eurooffice.tls=true" - "traefik.http.services.librecode-${COMPOSE_PROJECT_NAME}--eurooffice.loadbalancer.server.port=80" extra_hosts: - host.docker.internal:host-gateway @@ -148,10 +139,7 @@ services: - SELF_SIGNED_HOST=${COMPOSE_PROJECT_NAME}-playwright.localhost labels: - "traefik.enable=true" - - "traefik.docker.network=librecode-dev-proxy" - "traefik.http.routers.librecode-${COMPOSE_PROJECT_NAME}--playwright.rule=Host(`${COMPOSE_PROJECT_NAME}-playwright.localhost`)" - - "traefik.http.routers.librecode-${COMPOSE_PROJECT_NAME}--playwright.entrypoints=websecure" - - "traefik.http.routers.librecode-${COMPOSE_PROJECT_NAME}--playwright.tls=true" - "traefik.http.services.librecode-${COMPOSE_PROJECT_NAME}--playwright.loadbalancer.server.port=9323" command: sleep infinity depends_on: @@ -173,10 +161,7 @@ services: - SELF_SIGNED_HOST=${COMPOSE_PROJECT_NAME}-signal.localhost labels: - "traefik.enable=true" - - "traefik.docker.network=librecode-dev-proxy" - "traefik.http.routers.librecode-${COMPOSE_PROJECT_NAME}--signal-gateway.rule=Host(`${COMPOSE_PROJECT_NAME}-signal.localhost`)" - - "traefik.http.routers.librecode-${COMPOSE_PROJECT_NAME}--signal-gateway.entrypoints=websecure" - - "traefik.http.routers.librecode-${COMPOSE_PROJECT_NAME}--signal-gateway.tls=true" - "traefik.http.services.librecode-${COMPOSE_PROJECT_NAME}--signal-gateway.loadbalancer.server.port=8080" whatsapp.web: image: aldinokemal2104/go-whatsapp-web-multidevice From 1ed2b0c6a6442191864c3a4eeee6bb1b817b1ae9 Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Fri, 18 Sep 2026 15:21:20 -0300 Subject: [PATCH 32/63] test: align Traefik fixture with default TLS entrypoint Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- tests/proxy/fixtures/compose.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/tests/proxy/fixtures/compose.yml b/tests/proxy/fixtures/compose.yml index 5e4747e..cb9181f 100644 --- a/tests/proxy/fixtures/compose.yml +++ b/tests/proxy/fixtures/compose.yml @@ -7,10 +7,7 @@ services: - SELF_SIGNED_HOST=${COMPOSE_PROJECT_NAME}.localhost labels: - "traefik.enable=true" - - "traefik.docker.network=librecode-dev-proxy" - "traefik.http.routers.librecode-${COMPOSE_PROJECT_NAME}--nextcloud.rule=Host(`${COMPOSE_PROJECT_NAME}.localhost`)" - - "traefik.http.routers.librecode-${COMPOSE_PROJECT_NAME}--nextcloud.entrypoints=websecure" - - "traefik.http.routers.librecode-${COMPOSE_PROJECT_NAME}--nextcloud.tls=true" - "traefik.http.services.librecode-${COMPOSE_PROJECT_NAME}--nextcloud.loadbalancer.server.port=80" proxy-coordinator: From 4c97ae755319b31481bbafaa351e08b0fcb8dc89 Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Fri, 18 Sep 2026 15:24:19 -0300 Subject: [PATCH 33/63] poc: run proxy tests on Traefik branch pushes Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- .github/workflows/proxy-tests.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/proxy-tests.yml b/.github/workflows/proxy-tests.yml index 4392e6e..48331b2 100644 --- a/.github/workflows/proxy-tests.yml +++ b/.github/workflows/proxy-tests.yml @@ -5,6 +5,7 @@ on: push: branches: - main + - poc/traefik-development-proxy permissions: contents: read From 852a5431ab7c5b3303e7bf27fb7db2a838bc706c Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Fri, 18 Sep 2026 15:29:51 -0300 Subject: [PATCH 34/63] test: expose Traefik routing state on integration failure Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- tests/proxy/integration.bats | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/proxy/integration.bats b/tests/proxy/integration.bats index 2bd139a..8e102bf 100644 --- a/tests/proxy/integration.bats +++ b/tests/proxy/integration.bats @@ -71,8 +71,11 @@ wait_for_https_path_status() { [ "$status" = "$expected" ] && return 0 sleep 0.5 done - printf 'Timed out waiting for https://%s%s to return %s\n' "$host" "$path" "$expected" >&2 + printf 'Timed out waiting for https://%s%s to return %s; last status was %s\n' "$host" "$path" "$expected" "${status:-none}" >&2 docker ps --format 'table {{.Names}}\t{{.Status}}\t{{.Image}}' >&2 || true + docker inspect proxytesta-nginx-1 --format '{{json .NetworkSettings.Networks}}' >&2 2>/dev/null || true + curl --silent --show-error --insecure --connect-timeout 1 --max-time 2 \ + --resolve 'localhost:443:127.0.0.1' https://localhost/api/http/routers >&2 || true docker logs librecode-dev-proxy >&2 || true docker logs librecode-dev-dashboard >&2 || true return 1 From 5e95bd603b7c9872b758914feacc64334824a6be Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Fri, 18 Sep 2026 15:32:41 -0300 Subject: [PATCH 35/63] poc: attach Traefik to project networks Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- .docker/scripts/proxy/services.sh | 37 ++++++++++++++++--------------- 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/.docker/scripts/proxy/services.sh b/.docker/scripts/proxy/services.sh index 43d76a0..a2494b8 100644 --- a/.docker/scripts/proxy/services.sh +++ b/.docker/scripts/proxy/services.sh @@ -5,35 +5,36 @@ service_is_running() { grep -qx "$1" } -container_for_service() { - compose ps -q "$1" 2>/dev/null || true +project_network_name() { + printf '%s_default\n' "${PROJECT_NAME:-}" } -connect_to_proxy_network() { - service="$1" - container="$(container_for_service "$service")" - network="$(proxy_network_name)" +connect_proxy_to_project_network() { + proxy_container="$(compatible_proxy_container)" + network="$(project_network_name)" - [ -n "$container" ] || return 0 + [ -n "$proxy_container" ] || return 1 + [ -n "${PROJECT_NAME:-}" ] || return 1 - if container_networks "$container" | grep -q "\"$network\""; then + if container_networks "$proxy_container" | grep -q "\\\"$network\\\""; then return 0 fi - Docker network connect "$network" "$container" + Docker network connect "$network" "$proxy_container" } -connect_running_service_to_proxy_network() { - service="$1" +disconnect_proxy_from_project_network() { + proxy_container="$(compatible_proxy_container)" + network="$(project_network_name)" - service_is_running "$service" || return 0 - connect_to_proxy_network "$service" -} + [ -n "$proxy_container" ] || return 0 + [ -n "${PROJECT_NAME:-}" ] || return 0 + + if ! container_networks "$proxy_container" | grep -q "\\\"$network\\\""; then + return 0 + fi -connect_project_services() { - for service in nginx mailpit eurooffice playwright signal-gateway; do - connect_running_service_to_proxy_network "$service" - done + Docker network disconnect "$network" "$proxy_container" >/dev/null 2>&1 || true } report_environment_ready() { From 94055c85d941f7dcd27e63d5fcdcaa2bc9e09fd4 Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Fri, 18 Sep 2026 15:32:47 -0300 Subject: [PATCH 36/63] poc: manage project network attachment with proxy lifecycle Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- .docker/scripts/proxy-coordinator.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.docker/scripts/proxy-coordinator.sh b/.docker/scripts/proxy-coordinator.sh index fd9a3a5..e6c9e70 100755 --- a/.docker/scripts/proxy-coordinator.sh +++ b/.docker/scripts/proxy-coordinator.sh @@ -59,6 +59,7 @@ release() { fi echo 'Releasing shared development proxy lease.' + disconnect_proxy_from_project_network if release_proxy_if_unused; then touch "$release_marker" return 0 @@ -93,7 +94,7 @@ run() { rm -f "$release_marker" trap shutdown INT TERM HUP - connect_project_services + connect_proxy_to_project_network if ! report_environment_ready; then echo 'Could not print environment banner.' >&2 From eaa09363350b03cf74f3f889137ef428be733643 Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Fri, 18 Sep 2026 15:32:54 -0300 Subject: [PATCH 37/63] poc: route Traefik through each Compose project network Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- docker-compose.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docker-compose.yml b/docker-compose.yml index 316467f..a8ceebf 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -65,6 +65,7 @@ services: - SELF_SIGNED_HOST=${COMPOSE_PROJECT_NAME}.localhost labels: - "traefik.enable=true" + - "traefik.docker.network=${COMPOSE_PROJECT_NAME}_default" - "traefik.http.routers.librecode-${COMPOSE_PROJECT_NAME}--nextcloud.rule=Host(`${COMPOSE_PROJECT_NAME}.localhost`)" - "traefik.http.services.librecode-${COMPOSE_PROJECT_NAME}--nextcloud.loadbalancer.server.port=80" proxy-coordinator: @@ -95,6 +96,7 @@ services: - SELF_SIGNED_HOST=${COMPOSE_PROJECT_NAME}-mailpit.localhost labels: - "traefik.enable=true" + - "traefik.docker.network=${COMPOSE_PROJECT_NAME}_default" - "traefik.http.routers.librecode-${COMPOSE_PROJECT_NAME}--mailpit.rule=Host(`${COMPOSE_PROJECT_NAME}-mailpit.localhost`)" - "traefik.http.services.librecode-${COMPOSE_PROJECT_NAME}--mailpit.loadbalancer.server.port=8025" redis: @@ -118,6 +120,7 @@ services: - SELF_SIGNED_HOST=${COMPOSE_PROJECT_NAME}-eurooffice.localhost labels: - "traefik.enable=true" + - "traefik.docker.network=${COMPOSE_PROJECT_NAME}_default" - "traefik.http.routers.librecode-${COMPOSE_PROJECT_NAME}--eurooffice.rule=Host(`${COMPOSE_PROJECT_NAME}-eurooffice.localhost`)" - "traefik.http.services.librecode-${COMPOSE_PROJECT_NAME}--eurooffice.loadbalancer.server.port=80" extra_hosts: @@ -139,6 +142,7 @@ services: - SELF_SIGNED_HOST=${COMPOSE_PROJECT_NAME}-playwright.localhost labels: - "traefik.enable=true" + - "traefik.docker.network=${COMPOSE_PROJECT_NAME}_default" - "traefik.http.routers.librecode-${COMPOSE_PROJECT_NAME}--playwright.rule=Host(`${COMPOSE_PROJECT_NAME}-playwright.localhost`)" - "traefik.http.services.librecode-${COMPOSE_PROJECT_NAME}--playwright.loadbalancer.server.port=9323" command: sleep infinity @@ -161,6 +165,7 @@ services: - SELF_SIGNED_HOST=${COMPOSE_PROJECT_NAME}-signal.localhost labels: - "traefik.enable=true" + - "traefik.docker.network=${COMPOSE_PROJECT_NAME}_default" - "traefik.http.routers.librecode-${COMPOSE_PROJECT_NAME}--signal-gateway.rule=Host(`${COMPOSE_PROJECT_NAME}-signal.localhost`)" - "traefik.http.services.librecode-${COMPOSE_PROJECT_NAME}--signal-gateway.loadbalancer.server.port=8080" whatsapp.web: From 152906741647ad38691359b6b57e6dbb08a350dc Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Fri, 18 Sep 2026 15:33:00 -0300 Subject: [PATCH 38/63] test: use project network in Traefik fixture Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- tests/proxy/fixtures/compose.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/proxy/fixtures/compose.yml b/tests/proxy/fixtures/compose.yml index cb9181f..54d8a50 100644 --- a/tests/proxy/fixtures/compose.yml +++ b/tests/proxy/fixtures/compose.yml @@ -7,6 +7,7 @@ services: - SELF_SIGNED_HOST=${COMPOSE_PROJECT_NAME}.localhost labels: - "traefik.enable=true" + - "traefik.docker.network=${COMPOSE_PROJECT_NAME}_default" - "traefik.http.routers.librecode-${COMPOSE_PROJECT_NAME}--nextcloud.rule=Host(`${COMPOSE_PROJECT_NAME}.localhost`)" - "traefik.http.services.librecode-${COMPOSE_PROJECT_NAME}--nextcloud.loadbalancer.server.port=80" From ad84a3adcb0cfaafd91c0b1af6e96f1fda0bcbf4 Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Fri, 18 Sep 2026 15:33:24 -0300 Subject: [PATCH 39/63] test: cover proxy attachment to project networks Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- tests/proxy/services.bats | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/tests/proxy/services.bats b/tests/proxy/services.bats index 74f5655..1530630 100644 --- a/tests/proxy/services.bats +++ b/tests/proxy/services.bats @@ -13,39 +13,38 @@ setup() { PROJECT_NAME=current } -@test "running service is connected to proxy network" { - service_is_running() { return 0; } - container_for_service() { printf '%s\n' service-container; } +@test "proxy is connected to the current Compose project network" { + compatible_proxy_container() { printf '%s\n' librecode-dev-proxy; } container_networks() { printf '{}\n'; } Docker() { printf 'docker %s\n' "$*" >> "$TEST_LOG" } - connect_running_service_to_proxy_network nginx + connect_proxy_to_project_network - grep -q "^docker network connect $(proxy_network_name) service-container$" "$TEST_LOG" + grep -q '^docker network connect current_default librecode-dev-proxy$' "$TEST_LOG" } -@test "service already on proxy network is not connected twice" { - service_is_running() { return 0; } - container_for_service() { printf '%s\n' service-container; } - container_networks() { printf '{\"%s\":{}}\n' "$(proxy_network_name)"; } +@test "proxy is not connected twice to the project network" { + compatible_proxy_container() { printf '%s\n' librecode-dev-proxy; } + container_networks() { printf '{"current_default":{}}\n'; } Docker() { printf 'docker %s\n' "$*" >> "$TEST_LOG" } - connect_running_service_to_proxy_network nginx + connect_proxy_to_project_network ! grep -q '^docker network connect' "$TEST_LOG" } -@test "stopped service is ignored" { - service_is_running() { return 1; } +@test "project network is disconnected from proxy during release" { + compatible_proxy_container() { printf '%s\n' librecode-dev-proxy; } + container_networks() { printf '{"current_default":{}}\n'; } Docker() { printf 'docker %s\n' "$*" >> "$TEST_LOG" } - connect_running_service_to_proxy_network nginx + disconnect_proxy_from_project_network - [ ! -s "$TEST_LOG" ] + grep -q '^docker network disconnect current_default librecode-dev-proxy$' "$TEST_LOG" } From bcb5e1476e9ef32ce8acf39337d47ab78f8a68a7 Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Fri, 18 Sep 2026 15:34:17 -0300 Subject: [PATCH 40/63] refactor: attach proxy to project networks Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- .docker/scripts/proxy/services.sh | 37 ++++++++++++++++--------------- 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/.docker/scripts/proxy/services.sh b/.docker/scripts/proxy/services.sh index 43d76a0..a2494b8 100644 --- a/.docker/scripts/proxy/services.sh +++ b/.docker/scripts/proxy/services.sh @@ -5,35 +5,36 @@ service_is_running() { grep -qx "$1" } -container_for_service() { - compose ps -q "$1" 2>/dev/null || true +project_network_name() { + printf '%s_default\n' "${PROJECT_NAME:-}" } -connect_to_proxy_network() { - service="$1" - container="$(container_for_service "$service")" - network="$(proxy_network_name)" +connect_proxy_to_project_network() { + proxy_container="$(compatible_proxy_container)" + network="$(project_network_name)" - [ -n "$container" ] || return 0 + [ -n "$proxy_container" ] || return 1 + [ -n "${PROJECT_NAME:-}" ] || return 1 - if container_networks "$container" | grep -q "\"$network\""; then + if container_networks "$proxy_container" | grep -q "\\\"$network\\\""; then return 0 fi - Docker network connect "$network" "$container" + Docker network connect "$network" "$proxy_container" } -connect_running_service_to_proxy_network() { - service="$1" +disconnect_proxy_from_project_network() { + proxy_container="$(compatible_proxy_container)" + network="$(project_network_name)" - service_is_running "$service" || return 0 - connect_to_proxy_network "$service" -} + [ -n "$proxy_container" ] || return 0 + [ -n "${PROJECT_NAME:-}" ] || return 0 + + if ! container_networks "$proxy_container" | grep -q "\\\"$network\\\""; then + return 0 + fi -connect_project_services() { - for service in nginx mailpit eurooffice playwright signal-gateway; do - connect_running_service_to_proxy_network "$service" - done + Docker network disconnect "$network" "$proxy_container" >/dev/null 2>&1 || true } report_environment_ready() { From f3599db138ae4de63b02fc49ceecb9bda9a4d8be Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Fri, 18 Sep 2026 15:34:22 -0300 Subject: [PATCH 41/63] refactor: manage proxy project network lifecycle Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- .docker/scripts/proxy-coordinator.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.docker/scripts/proxy-coordinator.sh b/.docker/scripts/proxy-coordinator.sh index dad5f7c..72f16a0 100755 --- a/.docker/scripts/proxy-coordinator.sh +++ b/.docker/scripts/proxy-coordinator.sh @@ -59,6 +59,7 @@ release() { fi echo 'Releasing shared development proxy lease.' + disconnect_proxy_from_project_network if release_proxy_if_unused; then touch "$release_marker" return 0 @@ -94,7 +95,7 @@ run() { rm -f "$release_marker" trap shutdown INT TERM HUP - connect_project_services + connect_proxy_to_project_network if ! report_environment_ready; then echo 'Could not print environment banner.' >&2 From bd334be02a3cd6c0f5cf55f7bf910219368dd238 Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Fri, 18 Sep 2026 15:34:29 -0300 Subject: [PATCH 42/63] refactor: use coordinator leases for proxy ownership Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- .docker/scripts/proxy/lease.sh | 21 +-------------------- 1 file changed, 1 insertion(+), 20 deletions(-) diff --git a/.docker/scripts/proxy/lease.sh b/.docker/scripts/proxy/lease.sh index b8358a4..3c3b8fb 100644 --- a/.docker/scripts/proxy/lease.sh +++ b/.docker/scripts/proxy/lease.sh @@ -33,27 +33,8 @@ other_proxy_client_is_running() { return 1 } -other_proxy_route_is_running() { - network="$(proxy_network_name)" - proxy_project="$(proxy_project_name)" - - for container in $(Docker ps --filter "network=$network" --format '{{.ID}}'); do - container_project_name="$(container_project "$container")" - - case "$container_project_name" in - "${PROJECT_NAME:-}"|"$proxy_project") - continue - ;; - esac - - [ -z "$(container_virtual_host "$container")" ] || return 0 - done - - return 1 -} - proxy_is_used_by_another_environment() { - other_proxy_client_is_running || other_proxy_route_is_running + other_proxy_client_is_running } wait_for_concurrent_lease() { From 69c6fad72dcfc2fad8c7be7f7d34e4ccadb24428 Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Fri, 18 Sep 2026 15:35:04 -0300 Subject: [PATCH 43/63] refactor: list routes reachable from proxy networks Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- .docker/nginx-proxy/dashboard.tmpl | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/.docker/nginx-proxy/dashboard.tmpl b/.docker/nginx-proxy/dashboard.tmpl index 10246d9..57df750 100644 --- a/.docker/nginx-proxy/dashboard.tmpl +++ b/.docker/nginx-proxy/dashboard.tmpl @@ -1,4 +1,12 @@ {{- $routes := list -}} +{{- $proxyNetworks := dict -}} +{{- range $container := . -}} + {{- if eq (index $container.Labels "coop.librecode.dev-proxy") "true" -}} + {{- range $network := $container.Networks -}} + {{- $_ := set $proxyNetworks $network.Name true -}} + {{- end -}} + {{- end -}} +{{- end -}} {{- range $hostname, $containers := groupByMulti . "Env.VIRTUAL_HOST" "," -}} {{- $hostname = trim $hostname -}} {{- if or (eq $hostname "") (eq $hostname "localhost") (eq $hostname "*.localhost") -}} @@ -6,13 +14,13 @@ {{- end -}} {{- range $container := $containers -}} - {{- $onProxyNetwork := false -}} + {{- $reachableFromProxy := false -}} {{- range $network := $container.Networks -}} - {{- if eq $network.Name "librecode-dev-proxy" -}} - {{- $onProxyNetwork = true -}} + {{- if index $proxyNetworks $network.Name -}} + {{- $reachableFromProxy = true -}} {{- end -}} {{- end -}} - {{- if not $onProxyNetwork -}} + {{- if not $reachableFromProxy -}} {{- continue -}} {{- end -}} From 1ccb9a10eaa3436bdb62ca8201ffe426b8f0d6a7 Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Fri, 18 Sep 2026 15:35:10 -0300 Subject: [PATCH 44/63] test: cover proxy attachment to project networks Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- tests/proxy/services.bats | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/tests/proxy/services.bats b/tests/proxy/services.bats index 74f5655..1530630 100644 --- a/tests/proxy/services.bats +++ b/tests/proxy/services.bats @@ -13,39 +13,38 @@ setup() { PROJECT_NAME=current } -@test "running service is connected to proxy network" { - service_is_running() { return 0; } - container_for_service() { printf '%s\n' service-container; } +@test "proxy is connected to the current Compose project network" { + compatible_proxy_container() { printf '%s\n' librecode-dev-proxy; } container_networks() { printf '{}\n'; } Docker() { printf 'docker %s\n' "$*" >> "$TEST_LOG" } - connect_running_service_to_proxy_network nginx + connect_proxy_to_project_network - grep -q "^docker network connect $(proxy_network_name) service-container$" "$TEST_LOG" + grep -q '^docker network connect current_default librecode-dev-proxy$' "$TEST_LOG" } -@test "service already on proxy network is not connected twice" { - service_is_running() { return 0; } - container_for_service() { printf '%s\n' service-container; } - container_networks() { printf '{\"%s\":{}}\n' "$(proxy_network_name)"; } +@test "proxy is not connected twice to the project network" { + compatible_proxy_container() { printf '%s\n' librecode-dev-proxy; } + container_networks() { printf '{"current_default":{}}\n'; } Docker() { printf 'docker %s\n' "$*" >> "$TEST_LOG" } - connect_running_service_to_proxy_network nginx + connect_proxy_to_project_network ! grep -q '^docker network connect' "$TEST_LOG" } -@test "stopped service is ignored" { - service_is_running() { return 1; } +@test "project network is disconnected from proxy during release" { + compatible_proxy_container() { printf '%s\n' librecode-dev-proxy; } + container_networks() { printf '{"current_default":{}}\n'; } Docker() { printf 'docker %s\n' "$*" >> "$TEST_LOG" } - connect_running_service_to_proxy_network nginx + disconnect_proxy_from_project_network - [ ! -s "$TEST_LOG" ] + grep -q '^docker network disconnect current_default librecode-dev-proxy$' "$TEST_LOG" } From 0225c7c5ec8c5d29d4bffbe6409577767587a6ea Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Fri, 18 Sep 2026 15:35:17 -0300 Subject: [PATCH 45/63] test: keep lease coverage focused on coordinators Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- tests/proxy/lease.bats | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/tests/proxy/lease.bats b/tests/proxy/lease.bats index 8efc6f5..5a121c9 100644 --- a/tests/proxy/lease.bats +++ b/tests/proxy/lease.bats @@ -44,26 +44,6 @@ setup() { [ "$status" -eq 0 ] } -@test "route from another project keeps the proxy leased" { - Docker() { - printf '%s\n' proxy current-route other-route - } - container_project() { - case "$1" in - proxy) proxy_project_name ;; - current-route) printf '%s\n' current ;; - other-route) printf '%s\n' other ;; - esac - } - container_virtual_host() { - [ "$1" = other-route ] && printf '%s\n' other.localhost - } - - run other_proxy_route_is_running - - [ "$status" -eq 0 ] -} - @test "last lease stops the shared proxy with bounded timeout" { PROXY_STOP_TIMEOUT_SECONDS=7 container_networks() { From 8e170af4f84e3b96bc85f2871bed0f49500c3322 Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Fri, 18 Sep 2026 15:35:23 -0300 Subject: [PATCH 46/63] test: verify project network isolation Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- tests/proxy/integration.bats | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/proxy/integration.bats b/tests/proxy/integration.bats index f3d551e..56889f5 100644 --- a/tests/proxy/integration.bats +++ b/tests/proxy/integration.bats @@ -84,6 +84,11 @@ wait_for_https_status() { wait_for_running librecode-dev-proxy wait_for_running librecode-dev-proxy-ssl-companion + proxy_networks="$(docker inspect --format '{{json .NetworkSettings.Networks}}' librecode-dev-proxy)" + backend_networks="$(docker inspect --format '{{json .NetworkSettings.Networks}}' proxytesta-nginx-1)" + [[ "$proxy_networks" == *'"proxytesta_default"'* ]] + [[ "$backend_networks" != *'"librecode-dev-proxy"'* ]] + http_host_ip="$(docker inspect --format '{{(index (index .NetworkSettings.Ports "80/tcp") 0).HostIp}}' librecode-dev-proxy)" https_host_ip="$(docker inspect --format '{{(index (index .NetworkSettings.Ports "443/tcp") 0).HostIp}}' librecode-dev-proxy)" [ "$http_host_ip" = "127.0.0.1" ] From a46cad2b792ba25e67219d09153c0e62bf70ef40 Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Fri, 18 Sep 2026 15:36:41 -0300 Subject: [PATCH 47/63] poc: use coordinator leases for proxy ownership Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- .docker/scripts/proxy/lease.sh | 21 +-------------------- 1 file changed, 1 insertion(+), 20 deletions(-) diff --git a/.docker/scripts/proxy/lease.sh b/.docker/scripts/proxy/lease.sh index b8358a4..3c3b8fb 100644 --- a/.docker/scripts/proxy/lease.sh +++ b/.docker/scripts/proxy/lease.sh @@ -33,27 +33,8 @@ other_proxy_client_is_running() { return 1 } -other_proxy_route_is_running() { - network="$(proxy_network_name)" - proxy_project="$(proxy_project_name)" - - for container in $(Docker ps --filter "network=$network" --format '{{.ID}}'); do - container_project_name="$(container_project "$container")" - - case "$container_project_name" in - "${PROJECT_NAME:-}"|"$proxy_project") - continue - ;; - esac - - [ -z "$(container_virtual_host "$container")" ] || return 0 - done - - return 1 -} - proxy_is_used_by_another_environment() { - other_proxy_client_is_running || other_proxy_route_is_running + other_proxy_client_is_running } wait_for_concurrent_lease() { From 9d41c040b16e97b30a8982689d66e6ad0aa1bde3 Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Fri, 18 Sep 2026 15:36:47 -0300 Subject: [PATCH 48/63] test: keep Traefik lease coverage focused on coordinators Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- tests/proxy/lease.bats | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/tests/proxy/lease.bats b/tests/proxy/lease.bats index 8efc6f5..5a121c9 100644 --- a/tests/proxy/lease.bats +++ b/tests/proxy/lease.bats @@ -44,26 +44,6 @@ setup() { [ "$status" -eq 0 ] } -@test "route from another project keeps the proxy leased" { - Docker() { - printf '%s\n' proxy current-route other-route - } - container_project() { - case "$1" in - proxy) proxy_project_name ;; - current-route) printf '%s\n' current ;; - other-route) printf '%s\n' other ;; - esac - } - container_virtual_host() { - [ "$1" = other-route ] && printf '%s\n' other.localhost - } - - run other_proxy_route_is_running - - [ "$status" -eq 0 ] -} - @test "last lease stops the shared proxy with bounded timeout" { PROXY_STOP_TIMEOUT_SECONDS=7 container_networks() { From 8268cccd3de182cff317949363192103cd0948e2 Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Fri, 18 Sep 2026 15:36:53 -0300 Subject: [PATCH 49/63] test: verify Traefik project network isolation Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- tests/proxy/integration.bats | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/proxy/integration.bats b/tests/proxy/integration.bats index 8e102bf..ea70c39 100644 --- a/tests/proxy/integration.bats +++ b/tests/proxy/integration.bats @@ -91,6 +91,11 @@ wait_for_https_status() { wait_for_running librecode-dev-proxy wait_for_running librecode-dev-dashboard + proxy_networks="$(docker inspect --format '{{json .NetworkSettings.Networks}}' librecode-dev-proxy)" + backend_networks="$(docker inspect --format '{{json .NetworkSettings.Networks}}' proxytesta-nginx-1)" + [[ "$proxy_networks" == *'"proxytesta_default"'* ]] + [[ "$backend_networks" != *'"librecode-dev-proxy"'* ]] + http_host_ip="$(docker inspect --format '{{(index (index .NetworkSettings.Ports "80/tcp") 0).HostIp}}' librecode-dev-proxy)" https_host_ip="$(docker inspect --format '{{(index (index .NetworkSettings.Ports "443/tcp") 0).HostIp}}' librecode-dev-proxy)" [ "$http_host_ip" = "127.0.0.1" ] From bf135ea6a07019900cd7d4fc6f36b4c018727a3d Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Fri, 18 Sep 2026 15:37:04 -0300 Subject: [PATCH 50/63] poc: remove temporary Traefik branch CI trigger Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- .github/workflows/proxy-tests.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/proxy-tests.yml b/.github/workflows/proxy-tests.yml index 48331b2..4392e6e 100644 --- a/.github/workflows/proxy-tests.yml +++ b/.github/workflows/proxy-tests.yml @@ -5,7 +5,6 @@ on: push: branches: - main - - poc/traefik-development-proxy permissions: contents: read From e707c61eae542470ca73736f0b7627022aa978d3 Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Fri, 18 Sep 2026 15:39:43 -0300 Subject: [PATCH 51/63] test: wait for proxy project network attachment Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- tests/proxy/integration.bats | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/tests/proxy/integration.bats b/tests/proxy/integration.bats index 56889f5..6669468 100644 --- a/tests/proxy/integration.bats +++ b/tests/proxy/integration.bats @@ -55,6 +55,19 @@ wait_for_absent() { return 1 } +wait_for_network() { + container="$1" + network="$2" + + for _ in $(seq 1 60); do + networks="$(docker inspect --format '{{json .NetworkSettings.Networks}}' "$container" 2>/dev/null || true)" + [[ "$networks" == *"\"$network\""* ]] && return 0 + sleep 0.5 + done + + return 1 +} + wait_for_https_path_status() { host="$1" path="$2" @@ -84,9 +97,8 @@ wait_for_https_status() { wait_for_running librecode-dev-proxy wait_for_running librecode-dev-proxy-ssl-companion - proxy_networks="$(docker inspect --format '{{json .NetworkSettings.Networks}}' librecode-dev-proxy)" + wait_for_network librecode-dev-proxy proxytesta_default backend_networks="$(docker inspect --format '{{json .NetworkSettings.Networks}}' proxytesta-nginx-1)" - [[ "$proxy_networks" == *'"proxytesta_default"'* ]] [[ "$backend_networks" != *'"librecode-dev-proxy"'* ]] http_host_ip="$(docker inspect --format '{{(index (index .NetworkSettings.Ports "80/tcp") 0).HostIp}}' librecode-dev-proxy)" From 570c712925794452127dc10575b1479bdf4d794a Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Fri, 18 Sep 2026 15:39:50 -0300 Subject: [PATCH 52/63] test: wait for proxy project network attachment Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- tests/proxy/integration.bats | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/tests/proxy/integration.bats b/tests/proxy/integration.bats index ea70c39..f1b4dfd 100644 --- a/tests/proxy/integration.bats +++ b/tests/proxy/integration.bats @@ -55,6 +55,19 @@ wait_for_absent() { return 1 } +wait_for_network() { + container="$1" + network="$2" + + for _ in $(seq 1 60); do + networks="$(docker inspect --format '{{json .NetworkSettings.Networks}}' "$container" 2>/dev/null || true)" + [[ "$networks" == *"\"$network\""* ]] && return 0 + sleep 0.5 + done + + return 1 +} + wait_for_https_path_status() { host="$1" path="$2" @@ -91,9 +104,8 @@ wait_for_https_status() { wait_for_running librecode-dev-proxy wait_for_running librecode-dev-dashboard - proxy_networks="$(docker inspect --format '{{json .NetworkSettings.Networks}}' librecode-dev-proxy)" + wait_for_network librecode-dev-proxy proxytesta_default backend_networks="$(docker inspect --format '{{json .NetworkSettings.Networks}}' proxytesta-nginx-1)" - [[ "$proxy_networks" == *'"proxytesta_default"'* ]] [[ "$backend_networks" != *'"librecode-dev-proxy"'* ]] http_host_ip="$(docker inspect --format '{{(index (index .NetworkSettings.Ports "80/tcp") 0).HostIp}}' librecode-dev-proxy)" From cd3f5290e2ef323c95d18c0fded8c63a3b4d908c Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Fri, 18 Sep 2026 17:22:22 -0300 Subject: [PATCH 53/63] poc: remove nginx-proxy metadata from Traefik services Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- docker-compose.yml | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index a8ceebf..e1ff624 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -60,9 +60,6 @@ services: - ./volumes/nginx/includes:/etc/nginx/conf.d/includes/ - ./volumes/nginx/certs:/certs environment: - - VIRTUAL_HOST=${COMPOSE_PROJECT_NAME}.localhost - - VIRTUAL_PORT=80 - - SELF_SIGNED_HOST=${COMPOSE_PROJECT_NAME}.localhost labels: - "traefik.enable=true" - "traefik.docker.network=${COMPOSE_PROJECT_NAME}_default" @@ -91,9 +88,6 @@ services: mailpit: image: axllent/mailpit:v1.31.1@sha256:98b916bd3c8d61f7633a52d3ea2f58d00620cb01ca57ab59edde68c347a95365 environment: - - VIRTUAL_HOST=${COMPOSE_PROJECT_NAME}-mailpit.localhost - - VIRTUAL_PORT=8025 - - SELF_SIGNED_HOST=${COMPOSE_PROJECT_NAME}-mailpit.localhost labels: - "traefik.enable=true" - "traefik.docker.network=${COMPOSE_PROJECT_NAME}_default" @@ -115,9 +109,6 @@ services: - JWT_SECRET=${JWT_SECRET:-my_jwt_secret} - HOST_UID=${HOST_UID:-1000} - HOST_GID=${HOST_GID:-1000} - - VIRTUAL_HOST=${COMPOSE_PROJECT_NAME}-eurooffice.localhost - - VIRTUAL_PORT=80 - - SELF_SIGNED_HOST=${COMPOSE_PROJECT_NAME}-eurooffice.localhost labels: - "traefik.enable=true" - "traefik.docker.network=${COMPOSE_PROJECT_NAME}_default" @@ -137,9 +128,6 @@ services: - PLAYWRIGHT_BASE_URL=${PLAYWRIGHT_BASE_URL:-https://${COMPOSE_PROJECT_NAME}.localhost} - NODE_ENV=test - CI=${CI:-false} - - VIRTUAL_HOST=${COMPOSE_PROJECT_NAME}-playwright.localhost - - VIRTUAL_PORT=9323 - - SELF_SIGNED_HOST=${COMPOSE_PROJECT_NAME}-playwright.localhost labels: - "traefik.enable=true" - "traefik.docker.network=${COMPOSE_PROJECT_NAME}_default" @@ -160,9 +148,6 @@ services: - ./volumes/signal:/home/.local/share/signal-cli environment: - LOG_LEVEL=info - - VIRTUAL_HOST=${COMPOSE_PROJECT_NAME}-signal.localhost - - VIRTUAL_PORT=8080 - - SELF_SIGNED_HOST=${COMPOSE_PROJECT_NAME}-signal.localhost labels: - "traefik.enable=true" - "traefik.docker.network=${COMPOSE_PROJECT_NAME}_default" From 79fc8801d77680a4699b8a9cdb1ac21bc804a91e Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Fri, 18 Sep 2026 17:22:27 -0300 Subject: [PATCH 54/63] test: remove nginx-proxy metadata from Traefik fixture Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- tests/proxy/fixtures/compose.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/tests/proxy/fixtures/compose.yml b/tests/proxy/fixtures/compose.yml index 54d8a50..b410c58 100644 --- a/tests/proxy/fixtures/compose.yml +++ b/tests/proxy/fixtures/compose.yml @@ -2,9 +2,6 @@ services: nginx: image: nginx:1.31.5-alpine@sha256:72ba65eb42c10344912a84ff42408db7d34f2feb642204570ab8fc5ffd29f1d3 environment: - - VIRTUAL_HOST=${COMPOSE_PROJECT_NAME}.localhost - - VIRTUAL_PORT=80 - - SELF_SIGNED_HOST=${COMPOSE_PROJECT_NAME}.localhost labels: - "traefik.enable=true" - "traefik.docker.network=${COMPOSE_PROJECT_NAME}_default" From d1270877a741203ce208755d1b1696783e288e0d Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Fri, 18 Sep 2026 17:22:32 -0300 Subject: [PATCH 55/63] poc: tighten and simplify Traefik dashboard routing Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- .docker/docker-compose.proxy.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.docker/docker-compose.proxy.yml b/.docker/docker-compose.proxy.yml index aa362d1..525b32f 100644 --- a/.docker/docker-compose.proxy.yml +++ b/.docker/docker-compose.proxy.yml @@ -5,7 +5,7 @@ services: labels: coop.librecode.dev-proxy: "true" traefik.enable: "true" - traefik.http.routers.librecode-api.rule: Path(`/api/http/routers`) + traefik.http.routers.librecode-api.rule: Host(`localhost`) && Path(`/api/http/routers`) traefik.http.routers.librecode-api.priority: "200" traefik.http.routers.librecode-api.service: api@internal command: @@ -37,9 +37,7 @@ services: traefik.http.routers.librecode-dashboard.priority: "100" traefik.http.routers.librecode-dashboard.service: librecode-dashboard traefik.http.routers.librecode-dashboard-catchall.rule: HostRegexp(`[a-z0-9-]+\.localhost`) - traefik.http.routers.librecode-dashboard-catchall.entrypoints: websecure traefik.http.routers.librecode-dashboard-catchall.priority: "1" - traefik.http.routers.librecode-dashboard-catchall.tls: "true" traefik.http.routers.librecode-dashboard-catchall.service: librecode-dashboard traefik.http.routers.librecode-dashboard-catchall.middlewares: librecode-not-found traefik.http.middlewares.librecode-not-found.replacepath.path: /__librecode_not_found__ From bf3e84d42b0fda05cb8056e3bdbf988f893a4613 Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Fri, 18 Sep 2026 17:24:12 -0300 Subject: [PATCH 56/63] poc: keep Traefik API on an internal entrypoint Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- .docker/docker-compose.proxy.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.docker/docker-compose.proxy.yml b/.docker/docker-compose.proxy.yml index 525b32f..21328ef 100644 --- a/.docker/docker-compose.proxy.yml +++ b/.docker/docker-compose.proxy.yml @@ -5,8 +5,9 @@ services: labels: coop.librecode.dev-proxy: "true" traefik.enable: "true" - traefik.http.routers.librecode-api.rule: Host(`localhost`) && Path(`/api/http/routers`) + traefik.http.routers.librecode-api.rule: Path(`/api/http/routers`) traefik.http.routers.librecode-api.priority: "200" + traefik.http.routers.librecode-api.entrypoints: api traefik.http.routers.librecode-api.service: api@internal command: - --api=true @@ -17,6 +18,7 @@ services: - --entrypoints.web.http.redirections.entrypoint.to=websecure - --entrypoints.web.http.redirections.entrypoint.scheme=https - --entrypoints.websecure.address=:443 + - --entrypoints.api.address=:8080 - --entrypoints.websecure.asdefault=true - --entrypoints.websecure.http.tls=true ports: From fcc3273f7864082660adb597028c4d497e9e2db5 Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Fri, 18 Sep 2026 17:24:16 -0300 Subject: [PATCH 57/63] poc: proxy Traefik route metadata through dashboard Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- .docker/traefik/dashboard.conf | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.docker/traefik/dashboard.conf b/.docker/traefik/dashboard.conf index 7680fc7..d49028c 100644 --- a/.docker/traefik/dashboard.conf +++ b/.docker/traefik/dashboard.conf @@ -18,6 +18,11 @@ server { try_files $uri =404; } + location = /api/http/routers { + proxy_pass http://traefik:8080; + proxy_set_header Host localhost; + } + location / { try_files $uri /index.html; } From 555ac89fbe62585cfd50c6db4012a213b7b4b1b0 Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Fri, 18 Sep 2026 17:25:26 -0300 Subject: [PATCH 58/63] poc: keep README aligned with nginx baseline Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7ff5290..21d20d3 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ Start the basic environment with MySQL: docker compose up ``` -For PostgreSQL, optional services, dynamic host ports, multiple environments, +For PostgreSQL, optional services, multiple environments, proxy settings, and other advanced configuration, see the [advanced setup guide](docs/advanced-setup.md). From 12ca266d2372a4f66e44980732ee3cd58337b6d4 Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Fri, 18 Sep 2026 17:25:34 -0300 Subject: [PATCH 59/63] poc: keep environment banner aligned with nginx baseline Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- .docker/scripts/report-environment-ready | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.docker/scripts/report-environment-ready b/.docker/scripts/report-environment-ready index 3ee29bc..c437aff 100644 --- a/.docker/scripts/report-environment-ready +++ b/.docker/scripts/report-environment-ready @@ -33,8 +33,8 @@ emit_banner() { [ -z "${ENV_ADMIN_USER:-}" ] || printf 'β”‚ %-16s %s\n' 'Admin user' "$ENV_ADMIN_USER" [ -z "${ENV_ADMIN_PASSWORD:-}" ] || printf 'β”‚ %-16s %s\n' 'Admin password' "$ENV_ADMIN_PASSWORD" printf 'β”‚\n' - printf 'β”‚ πŸ” HTTPS uses the LibreCode local development CA.\n' - printf 'β”‚ Trust it once if your browser shows a warning.\n' + printf 'β”‚ πŸ” HTTPS uses a local self-signed development certificate.\n' + printf 'β”‚ Your browser may show a warning until you trust the local certificate.\n' printf 'β”‚\n' printf '└──────────────────────────────────────────────────\n' } > /proc/1/fd/2 From f0439755b9247476ab9f4d5c1ea929c840a09d2a Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Fri, 18 Sep 2026 17:25:38 -0300 Subject: [PATCH 60/63] poc: document Traefik Docker daemon access Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- docs/advanced-setup.md | 43 +++++++++--------------------------------- 1 file changed, 9 insertions(+), 34 deletions(-) diff --git a/docs/advanced-setup.md b/docs/advanced-setup.md index 5aeabd4..f8ea2a0 100644 --- a/docs/advanced-setup.md +++ b/docs/advanced-setup.md @@ -61,9 +61,7 @@ docker compose \ ## Multiple environments -Multiple checkouts can run at the same time. - -Docker uses the standard host ports when they are available. If a port is already in use, another available port is selected automatically. +Multiple checkouts can run at the same time. Each checkout keeps its own Compose network, while the shared development proxy connects to the active project networks. Start each environment from its own directory: @@ -77,43 +75,22 @@ cd /path/to/second-checkout docker compose up ``` -To see the ports assigned to an environment: - -```bash -docker compose ps -``` - -To check a specific port, use: - -```bash -docker compose port nginx 80 -``` - -For example, if it returns: +The Compose project name is used to build the local hostnames. With project names `first-checkout` and `second-checkout`, Nextcloud is available at: ```text -127.0.0.1:81 +https://first-checkout.localhost +https://second-checkout.localhost ``` -access Nextcloud at: +Open `https://localhost` to see the active environments and their available service URLs. -```text -http://localhost:81 -``` - -When port `80` is available, Nextcloud remains accessible normally at: - -```text -http://localhost -``` - -A specific host port can still be requested explicitly: +If the directory name creates a long local hostname, use a shorter Compose project name without renaming the directory: ```bash -HTTP_PORT=9000 HTTPS_PORT=9443 docker compose up +COMPOSE_PROJECT_NAME=dev docker compose up ``` -By default, the shared development proxy binds to `127.0.0.1`. To expose only the proxy on other network interfaces, set: +The shared proxy owns host ports `80` and `443` and binds to `127.0.0.1` by default. To expose only the proxy on other network interfaces, set: ```bash PROXY_IP_BIND=0.0.0.0 docker compose up @@ -128,11 +105,9 @@ POSTGRES_IP_BIND=0.0.0.0 DB_HOST=pgsql docker compose up Expose development services only on trusted networks and with an appropriate host firewall. -HTTP and HTTPS use automatic ranges beginning at ports `80` and `443` when no override is provided. The example above forces `host:9000 -> container:80` and `host:9443 -> container:443`. - ## Docker daemon access -The proxy coordinator, reverse proxy, and certificate companion access the Docker daemon as part of the development workflow. This is an intentional trust boundary: code running through these infrastructure components can interact with the local Docker daemon. +The proxy coordinator and Traefik reverse proxy access the Docker daemon as part of the development workflow. This is an intentional trust boundary: code running through these infrastructure components can interact with the local Docker daemon. Application containers do not receive the Docker socket. Use this development environment only with repository code you trust. From b612e674dc7062811891eb3686ddaf13cc98f664 Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Fri, 18 Sep 2026 17:26:13 -0300 Subject: [PATCH 61/63] fix: remove empty environment blocks from docker-compose.yml Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- docker-compose.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index e1ff624..04c155e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -59,7 +59,6 @@ services: - ./volumes/nextcloud:/var/www/html:ro - ./volumes/nginx/includes:/etc/nginx/conf.d/includes/ - ./volumes/nginx/certs:/certs - environment: labels: - "traefik.enable=true" - "traefik.docker.network=${COMPOSE_PROJECT_NAME}_default" @@ -87,7 +86,6 @@ services: - nextcloud mailpit: image: axllent/mailpit:v1.31.1@sha256:98b916bd3c8d61f7633a52d3ea2f58d00620cb01ca57ab59edde68c347a95365 - environment: labels: - "traefik.enable=true" - "traefik.docker.network=${COMPOSE_PROJECT_NAME}_default" From 024beb94eaf62e60d0668ae68d1b1b45f2dde65f Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Fri, 18 Sep 2026 17:26:18 -0300 Subject: [PATCH 62/63] fix: remove empty environment blocks from tests/proxy/fixtures/compose.yml Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- tests/proxy/fixtures/compose.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/proxy/fixtures/compose.yml b/tests/proxy/fixtures/compose.yml index b410c58..a0fae0e 100644 --- a/tests/proxy/fixtures/compose.yml +++ b/tests/proxy/fixtures/compose.yml @@ -1,7 +1,6 @@ services: nginx: image: nginx:1.31.5-alpine@sha256:72ba65eb42c10344912a84ff42408db7d34f2feb642204570ab8fc5ffd29f1d3 - environment: labels: - "traefik.enable=true" - "traefik.docker.network=${COMPOSE_PROJECT_NAME}_default" From 5574f0a5808b4ed2c1f73a5807bff84bf0ab9d79 Mon Sep 17 00:00:00 2001 From: Vitor Mattos Date: Fri, 18 Sep 2026 17:26:59 -0300 Subject: [PATCH 63/63] test: verify proxy certificate matches project hostname Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- tests/proxy/integration.bats | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/proxy/integration.bats b/tests/proxy/integration.bats index f1b4dfd..95f36fc 100644 --- a/tests/proxy/integration.bats +++ b/tests/proxy/integration.bats @@ -98,6 +98,12 @@ wait_for_https_status() { wait_for_https_path_status "$1" / "$2" } +certificate_matches_host() { + host="$1" + openssl s_client -connect 127.0.0.1:443 -servername "$host" /dev/null | + openssl x509 -noout -checkhost "$host" >/dev/null +} + @test "single project starts routing and releases the shared proxy" { compose_test proxytesta up --detach @@ -129,6 +135,7 @@ wait_for_https_status() { grep -q '"runc":"' "$BODY" wait_for_https_status proxytesta.localhost 200 + certificate_matches_host proxytesta.localhost grep -q 'Welcome to nginx' "$BODY" wait_for_https_status something-wrong.localhost 404