feat(infra): add dev.sh wrapper for the local-dev compose stack #68
+6
-5
@@ -99,7 +99,7 @@ cd apps/portal-bff && pnpm exec prisma generate && cd ../..
|
|||||||
pnpm nx run-many -t lint test build
|
pnpm nx run-many -t lint test build
|
||||||
```
|
```
|
||||||
|
|
||||||
For the BFF to actually run end-to-end, you'll also need the local infrastructure stack — Postgres, Redis, OpenTelemetry Collector — provisioned via Docker Compose:
|
For the BFF to actually run end-to-end, you'll also need the local infrastructure stack — Postgres, Redis, OpenTelemetry Collector — provisioned via Docker Compose. A thin wrapper script ([`infra/local/dev.sh`](../infra/local/dev.sh)) hides Compose-profile quirks and gives ergonomic verbs:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 1. Configure infra secrets (copy template, edit, do not commit).
|
# 1. Configure infra secrets (copy template, edit, do not commit).
|
||||||
@@ -108,11 +108,12 @@ $EDITOR infra/local/.env
|
|||||||
# Set strong dev values for POSTGRES_PASSWORD and REDIS_PASSWORD.
|
# Set strong dev values for POSTGRES_PASSWORD and REDIS_PASSWORD.
|
||||||
|
|
||||||
# 2. Bring up the core stack (postgres + redis + otel-collector).
|
# 2. Bring up the core stack (postgres + redis + otel-collector).
|
||||||
docker compose -f infra/local/dev.compose.yml up -d
|
./infra/local/dev.sh up
|
||||||
|
|
||||||
# 3. (Optional) Activate viewers when debugging:
|
# 3. (Optional) Activate viewers when debugging:
|
||||||
docker compose -f infra/local/dev.compose.yml --profile dbtools up -d # pgweb
|
./infra/local/dev.sh up dbtools # adds pgweb (http://localhost:8081)
|
||||||
docker compose -f infra/local/dev.compose.yml --profile observability up -d # Jaeger UI
|
./infra/local/dev.sh up observability # adds Jaeger UI (http://localhost:16686)
|
||||||
|
./infra/local/dev.sh up all # core + every profile
|
||||||
|
|
||||||
# 4. App-side env from .env.example, then fill in DATABASE_URL pointing
|
# 4. App-side env from .env.example, then fill in DATABASE_URL pointing
|
||||||
# to the compose-managed Postgres (matches the values you set in
|
# to the compose-managed Postgres (matches the values you set in
|
||||||
@@ -120,7 +121,7 @@ docker compose -f infra/local/dev.compose.yml --profile observability up -d #
|
|||||||
cp apps/portal-bff/.env.example apps/portal-bff/.env
|
cp apps/portal-bff/.env.example apps/portal-bff/.env
|
||||||
```
|
```
|
||||||
|
|
||||||
Full reference for the local stack — service inventory, port table, persistence, bootstrap re-run procedure — lives in [`infra/README.md`](../infra/README.md) → "Local-dev stack".
|
`./infra/local/dev.sh help` lists the rest of the verbs (`down`, `status`, `logs`, `stop`, `restart`, `exec`). Full reference — service inventory, port table, persistence, bootstrap re-run procedure — lives in [`infra/README.md`](../infra/README.md) → "Local-dev stack".
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
+35
-20
@@ -127,6 +127,7 @@ A Docker Compose recipe spinning up the runtime services the BFF and ADRs assume
|
|||||||
|
|
||||||
| File | Role |
|
| File | Role |
|
||||||
| -------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- |
|
| -------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- |
|
||||||
|
| [`local/dev.sh`](local/dev.sh) | Convenience wrapper around `docker compose` — see "Convenience script" below |
|
||||||
| [`local/dev.compose.yml`](local/dev.compose.yml) | Service definitions: postgres, redis, otel-collector, plus pgweb and jaeger behind profiles |
|
| [`local/dev.compose.yml`](local/dev.compose.yml) | Service definitions: postgres, redis, otel-collector, plus pgweb and jaeger behind profiles |
|
||||||
| [`local/.env.example`](local/.env.example) | Credentials + ports template (copy to `.env`, which is git-ignored) |
|
| [`local/.env.example`](local/.env.example) | Credentials + ports template (copy to `.env`, which is git-ignored) |
|
||||||
| [`local/init/postgres/01-init.sql`](local/init/postgres/01-init.sql) | Bootstrap SQL for ADR-0013: audit roles + schema, applied on first boot only |
|
| [`local/init/postgres/01-init.sql`](local/init/postgres/01-init.sql) | Bootstrap SQL for ADR-0013: audit roles + schema, applied on first boot only |
|
||||||
@@ -135,27 +136,51 @@ A Docker Compose recipe spinning up the runtime services the BFF and ADRs assume
|
|||||||
### First-time setup
|
### First-time setup
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd infra/local
|
|
||||||
|
|
||||||
# 1. Configure local secrets (copy template, edit, do not commit).
|
# 1. Configure local secrets (copy template, edit, do not commit).
|
||||||
cp .env.example .env
|
cp infra/local/.env.example infra/local/.env
|
||||||
$EDITOR .env
|
$EDITOR infra/local/.env
|
||||||
# Set strong dev values for POSTGRES_PASSWORD and REDIS_PASSWORD
|
# Set strong dev values for POSTGRES_PASSWORD and REDIS_PASSWORD
|
||||||
# (defaults in the template are placeholders that the compose
|
# (defaults in the template are placeholders that the compose
|
||||||
# rejects with `must be set in infra/local/.env` if left as-is).
|
# rejects with `must be set in infra/local/.env` if left as-is).
|
||||||
|
|
||||||
# 2. Bring up the core stack (postgres + redis + otel-collector).
|
# 2. Bring up the core stack (postgres + redis + otel-collector).
|
||||||
docker compose -f dev.compose.yml up -d
|
./infra/local/dev.sh up
|
||||||
|
|
||||||
# 3. (Optional) Activate viewers when needed:
|
# 3. (Optional) Activate viewers when needed:
|
||||||
docker compose -f dev.compose.yml --profile dbtools up -d # pgweb
|
./infra/local/dev.sh up dbtools # adds pgweb
|
||||||
docker compose -f dev.compose.yml --profile observability up -d # Jaeger UI
|
./infra/local/dev.sh up observability # adds Jaeger UI
|
||||||
docker compose -f dev.compose.yml --profile dbtools --profile observability up -d # both
|
./infra/local/dev.sh up all # core + every profile
|
||||||
|
|
||||||
# 4. Verify health.
|
# 4. Verify health.
|
||||||
docker compose -f dev.compose.yml ps
|
./infra/local/dev.sh status
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Convenience script — `dev.sh`
|
||||||
|
|
||||||
|
[`local/dev.sh`](local/dev.sh) is a thin wrapper around `docker compose -f dev.compose.yml ...` with two reasons to exist:
|
||||||
|
|
||||||
|
1. **Hides the Compose-profile gotcha.** `docker compose down` only operates on services whose profile is currently active — anything started under `--profile X` keeps running unless the same flag is on `down`. The script always passes every profile in scope on teardown / status / log commands, so profile-gated services (pgweb, Jaeger) are never accidentally orphaned.
|
||||||
|
2. **Ergonomic verbs** for the common workflows. `./dev.sh up all`, `./dev.sh stop pgweb`, `./dev.sh logs otel-collector`, etc.
|
||||||
|
|
||||||
|
Run `./infra/local/dev.sh help` for the full reference. Cheat-sheet:
|
||||||
|
|
||||||
|
| Command | Effect |
|
||||||
|
| ------------------------------------------------------------------------------- | ------------------------------------------------------------ |
|
||||||
|
| `./infra/local/dev.sh up` | Core only (postgres + redis + otel-collector) |
|
||||||
|
| `./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` | Tear down the whole stack (every profile in scope) |
|
||||||
|
| `./infra/local/dev.sh down -v` | Tear down + wipe named volumes (incl. audit-roles bootstrap) |
|
||||||
|
| `./infra/local/dev.sh stop pgweb` | Stop one service (containers stay around) |
|
||||||
|
| `./infra/local/dev.sh status` | `docker compose ps`, with every profile visible |
|
||||||
|
| `./infra/local/dev.sh logs otel-collector` | Follow logs |
|
||||||
|
| `./infra/local/dev.sh exec postgres psql -U "$POSTGRES_USER" -d "$POSTGRES_DB"` | Run a command inside a service |
|
||||||
|
|
||||||
|
Anything not matching one of the named verbs is passed through to `docker compose -f dev.compose.yml ...` (with every profile flagged in), so you keep the full Compose surface available — `./dev.sh config`, `./dev.sh top`, `./dev.sh inspect …`, etc.
|
||||||
|
|
||||||
|
If you prefer to call `docker compose` directly, every example below shows the raw command alongside the script form.
|
||||||
|
|
||||||
### Service endpoints (defaults)
|
### Service endpoints (defaults)
|
||||||
|
|
||||||
| Service | Host port | Purpose |
|
| Service | Host port | Purpose |
|
||||||
@@ -172,17 +197,7 @@ All ports are overridable via `.env` if the host machine has conflicts.
|
|||||||
### Operational tips
|
### Operational tips
|
||||||
|
|
||||||
- **Persistence** — state lives in named Docker volumes (`apf-portal-postgres-data`, `apf-portal-redis-data`). Survives `docker compose down`. Use `docker compose -f dev.compose.yml down -v` to wipe (also wipes the audit-roles bootstrap, which re-runs on the next fresh boot).
|
- **Persistence** — state lives in named Docker volumes (`apf-portal-postgres-data`, `apf-portal-redis-data`). Survives `docker compose down`. Use `docker compose -f dev.compose.yml down -v` to wipe (also wipes the audit-roles bootstrap, which re-runs on the next fresh boot).
|
||||||
- **Profiles must match on `down` as on `up`** — `docker compose down` only acts on services whose profile is currently active. If you brought the stack up with `--profile dbtools --profile observability`, those same flags are required on `down` to also stop pgweb and Jaeger; otherwise they keep running until the next reboot. Two pragmatic patterns:
|
- **Profile symmetry** — `dev.sh down` (and `status`, `logs`, …) always include every profile in scope, so profile-gated services are caught. If you bypass the script and call `docker compose down` directly, you must pass the same `--profile` flags as on `up`, otherwise pgweb and Jaeger keep running silently. Either pass them again, or `export COMPOSE_PROFILES=dbtools,observability` in your shell or `infra/local/.env`.
|
||||||
|
|
||||||
```bash
|
|
||||||
# Either pass the same flags on each command:
|
|
||||||
docker compose -f dev.compose.yml --profile dbtools --profile observability down -v
|
|
||||||
|
|
||||||
# Or set COMPOSE_PROFILES once (e.g. in your shell or in infra/local/.env)
|
|
||||||
# so every `up` / `down` / `ps` sees the right scope:
|
|
||||||
export COMPOSE_PROFILES=dbtools,observability
|
|
||||||
docker compose -f dev.compose.yml down -v
|
|
||||||
```
|
|
||||||
|
|
||||||
- **Bootstrap re-run** — the SQL in `local/init/postgres/` only runs on a **fresh** Postgres data volume. To replay after editing the file, `down -v` (loses all dev data) or run the SQL manually with `docker compose exec postgres psql -U portal -d portal_dev -f /docker-entrypoint-initdb.d/01-init.sql`.
|
- **Bootstrap re-run** — the SQL in `local/init/postgres/` only runs on a **fresh** Postgres data volume. To replay after editing the file, `down -v` (loses all dev data) or run the SQL manually with `docker compose exec postgres psql -U portal -d portal_dev -f /docker-entrypoint-initdb.d/01-init.sql`.
|
||||||
- **Logs** — `docker compose -f dev.compose.yml logs -f <service>` to follow a single service. `otel-collector` is the loudest — its `debug` exporter prints every span / metric / log it receives.
|
- **Logs** — `docker compose -f dev.compose.yml logs -f <service>` to follow a single service. `otel-collector` is the loudest — its `debug` exporter prints every span / metric / log it receives.
|
||||||
|
|||||||
Executable
+149
@@ -0,0 +1,149 @@
|
|||||||
|
#!/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
|
||||||
Reference in New Issue
Block a user