From 47da47d8e03c019e587cb2e568b74e008c365690 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Fri, 7 Aug 2026 18:18:58 +0200 Subject: [PATCH] fix(docker): escape Traefik www redirect replacements for Compose Double-escape ${n} in Traefik redirectregex.replacement labels so Docker Compose emits $${n} and Traefik still receives the correct capture groups. --- bootstrap/helpers/docker.php | 4 ++-- tests/Feature/ApplicationParserDockerComposeDomainsTest.php | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/bootstrap/helpers/docker.php b/bootstrap/helpers/docker.php index 52783ff4d..b688ffbb8 100644 --- a/bootstrap/helpers/docker.php +++ b/bootstrap/helpers/docker.php @@ -555,12 +555,12 @@ function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_ $to_non_www_name = "{$loop}-{$uuid}-to-non-www"; $redirect_to_non_www = [ "traefik.http.middlewares.{$to_non_www_name}.redirectregex.regex=^(http|https)://www\.(.+)", - "traefik.http.middlewares.{$to_non_www_name}.redirectregex.replacement=\${1}://\${2}", + "traefik.http.middlewares.{$to_non_www_name}.redirectregex.replacement=\$\${1}://\$\${2}", "traefik.http.middlewares.{$to_non_www_name}.redirectregex.permanent=false", ]; $redirect_to_www = [ "traefik.http.middlewares.{$to_www_name}.redirectregex.regex=^(http|https)://(?:www\.)?(.+)", - "traefik.http.middlewares.{$to_www_name}.redirectregex.replacement=\${1}://www.\${2}", + "traefik.http.middlewares.{$to_www_name}.redirectregex.replacement=\$\${1}://www.\$\${2}", "traefik.http.middlewares.{$to_www_name}.redirectregex.permanent=false", ]; if ($schema === 'https') { diff --git a/tests/Feature/ApplicationParserDockerComposeDomainsTest.php b/tests/Feature/ApplicationParserDockerComposeDomainsTest.php index eca364238..539dba5f2 100644 --- a/tests/Feature/ApplicationParserDockerComposeDomainsTest.php +++ b/tests/Feature/ApplicationParserDockerComposeDomainsTest.php @@ -301,7 +301,7 @@ YAML, $parsedCompose = applicationParser($application); $labels = collect(data_get($parsedCompose, 'services.frontend.labels')); - expect($labels->contains(fn (string $label): bool => str_contains($label, 'redirectregex.replacement=${1}://www.${2}')))->toBeTrue(); + expect($labels->contains(fn (string $label): bool => str_contains($label, 'redirectregex.replacement=$${1}://www.$${2}')))->toBeTrue(); }); test('compose domain reconciliation preserves stored domains when parsing returns no services', function () {