Avoid dev port clashes and isolate docker compose per git worktree (#203)

* Avoid dev port clashes and isolate docker compose per git worktree

Default dev ports (80/443/5432) clash with other projects' compose
stacks. Remap them to 8080/8443/5433+ by default, and add `just init`
(auto-run by `just up`) to generate a per-worktree `.env.local` with a
unique COMPOSE_PROJECT_NAME, image tag, and free host ports, so
multiple worktrees can run `just up` concurrently without sharing
containers, volumes, images, or ports.

* Pin CI to port 80 for the HTTP reachability check; use 5430 as default Postgres dev port

CI runs in an isolated single-purpose runner with no port-clash concern,
so pin the HTTP reachability check back to port 80 explicitly rather
than changing the dev default.

Also move the default dev Postgres port range from 5433 to 5430, since
5433 clashes with other commonly used local projects.

* Fix Justfile init: propagate free_port failures and use portable hash

free_port exhaustion was swallowed inside a command substitution used as
an echo argument, silently writing empty ports to .env.local. shasum is
also not guaranteed on stripped-down Linux hosts.
This commit is contained in:
2026-07-10 18:14:39 +02:00
committed by GitHub
parent 2fd15ba8fa
commit 33a0e8a584
5 changed files with 95 additions and 9 deletions
+62 -1
View File
@@ -1,9 +1,70 @@
up *args:
# Load per-worktree overrides (project name, image tag, ports) generated by `just init`
set dotenv-load := true
set dotenv-filename := ".env.local"
# Generate a per-worktree COMPOSE_PROJECT_NAME, IMAGES_PREFIX and free host ports in .env.local,
# so multiple worktrees/checkouts of this repo can run `just up` at the same time without their
# containers, volumes, images or ports colliding. Safe to re-run; no-ops if already configured.
init:
#!/usr/bin/env bash
set -euo pipefail
if [ -f .env.local ] && grep -q '^COMPOSE_PROJECT_NAME=' .env.local; then
echo ".env.local already configured for this worktree, skipping (delete it to regenerate)."
exit 0
fi
free_port() {
local port="$1" max="$2"
while [ "$port" -le "$max" ]; do
if ! (exec 3<>/dev/tcp/127.0.0.1/"$port") 2>/dev/null; then
echo "$port"
return
fi
exec 3>&- 2>/dev/null || true
port=$((port + 1))
done
echo "no free port found between $1 and $2" >&2
exit 1
}
if command -v sha1sum >/dev/null 2>&1; then
hash_cmd=(sha1sum)
elif command -v shasum >/dev/null 2>&1; then
hash_cmd=(shasum)
else
hash_cmd=(sha256sum)
fi
hash=$(pwd | "${hash_cmd[@]}" | cut -c1-8)
project="tvdt-${hash}"
http_port=$(free_port 8080 8179)
https_port=$(free_port 8443 8542)
postgres_port=$(free_port 5430 5529)
mailpit_port=$(free_port 8025 8124)
spotlight_port=$(free_port 8969 9068)
{
echo "COMPOSE_PROJECT_NAME=${project}"
echo "IMAGES_PREFIX=${project}-"
echo "HTTP_PORT=${http_port}"
echo "HTTPS_PORT=${https_port}"
echo "POSTGRES_PORT=${postgres_port}"
echo "MAILPIT_PORT=${mailpit_port}"
echo "SPOTLIGHT_PORT=${spotlight_port}"
} >> .env.local
echo "Generated .env.local for this worktree:"
cat .env.local
up *args: init
#!/usr/bin/env bash
set -a
[ -f .env.local ] && source .env.local
set +a
docker compose up -d {{ args }}
down *args:
docker compose down --remove-orphans {{ args }}
# Show the host ports assigned to this worktree (see `just init`)
ports:
@cat .env.local 2>/dev/null || echo "No .env.local yet, run 'just up' or 'just init' first."
stop:
docker compose stop