#!/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 serve-static apps) # 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 [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 serve-static core + caddy (production-build reverse proxy) apps core + the three Nx dev servers in Docker — no native Node/pnpm needed (ADR-0030). portal-bff:3000, portal-shell:4200, portal-admin:4300. 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 Stop a single service (containers stay around). Use `up ` (or restart) to bring it back. restart Restart a single service. status docker compose ps, with every profile in scope. logs [service] Follow logs (one service or all of them). exec Run a command inside a running service container. help Show this help. [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 apps ./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