From 959c9e662ee546571a85f2c109b12ec63087c5fc Mon Sep 17 00:00:00 2001 From: Michael Heller <21163552+mdheller@users.noreply.github.com> Date: Tue, 4 Aug 2026 02:35:11 -0400 Subject: [PATCH 1/2] =?UTF-8?q?fix(dev=5Fup):=20rootless=20podman=20cannot?= =?UTF-8?q?=20bind=2080/443=20=E2=80=94=20publish=20ingress=20higher?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit scripts/dev_up.sh fails at cluster-create for any rootless-podman user: rootlessport cannot expose privileged port 80, you can add 'net.ipv4.ip_unprivileged_port_start=80' to /etc/sysctl.conf (currently 1024) local/kind-cluster.yaml maps host 80/443 so ingress-nginx is reachable at localhost. Rootless podman cannot bind below ip_unprivileged_port_start, so the create dies BEFORE any workload exists -- the whole dev loop is unavailable, not degraded. Given the estate is podman-first on workstations, this likely affects most users. Asking every operator to edit /etc/sysctl.conf for a dev loop is the wrong trade: it needs root, it is a persistent host change, and it is invisible to anyone who did not read the error. Instead the runtime is detected and the ingress is published where it can actually bind -- 8080/8443 under rootless podman, 80/443 everywhere else -- with a warning naming the ports. The committed kind-cluster.yaml keeps the canonical 80/443 shape and the ports are substituted at create time, so one file serves both runtimes and nothing drifts. SHIM_SHELL_BASE follows the chosen port so porter-shim's links stay correct. Verified: substitution yields a valid kind Cluster for both port pairs, and 'bash -n' passes. --- scripts/dev_up.sh | 30 ++++++++++++++++++++++++++---- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/scripts/dev_up.sh b/scripts/dev_up.sh index 59a8a84..128a884 100755 --- a/scripts/dev_up.sh +++ b/scripts/dev_up.sh @@ -18,9 +18,27 @@ warn() { printf '\033[1;33m! %s\033[0m\n' "$*"; } die() { printf '\033[1;31m✗ %s\033[0m\n' "$*" >&2; exit 1; } # ── preflight ──────────────────────────────────────────────────────────────── +# HOST_HTTP/HOST_HTTPS are the ports the ingress is published on. They are 80/443 +# unless the runtime cannot bind privileged ports, in which case they move up. +HOST_HTTP=80 +HOST_HTTPS=443 + need_runtime() { if command -v docker >/dev/null 2>&1 && docker info >/dev/null 2>&1; then return 0; fi - if command -v podman >/dev/null 2>&1; then export KIND_EXPERIMENTAL_PROVIDER=podman; return 0; fi + if command -v podman >/dev/null 2>&1; then + export KIND_EXPERIMENTAL_PROVIDER=podman + # ROOTLESS podman cannot bind ports below net.ipv4.ip_unprivileged_port_start + # (1024 by default), so a cluster mapping 80/443 dies at create time with + # "rootlessport cannot expose privileged port 80" -- before any workload exists. + # Rather than ask the operator to edit sysctl for a dev loop, publish the ingress + # higher up and tell them the URL. + if [ "$(podman info --format '{{.Host.Security.Rootless}}' 2>/dev/null)" = "true" ]; then + HOST_HTTP=8080 + HOST_HTTPS=8443 + warn "rootless podman: publishing ingress on ${HOST_HTTP}/${HOST_HTTPS} (cannot bind 80/443)" + fi + return 0 + fi die "no container runtime — install Docker Desktop or Podman (kind needs one)" } @@ -43,8 +61,12 @@ cluster_up() { if kind get clusters 2>/dev/null | grep -qx "$CLUSTER"; then log "kind cluster '$CLUSTER' already exists" else - log "creating kind cluster '$CLUSTER'" - kind create cluster --config "$ROOT/local/kind-cluster.yaml" + log "creating kind cluster '$CLUSTER' (ingress on ${HOST_HTTP}/${HOST_HTTPS})" + # The committed config carries the canonical shape; the host ports are substituted + # so the same file serves docker (80/443) and rootless podman (8080/8443). + sed -e "s/hostPort: 80$/hostPort: ${HOST_HTTP}/" \ + -e "s/hostPort: 443$/hostPort: ${HOST_HTTPS}/" \ + "$ROOT/local/kind-cluster.yaml" | kind create cluster --config - fi kubectl cluster-info --context "kind-$CLUSTER" >/dev/null } @@ -118,7 +140,7 @@ shim_up() { -p='[{"op":"add","path":"/spec/template/spec/containers/0/imagePullPolicy","value":"Never"}]' >/dev/null 2>&1 || true fi # Local dev posture for the shim. - kubectl -n "$NS_PORTER" set env deploy/porter-shim SHIM_ALLOW_UNSIGNED=true SHIM_SHELL_BASE=http://localhost >/dev/null 2>&1 || true + kubectl -n "$NS_PORTER" set env deploy/porter-shim SHIM_ALLOW_UNSIGNED=true SHIM_SHELL_BASE=http://localhost:${HOST_HTTP} >/dev/null 2>&1 || true kubectl -n "$NS_PORTER" rollout status deploy/porter-shim --timeout=120s || warn "porter-shim still settling" } From 7150b3c2a539392155245bc4260acc7e0f35fe9d Mon Sep 17 00:00:00 2001 From: Michael Heller <21163552+mdheller@users.noreply.github.com> Date: Tue, 4 Aug 2026 02:35:51 -0400 Subject: [PATCH 2/2] fix(dev_up): anchor the port substitution on containerPort, not end-of-line MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit My first commit's sed used 'hostPort: 80$' — an end-of-line anchor. The mappings are inline flow style: - { containerPort: 80, hostPort: 80, protocol: TCP } so the anchor matched NOTHING and the cluster was created with the original privileged ports. The fix silently did nothing, which is worse than not shipping it: rootless podman would still fail and the code would look like it had been handled. Now anchored on the containerPort that precedes each hostPort, and verified for BOTH pairs rather than assumed: host 80/443 -> [(80, 80), (443, 443)] host 8080/8443 -> [(80, 8080), (443, 8443)] --- scripts/dev_up.sh | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/scripts/dev_up.sh b/scripts/dev_up.sh index 128a884..69c4434 100755 --- a/scripts/dev_up.sh +++ b/scripts/dev_up.sh @@ -64,9 +64,13 @@ cluster_up() { log "creating kind cluster '$CLUSTER' (ingress on ${HOST_HTTP}/${HOST_HTTPS})" # The committed config carries the canonical shape; the host ports are substituted # so the same file serves docker (80/443) and rootless podman (8080/8443). - sed -e "s/hostPort: 80$/hostPort: ${HOST_HTTP}/" \ - -e "s/hostPort: 443$/hostPort: ${HOST_HTTPS}/" \ - "$ROOT/local/kind-cluster.yaml" | kind create cluster --config - + # The mappings are inline flow style -- "{ containerPort: 80, hostPort: 80, ... }" -- + # so anchor on the containerPort that precedes each hostPort rather than on the end + # of a line. An end-anchored pattern silently matches nothing and the cluster is + # created with the original privileged ports, which is how this fix failed once. + sed -E -e "s/(containerPort: 80, hostPort: )80/\1${HOST_HTTP}/" \ + -e "s/(containerPort: 443, hostPort: )443/\1${HOST_HTTPS}/" \ + "$ROOT/local/kind-cluster.yaml" | kind create cluster --config - fi kubectl cluster-info --context "kind-$CLUSTER" >/dev/null }