fix(ci): use container name 'portainer' instead of IP

All services (Gitea, the 3 runners and Portainer itself) are in the
same Docker network 'portainer_default', so the service name 'portainer'
resolves directly from inside any container in that network. The previous
auto-discovery dance with IPs and routes is no longer needed.

- Set the PORTAINER_URL secret to 'http://portainer:9000'.
- Replace the entire 'Step 0' auto-discovery with a simple check that
  the configured URL is reachable.
- Drop the debug Step 0a that was added during troubleshooting.

The runner's job container can now reach Portainer the same way the
runner itself does — by name.
This commit is contained in:
2026-07-10 12:17:40 +00:00
parent d14b719bd2
commit 687e4d9f2d
+8 -54
View File
@@ -65,64 +65,18 @@ jobs:
- name: Redeploy stack on Portainer - name: Redeploy stack on Portainer
if: github.event_name == 'workflow_dispatch' && inputs.deploy == true if: github.event_name == 'workflow_dispatch' && inputs.deploy == true
env: env:
PORTAINER_URL: ${{ secrets.PORTAINER_URL }}
PORTAINER_TOKEN: ${{ secrets.PORTAINER_TOKEN }} PORTAINER_TOKEN: ${{ secrets.PORTAINER_TOKEN }}
PORTAINER_ENDPOINT_ID: ${{ secrets.PORTAINER_ENDPOINT_ID }} PORTAINER_ENDPOINT_ID: ${{ secrets.PORTAINER_ENDPOINT_ID }}
PORTAINER_STACK_ID: ${{ secrets.PORTAINER_STACK_ID }} PORTAINER_STACK_ID: ${{ secrets.PORTAINER_STACK_ID }}
run: | run: |
# NOTE: no `set -e` here on purpose — the auto-discovery loop below # Portainer, Gitea and the runners all share the `portainer_default`
# needs to try every candidate even if some probes fail, and some # Docker network on the NAS, so the service name "portainer" resolves
# diagnostic commands (ip route, hostname -I) might return empty # directly from the job container. No need for IP auto-discovery.
# in unusual container network setups without aborting the job. echo "Using PORTAINER_URL=$PORTAINER_URL"
echo "hostname: $(hostname)" if ! curl -sS -o /dev/null -w '%{http_code}' --max-time 5 \
echo "hostname -I:" "${PORTAINER_URL}/api/status" | grep -q '^200$'; then
hostname -I 2>/dev/null || echo "(none)" echo "ERROR: Portainer not reachable at $PORTAINER_URL" >&2
echo "ip -4 addr show (eth* only):"
ip -4 addr show 2>/dev/null | grep -E "^[0-9]+:|inet " | head -20
echo "ip route:"
ip route 2>/dev/null
echo "ip route show default:"
ip -4 route show default 2>/dev/null
echo "curl to 192.168.1.30:9000 (5s timeout):"
curl -sS -o /dev/null -w ' HTTP=%{http_code} time=%{time_total}s\n' --max-time 5 "http://192.168.1.30:9000/api/status" 2>&1 || echo " (curl failed)"
echo "---"
echo "--- Step 0: auto-discover a reachable Portainer URL ---"
# The runner creates an ephemeral docker network per job, so the
# canonical PORTAINER_URL (e.g. http://192.168.1.30:9000) often isn't
# reachable from inside the job container. Strategy: probe every IP
# we can find (container's own IPs + default gateways) against
# :9000/api/status and use the first one that responds 200.
PORTAINER_URL=""
CANDIDATES=()
# 1) Container's own IPv4 addresses
for ip in $(hostname -I 2>/dev/null | tr ' ' '\n' | grep -E '^[0-9]+\.'); do
CANDIDATES+=("http://${ip}:9000")
done
# 2) Default gateways of every default route
while IFS= read -r gw; do
[ -n "$gw" ] && CANDIDATES+=("http://${gw}:9000")
done < <(ip -4 route show default 2>/dev/null | awk '{print $3}' | sort -u)
# 3) Fallback: secret value (in case everything else fails)
CANDIDATES+=("${{ secrets.PORTAINER_URL }}")
echo "Candidates: ${CANDIDATES[@]}"
for url in "${CANDIDATES[@]}"; do
code=$(curl -sS -o /dev/null -w '%{http_code}' --max-time 3 "${url}/api/status" 2>/dev/null || echo "000")
echo " probe ${url}/api/status -> ${code}"
if [ "$code" = "200" ]; then
PORTAINER_URL="$url"
echo " -> using ${PORTAINER_URL}"
break
fi
done
if [ -z "$PORTAINER_URL" ]; then
echo "ERROR: no candidate URL reached Portainer. Tried: ${CANDIDATES[@]}" >&2
echo "ERROR: container hostname=$(hostname) IP=$(hostname -I)" >&2
echo "ERROR: ip route=$(ip route 2>&1 | tr '\n' ';')" >&2
exit 1 exit 1
fi fi