From c001cff9ba9293c0f702cc2074f5e3f28445afec Mon Sep 17 00:00:00 2001 From: James Wampler Date: Sat, 4 Jul 2026 18:25:26 -0700 Subject: [PATCH] Reach QA's published ports from the smoke-qa job container via bridge gateway The runner executes each job in its own container on docker's default bridge network, so "localhost" never resolves to the docker host - smoke-qa's direct Postgres connection and Playwright/API HTTP calls need the bridge gateway IP instead. Bind the db port to that same gateway IP (not 0.0.0.0) so it stays reachable only from sibling containers, not off-box. --- deploy/qa/docker-compose.qa.yml | 9 +++++---- scripts/ci/deploy-qa.sh | 6 ++++++ scripts/ci/lib.sh | 10 ++++++++++ scripts/ci/smoke-qa.sh | 15 +++++++++++---- 4 files changed, 32 insertions(+), 8 deletions(-) diff --git a/deploy/qa/docker-compose.qa.yml b/deploy/qa/docker-compose.qa.yml index 737d9fe..ca1c4f2 100644 --- a/deploy/qa/docker-compose.qa.yml +++ b/deploy/qa/docker-compose.qa.yml @@ -2,9 +2,10 @@ name: miccheck-qa # Dedicated, network-isolated QA stack. Not connected to the dev compose # stack's network - runs entirely on its own bridge network below. The API has -# no published host port; Postgres is bound to 127.0.0.1 only, so the smoke-qa -# CI job (running on this same host) can seed/inspect data directly while it -# stays unreachable off-box. +# no published host port; Postgres is bound to DB_BIND_HOST (docker's bridge +# gateway IP, computed by deploy-qa.sh) so the smoke-qa CI job - itself a +# sibling container on that same default bridge - can seed/inspect data +# directly, while it stays unreachable off-box (unlike binding to 0.0.0.0). services: db: @@ -14,7 +15,7 @@ services: POSTGRES_USER: miccheck POSTGRES_PASSWORD: password ports: - - "127.0.0.1:55432:5432" + - "${DB_BIND_HOST:-127.0.0.1}:55432:5432" volumes: - miccheck-qa-pgdata:/var/lib/postgresql/data networks: diff --git a/scripts/ci/deploy-qa.sh b/scripts/ci/deploy-qa.sh index dffc4f6..91715cb 100755 --- a/scripts/ci/deploy-qa.sh +++ b/scripts/ci/deploy-qa.sh @@ -13,6 +13,12 @@ export API_IMAGE ADMIN_IMAGE export JWT_SECRET_KEY="${JWT_SECRET_KEY:?JWT_SECRET_KEY env var is required}" export QA_ADMIN_PORT="${QA_ADMIN_PORT:-3001}" +# Bind narrowly to docker's bridge gateway IP rather than 0.0.0.0: reachable +# from the smoke-qa job container (a sibling on the default bridge), but not +# exposed on the host's public interface. +export DB_BIND_HOST="$(docker_bridge_gateway)" +[[ -n "$DB_BIND_HOST" ]] || fail "could not determine docker bridge gateway IP to bind the QA db port" + COMPOSE="docker compose -p miccheck-qa -f deploy/qa/docker-compose.qa.yml" log "Pulling latest :qa images" diff --git a/scripts/ci/lib.sh b/scripts/ci/lib.sh index 612b0de..e0c98d6 100755 --- a/scripts/ci/lib.sh +++ b/scripts/ci/lib.sh @@ -111,6 +111,16 @@ ensure_node() { export PATH="$install_dir/bin:$PATH" } +# The IP address on which a container published on 0.0.0.0/ is +# reachable from a sibling container on docker's default bridge network (i.e. +# the docker host's bridge-side address, not its public interface). Used to +# bind QA's db port narrowly - reachable by the smoke-qa job container, not +# exposed off-box the way 0.0.0.0 would be. +docker_bridge_gateway() { + docker network inspect bridge -f '{{(index .IPAM.Config 0).Gateway}}' 2>/dev/null \ + || ip route show default 2>/dev/null | awk '/default/ {print $3; exit}' +} + registry_login() { require_registry_vars [[ -n "$REGISTRY_USER" ]] || fail "REGISTRY_USER env var is required to push images" diff --git a/scripts/ci/smoke-qa.sh b/scripts/ci/smoke-qa.sh index 8713237..3f86530 100755 --- a/scripts/ci/smoke-qa.sh +++ b/scripts/ci/smoke-qa.sh @@ -2,8 +2,11 @@ # Post-deploy validation for the QA environment: runs the API integration # suite (real HTTP + real Postgres, seeding/cleaning up its own data) and the # Playwright admin e2e suite against the just-deployed QA stack. Intended to -# run immediately after deploy-qa.sh, on the same self-hosted qa runner, so -# `localhost` resolves to the deployed containers. +# run immediately after deploy-qa.sh, on the same self-hosted qa runner. This +# job runs in its own job container, a sibling of the QA stack's containers +# on docker's default bridge network - not "localhost" from the host's point +# of view - so it reaches published ports via the bridge gateway IP, same as +# deploy-qa.sh binds the db port to. set -euo pipefail cd "$(dirname "${BASH_SOURCE[0]}")" && source ./lib.sh cd "$CI_ROOT" @@ -12,10 +15,14 @@ ensure_dotnet ensure_node QA_ADMIN_PORT="${QA_ADMIN_PORT:-3001}" -BASE_URL="http://localhost:${QA_ADMIN_PORT}" +CI_HOST="$(docker_bridge_gateway)" +[[ -n "$CI_HOST" ]] || fail "could not determine docker bridge gateway IP to reach the QA stack" +BASE_URL="http://${CI_HOST}:${QA_ADMIN_PORT}" + +log "Resolved docker host as $CI_HOST for reaching the QA stack's published ports" export MICCHECK_API_BASE_URL="$BASE_URL" -export MICCHECK_DB_CONNECTION_STRING="${MICCHECK_DB_CONNECTION_STRING:-Host=localhost;Port=55432;Database=miccheck;Username=miccheck;Password=password}" +export MICCHECK_DB_CONNECTION_STRING="${MICCHECK_DB_CONNECTION_STRING:-Host=$CI_HOST;Port=55432;Database=miccheck;Username=miccheck;Password=password}" log "Running API integration suite against $BASE_URL" dotnet test tests/api/MicCheck.Api.Tests.Integration/MicCheck.Api.Tests.Integration.csproj -c Release --logger trx