fix: use fixed port 3010 for production postgrest, remove dynamic allocation
Deploy to route.crispygoat.com / deploy (push) Has been cancelled
Build / build (push) Has been cancelled

Dynamic port leasing was the root cause of all the "address already in use"
failures. Production now always uses 3010; dev still uses 3001. No more
.postgrest-port file, no more port scanning loop, no more race conditions.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
tyler
2026-06-05 19:12:06 -06:00
parent 7dd29cc44f
commit 803cc5470d
2 changed files with 25 additions and 68 deletions
+24 -67
View File
@@ -29,77 +29,36 @@ jobs:
APP_DIR=/home/tyler/route-commerce APP_DIR=/home/tyler/route-commerce
mkdir -p $APP_DIR mkdir -p $APP_DIR
# Free the dev-stack port (3001) and the port the previous deploy used # Production postgrest always owns port 3010. No dynamic allocation.
# (so a new deploy can pick it back up if it's the lowest free port) POSTGREST_HOST_PORT=3010
PREV_PORT=$(cat .postgrest-port 2>/dev/null || echo "") export NEXT_PUBLIC_API_URL="http://localhost:$POSTGREST_HOST_PORT"
for port in 3001 $PREV_PORT; do
if [ -n "$port" ] && ss -tln 2>/dev/null | grep -qE "[[:space:]]127\.0\.0\.1:${port}[[:space:]]"; then
echo "Port $port in use, freeing..."
fuser -k -9 $port/tcp 2>/dev/null || true
docker ps -aq --filter "publish=$port" 2>/dev/null | xargs -r docker rm -f 2>/dev/null || true
fi
done
# Hard-stop the previous stack by name — works even if docker-compose.yml # Tear down previous stack: kill containers by name (two separate calls
# is missing or stale from a prior failed deploy. # because multiple --filter name= flags are ANDed, not ORed).
# Note: multiple --filter name= flags are ANDed; use two separate calls.
{ {
docker ps -aq --filter "name=route_commerce_" docker ps -aq --filter "name=route_commerce_"
docker ps -aq --filter "name=route-commerce-" docker ps -aq --filter "name=route-commerce-"
} | sort -u | xargs -r docker rm -f } | sort -u | xargs -r docker rm -f || true
# Network can only be removed after all containers are detached. # Remove network after containers are gone so Docker allocates a fresh subnet.
docker network rm route-commerce_default 2>/dev/null || true docker network rm route-commerce_default 2>/dev/null || true
sleep 2
# Verify all route-commerce containers are gone before picking a port. # Write fresh .env for docker compose
REMAINING=$({ docker ps -aq --filter "name=route_commerce_"; docker ps -aq --filter "name=route-commerce-"; } | sort -u | wc -l) cp docker-compose.yml $APP_DIR/docker-compose.yml
if [ "$REMAINING" -gt 0 ]; then cat > $APP_DIR/.env <<EOF
echo "ERROR: $REMAINING route-commerce container(s) still running after teardown" POSTGRES_USER=${POSTGRES_USER}
docker ps --filter "name=route_commerce_" POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
docker ps --filter "name=route-commerce-" POSTGRES_DB=${POSTGRES_DB}
exit 1 MINIO_ROOT_USER=${MINIO_ROOT_USER}
fi MINIO_ROOT_PASSWORD=${MINIO_ROOT_PASSWORD}
POSTGREST_JWT_SECRET=${POSTGREST_JWT_SECRET}
POSTGREST_HOST_PORT=${POSTGREST_HOST_PORT}
NEXT_PUBLIC_API_URL=${NEXT_PUBLIC_API_URL}
EOF
# Find the first free host port starting from 3011. Persist the choice
# so the Build and Deploy steps below can use the same URL.
POSTGREST_HOST_PORT=3011
for attempt in 1 2 3 4 5 6 7 8 9 10; do
if ! ss -tln 2>/dev/null | grep -qE "[[:space:]]127\.0\.0\.1:${POSTGREST_HOST_PORT}[[:space:]]"; then
break
fi
echo "Port $POSTGREST_HOST_PORT in use, trying next... (attempt $attempt)"
POSTGREST_HOST_PORT=$((POSTGREST_HOST_PORT + 1))
if [ $POSTGREST_HOST_PORT -gt 30200 ]; then
echo "ERROR: no free port in 3011-30200 range"
exit 1
fi
sleep 1
done
echo "Using PostgREST host port: $POSTGREST_HOST_PORT"
echo "$POSTGREST_HOST_PORT" > .postgrest-port
export NEXT_PUBLIC_API_URL="http://localhost:$POSTGREST_HOST_PORT"
export POSTGREST_HOST_PORT
# Seed config files into APP_DIR if missing (they live in the repo, not in APP_DIR)
[ -f $APP_DIR/.env.example ] || cp .env.example $APP_DIR/.env.example
[ -f $APP_DIR/docker-compose.yml ] || cp docker-compose.yml $APP_DIR/docker-compose.yml
cd $APP_DIR cd $APP_DIR
[ -f .env ] || cp .env.example .env docker compose up -d db postgrest minio
# Append production secrets to .env (overriding .env.example defaults)
{ # Wait for Postgres
echo "POSTGRES_USER=${POSTGRES_USER}"
echo "POSTGRES_PASSWORD=${POSTGRES_PASSWORD}"
echo "POSTGRES_DB=${POSTGRES_DB}"
echo "MINIO_ROOT_USER=${MINIO_ROOT_USER}"
echo "MINIO_ROOT_PASSWORD=${MINIO_ROOT_PASSWORD}"
echo "POSTGREST_JWT_SECRET=${POSTGREST_JWT_SECRET}"
echo "POSTGREST_HOST_PORT=$POSTGREST_HOST_PORT"
echo "NEXT_PUBLIC_API_URL=$NEXT_PUBLIC_API_URL"
} >> .env
# Bring the stack up fresh — --force-recreate ensures no stale
# network/container references from prior failed attempts
docker compose up -d --force-recreate db postgrest minio minio_init
# Wait for Postgres healthcheck
for i in $(seq 1 30); do for i in $(seq 1 30); do
if docker compose exec -T db pg_isready -U "${POSTGRES_USER}" -d "${POSTGRES_DB}" > /dev/null 2>&1; then if docker compose exec -T db pg_isready -U "${POSTGRES_USER}" -d "${POSTGRES_DB}" > /dev/null 2>&1; then
echo "Postgres is ready" echo "Postgres is ready"
@@ -152,8 +111,7 @@ jobs:
MINIMAX_BASE_URL: ${{ secrets.MINIMAX_BASE_URL }} MINIMAX_BASE_URL: ${{ secrets.MINIMAX_BASE_URL }}
FROM_EMAIL: ${{ secrets.FROM_EMAIL }} FROM_EMAIL: ${{ secrets.FROM_EMAIL }}
run: | run: |
POSTGREST_HOST_PORT=$(cat .postgrest-port) export NEXT_PUBLIC_API_URL="http://localhost:3010"
export NEXT_PUBLIC_API_URL="http://localhost:$POSTGREST_HOST_PORT"
npm run build npm run build
- name: Deploy - name: Deploy
@@ -191,8 +149,7 @@ jobs:
run: | run: |
APP_DIR=/home/tyler/route-commerce APP_DIR=/home/tyler/route-commerce
mkdir -p $APP_DIR mkdir -p $APP_DIR
# Use the port chosen by Start Docker stack (persisted to .postgrest-port) POSTGREST_HOST_PORT=3010
POSTGREST_HOST_PORT=$(cat .postgrest-port)
export NEXT_PUBLIC_API_URL="http://localhost:$POSTGREST_HOST_PORT" export NEXT_PUBLIC_API_URL="http://localhost:$POSTGREST_HOST_PORT"
# Write env file from secrets (preserves existing .env for docker compose) # Write env file from secrets (preserves existing .env for docker compose)
+1 -1
View File
@@ -36,7 +36,7 @@ services:
PGRST_OPENAPI_MODE: disabled PGRST_OPENAPI_MODE: disabled
PGRST_DB_EXTRA_SEARCH_PATH: public,extensions PGRST_DB_EXTRA_SEARCH_PATH: public,extensions
ports: ports:
- "127.0.0.1:${POSTGREST_HOST_PORT:-3011}:3001" - "127.0.0.1:${POSTGREST_HOST_PORT:-3010}:3001"
minio: minio:
image: minio/minio:latest image: minio/minio:latest