Files
apf_portal/infra/local/dev.sh
T
Julien Gautier 7a475a52fb
CI / commits (pull_request) Successful in 1m19s
CI / check (pull_request) Successful in 1m25s
CI / scan (pull_request) Successful in 1m28s
CI / a11y (pull_request) Successful in 1m37s
CI / perf (pull_request) Successful in 3m37s
feat(infra): add dev.sh wrapper for the local-dev compose stack
Direct `docker compose -f infra/local/dev.compose.yml ...` works
fine, but two things kept biting on this stack:

- Compose-profile asymmetry — `down` only operates on services
  whose profile is currently active, so anything brought up with
  `--profile X` keeps running unless the same flag is on `down`.
  pgweb and Jaeger silently survived several `down -v` invocations
  before we spotted it.
- Verbose invocations — typing the compose-file flag and the
  profile flags on every command for routine ops gets old fast.

Add `infra/local/dev.sh` as a thin wrapper:

  ./infra/local/dev.sh up                 # core only
  ./infra/local/dev.sh up all             # core + every profile
  ./infra/local/dev.sh up dbtools         # core + pgweb
  ./infra/local/dev.sh up observability   # core + Jaeger
  ./infra/local/dev.sh down [-v]          # always with all profiles
  ./infra/local/dev.sh stop <service>     # stop one service
  ./infra/local/dev.sh restart <service>  # restart one service
  ./infra/local/dev.sh status             # ps with all profiles
  ./infra/local/dev.sh logs [service]     # follow logs
  ./infra/local/dev.sh exec <svc> <cmd>   # run inside a container

Anything not matching one of the named verbs is passed through to
`docker compose -f dev.compose.yml ...` (with every profile flagged
in), so the full Compose surface remains available without losing
the profile-symmetry guarantee.

Docs:

- `infra/README.md` "Local-dev stack" — new "Convenience script"
  subsection with the cheat-sheet table; "First-time setup" walk-
  through rewritten to use the script; the previous standalone
  "Profiles must match on `down` as on `up`" tip is collapsed
  into a one-liner since the script handles it.
- `docs/development.md` §3 — point at the script for the typical
  setup flow.

The compose file itself is unchanged.
2026-05-09 21:45:54 +02:00

150 lines
4.1 KiB
Bash
Executable File

#!/usr/bin/env bash
# Convenience wrapper around infra/local/dev.compose.yml. Documented
# in infra/README.md → "Local-dev stack" → "Convenience script".
#
# Hides the Compose-profile gotcha: `docker compose down` only acts
# on services whose profile is currently active, so anything brought
# up with `--profile X` keeps running unless the same flag is on
# `down`. Every teardown / status / log command in this script
# always includes every profile in scope, so profile-gated services
# are visible and stoppable.
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
COMPOSE_FILE="${SCRIPT_DIR}/dev.compose.yml"
# Profiles defined in dev.compose.yml. Keep in sync if a new profile
# is added.
ALL_PROFILES=(dbtools observability)
# Build "--profile p1 --profile p2 …" as separate arguments.
build_all_profile_flags() {
local p
ALL_PROFILE_FLAGS=()
for p in "${ALL_PROFILES[@]}"; do
ALL_PROFILE_FLAGS+=(--profile "$p")
done
}
# Run docker compose with every profile flagged in.
dc_all() {
build_all_profile_flags
docker compose -f "$COMPOSE_FILE" "${ALL_PROFILE_FLAGS[@]}" "$@"
}
# Run docker compose with no profiles (core services only).
dc_core() {
docker compose -f "$COMPOSE_FILE" "$@"
}
# Run `up -d` with a custom set of profiles.
up_with_profiles() {
local p
local -a flags=()
for p in "$@"; do
flags+=(--profile "$p")
done
docker compose -f "$COMPOSE_FILE" "${flags[@]}" up -d
}
usage() {
cat <<'USAGE'
Usage: ./infra/local/dev.sh <command> [args]
Commands:
up [target...] Bring up the stack.
Targets:
(none) core only (postgres + redis + otel-collector)
all core + all profiles
dbtools core + pgweb
observability core + jaeger
Multiple targets allowed (e.g. `up dbtools observability`).
down [-v] Tear the stack down. Always runs with every
profile in scope, so profile-gated services
(pgweb, jaeger) are caught too. -v also wipes
the named Docker volumes.
stop <service> Stop a single service (containers stay around).
Use `up <service>` (or restart) to bring it back.
restart <service> Restart a single service.
status docker compose ps, with every profile in scope.
logs [service] Follow logs (one service or all of them).
exec <service> <cmd> Run a command inside a running service container.
help Show this help.
<other> [args...] Anything else is passed through to
`docker compose -f dev.compose.yml ...` with
every profile in scope.
Examples:
./infra/local/dev.sh up
./infra/local/dev.sh up all
./infra/local/dev.sh up observability
./infra/local/dev.sh down -v
./infra/local/dev.sh stop pgweb
./infra/local/dev.sh logs otel-collector
./infra/local/dev.sh exec postgres psql -U "$POSTGRES_USER" -d "$POSTGRES_DB"
USAGE
}
require_args() {
local verb="$1"
local n="$2"
local hint="$3"
if [[ $# -lt $((3 + n)) ]]; then
echo "Error: '$verb' requires $hint." >&2
echo "Try: ./infra/local/dev.sh help" >&2
exit 64
fi
}
cmd="${1:-help}"
[[ $# -gt 0 ]] && shift
case "$cmd" in
up | start)
if [[ $# -eq 0 ]]; then
dc_core up -d
elif [[ "$1" == "all" ]]; then
dc_all up -d
else
up_with_profiles "$@"
fi
;;
down)
dc_all down "$@"
;;
stop)
require_args stop 1 "a service name (use 'down' to tear down the whole stack)" "$@"
dc_all stop "$@"
;;
restart)
require_args restart 1 "a service name" "$@"
dc_all restart "$@"
;;
status | ps)
dc_all ps "$@"
;;
logs)
dc_all logs -f "$@"
;;
exec)
require_args exec 2 "a service name and a command" "$@"
dc_all exec "$@"
;;
help | -h | --help | "")
usage
;;
*)
# Pass-through to docker compose, with profiles in scope.
dc_all "$cmd" "$@"
;;
esac