feat: route paths to non-web processes via proxy:route

Adds proxy:route:set/remove/clear/report so users can declare path-prefix routes to non-web Procfile processes without hand-writing nginx.conf.sigil. Routes are stored on the proxy plugin as a PropertyMap of path to process:port:strip, with set semantics so each invocation produces a fully-determined state and re-application is idempotent. A proxy-supports-routes capability trigger gates the command per backend - nginx-vhosts, traefik-vhosts, caddy-vhosts, and scheduler-k3s render routes today; openresty-vhosts and haproxy-vhosts refuse until their sidecar images learn a path-prefix vocabulary (tracked in dokku/openresty-docker-proxy#137 and the haproxy sidecar). Closes #8725.
This commit is contained in:
Jose Diaz-Gonzalez
2026-06-01 13:48:17 -04:00
parent 2cd558e102
commit ffd5248cc3
29 changed files with 1539 additions and 7 deletions

View File

@@ -14,11 +14,26 @@ trigger-traefik-vhosts-docker-args-process-deploy() {
local proxy_container_https_port proxy_container_https_port_candidate proxy_host_https_port_candidate
local STDIN=$(cat)
if [[ "$PROC_TYPE" != "web" ]]; then
if [[ "$(plugn trigger proxy-type "$APP")" != "traefik" ]]; then
return
fi
if [[ "$(plugn trigger proxy-type "$APP")" != "traefik" ]]; then
if [[ "$PROC_TYPE" != "web" ]]; then
# Non-web processes are only labeled when at least one path-based route
# targets the process. Otherwise traefik should ignore the container.
local route_line route_proc
local has_route=false
while IFS='|' read -r route_proc _ _ _; do
if [[ "$route_proc" == "$PROC_TYPE" ]]; then
has_route=true
break
fi
done < <(plugn trigger proxy-routes-list "$APP")
if [[ "$has_route" != "true" ]]; then
echo -n "$STDIN"
return
fi
fn-traefik-route-labels "$APP" "$PROC_TYPE" "$STDIN"
return
fi

View File

@@ -4,6 +4,87 @@ source "$PLUGIN_CORE_AVAILABLE_PATH/common/property-functions"
set -eo pipefail
[[ $DOKKU_TRACE ]] && set -x
fn-traefik-route-labels() {
declare desc="emits per-route traefik labels for a non-web container"
declare APP="$1" PROC_TYPE="$2" STDIN="$3"
local host_rule output route_proc route_port route_path route_strip
local router_name service_name middleware_name path_len
host_rule="$(fn-traefik-host-rule "$APP")"
output="--label traefik.enable=true"
while IFS='|' read -r route_proc route_port route_path route_strip; do
[[ -z "$route_proc" ]] && continue
[[ "$route_proc" != "$PROC_TYPE" ]] && continue
local route_slug
route_slug="$(fn-traefik-route-slug "$route_path")"
router_name="${APP}-${route_proc}-${route_slug}"
service_name="${APP}-${route_proc}-${route_slug}"
path_len="${#route_path}"
local rule="PathPrefix(\`${route_path}\`)"
if [[ -n "$host_rule" ]]; then
rule="(${host_rule}) && ${rule}"
fi
output="$output --label \"traefik.http.routers.${router_name}.rule=${rule}\""
output="$output --label traefik.http.routers.${router_name}.entrypoints=$(fn-traefik-computed-http-entry-point)"
output="$output --label traefik.http.routers.${router_name}.priority=${path_len}"
output="$output --label traefik.http.routers.${router_name}.service=${service_name}"
output="$output --label traefik.http.services.${service_name}.loadbalancer.server.port=${route_port}"
if [[ "$route_strip" == "1" ]]; then
middleware_name="${router_name}-strip"
output="$output --label traefik.http.middlewares.${middleware_name}.stripprefix.prefixes=${route_path}"
output="$output --label traefik.http.routers.${router_name}.middlewares=${middleware_name}"
fi
done < <(plugn trigger proxy-routes-list "$APP")
local proxy_labels_file_path=$(fn-proxy-get-labels-file-path "traefik" "$APP")
if [[ -f "$proxy_labels_file_path" ]]; then
while read -r line; do
[[ -z "$line" ]] && continue
output="$output --label '$line'"
done <"$proxy_labels_file_path"
fi
echo -n "$STDIN $output"
}
fn-traefik-route-slug() {
declare desc="returns a deterministic kebab-case slug for a route path"
declare ROUTE_PATH="$1"
local s="${ROUTE_PATH#/}"
s="${s%/}"
s="${s//\//-}"
s="${s,,}"
if [[ -z "$s" ]]; then
s="root"
fi
echo "$s"
}
fn-traefik-host-rule() {
declare desc="builds a traefik Host(\`d1\`) || Host(\`d2\`) rule fragment from an app's domains"
declare APP="$1"
local app_domains domain result=""
app_domains="$(plugn trigger domains-list "$APP")"
if [[ -z "$app_domains" ]]; then
return
fi
for domain in $(echo "$app_domains" | xargs); do
if [[ -z "$result" ]]; then
result="Host(\`${domain}\`)"
continue
fi
result="${result} || Host(\`${domain}\`)"
done
echo "$result"
}
fn-traefik-logs() {
declare desc="shows the logs for the traefik container"
declare TAIL="$1" NUM="$2"

View File

@@ -0,0 +1,18 @@
#!/usr/bin/env bash
set -eo pipefail
[[ $DOKKU_TRACE ]] && set -x
source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions"
trigger-traefik-vhosts-proxy-supports-routes() {
declare desc="advertises path-based routing support for traefik"
declare trigger="proxy-supports-routes"
declare APP="$1"
if [[ "$(plugn trigger proxy-type "$APP")" != "traefik" ]]; then
return
fi
echo "true"
}
trigger-traefik-vhosts-proxy-supports-routes "$@"