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
mkdir -p $APP_DIR
# Free the dev-stack port (3001) and the port the previous deploy used
# (so a new deploy can pick it back up if it's the lowest free port)
PREV_PORT=$(cat .postgrest-port 2>/dev/null || echo "")
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
# Production postgrest always owns port 3010. No dynamic allocation.
POSTGREST_HOST_PORT=3010
export NEXT_PUBLIC_API_URL="http://localhost:$POSTGREST_HOST_PORT"
# Hard-stop the previous stack by name — works even if docker-compose.yml
# is missing or stale from a prior failed deploy.
# Note: multiple --filter name= flags are ANDed; use two separate calls.
# Tear down previous stack: kill containers by name (two separate calls
# because multiple --filter name= flags are ANDed, not ORed).
{
docker ps -aq --filter "name=route_commerce_"
docker ps -aq --filter "name=route-commerce-"
} | sort -u | xargs -r docker rm -f
# Network can only be removed after all containers are detached.
} | sort -u | xargs -r docker rm -f || true
# Remove network after containers are gone so Docker allocates a fresh subnet.
docker network rm route-commerce_default 2>/dev/null || true
sleep 2
# Verify all route-commerce containers are gone before picking a port.
REMAINING=$({ docker ps -aq --filter "name=route_commerce_"; docker ps -aq --filter "name=route-commerce-"; } | sort -u | wc -l)
if [ "$REMAINING" -gt 0 ]; then
echo "ERROR: $REMAINING route-commerce container(s) still running after teardown"
docker ps --filter "name=route_commerce_"
docker ps --filter "name=route-commerce-"
exit 1
fi
# Write fresh .env for docker compose
cp docker-compose.yml $APP_DIR/docker-compose.yml
cat > $APP_DIR/.env <<EOF
POSTGRES_USER=${POSTGRES_USER}
POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
POSTGRES_DB=${POSTGRES_DB}
MINIO_ROOT_USER=${MINIO_ROOT_USER}
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
[ -f .env ] || cp .env.example .env
# Append production secrets to .env (overriding .env.example defaults)
{
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
docker compose up -d db postgrest minio
# Wait for Postgres
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
echo "Postgres is ready"
@@ -152,8 +111,7 @@ jobs:
MINIMAX_BASE_URL: ${{ secrets.MINIMAX_BASE_URL }}
FROM_EMAIL: ${{ secrets.FROM_EMAIL }}
run: |
POSTGREST_HOST_PORT=$(cat .postgrest-port)
export NEXT_PUBLIC_API_URL="http://localhost:$POSTGREST_HOST_PORT"
export NEXT_PUBLIC_API_URL="http://localhost:3010"
npm run build
- name: Deploy
@@ -191,8 +149,7 @@ jobs:
run: |
APP_DIR=/home/tyler/route-commerce
mkdir -p $APP_DIR
# Use the port chosen by Start Docker stack (persisted to .postgrest-port)
POSTGREST_HOST_PORT=$(cat .postgrest-port)
POSTGREST_HOST_PORT=3010
export NEXT_PUBLIC_API_URL="http://localhost:$POSTGREST_HOST_PORT"
# 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_DB_EXTRA_SEARCH_PATH: public,extensions
ports:
- "127.0.0.1:${POSTGREST_HOST_PORT:-3011}:3001"
- "127.0.0.1:${POSTGREST_HOST_PORT:-3010}:3001"
minio:
image: minio/minio:latest