Local Development
What this covers / who it's for. The local dev environment: the Docker stack and its services/ports, the make-first workflow, MinIO-backed object storage, Xdebug, and the concurrency caveats that bite parallel sessions. Everything here runs from
stables-core-app/and everything executes inside Docker.
The make-first rule
Every common dev task has a Makefile target that wraps the right docker compose
exec/run invocation — reach for make before raw docker/artisan/composer.
make help lists all targets grouped by category; make status shows containers, URLs, and key
config at a glance. When you discover a genuinely reusable command with no target, add one (with
a ## help description) in the same change.
make setup # first time: .env from .env.example + APP_KEY + build + start
make up # start all services (detached); make up-fg for foreground logs
make down # stop (volumes preserved); make down-clean wipes volumes
make rebuild # --no-cache rebuild + recreate containers
.env is per-developer and gitignored; .env.example is the committed template with the Docker
service hostnames already filled in. make setup creates it; manually:
cp .env.example .env && make artisan ARGS="key:generate".
Services & ports
Host ports follow the 1XXXX scheme — prepend 1 to the standard port. Inside the Docker
network, containers use the unchanged internal ports (the app reaches postgres on 5432).
| Service | Host URL / port | Purpose |
|---|---|---|
app | http://localhost:18000 | Laravel web server (php artisan serve) |
vite | http://localhost:15173 | Frontend HMR |
mailpit | http://localhost:18025 (UI), 11025 (SMTP) | Mail catcher |
postgres | localhost:15432 | PostgreSQL 18 |
valkey | localhost:16379 | Valkey (Redis-compatible) cache/queue |
minio | http://localhost:19001 (console), 19000 (S3 API) | S3-compatible object storage |
worker | — (no HTTP) | queue:work --tries=3 --timeout=90 — consumes the default queue only |
scheduler | — (no HTTP) | schedule:work — drives all scheduled commands |
minio-init | — (one-shot) | Creates the dev bucket then exits — Exited (0) is normal |
Two runtime notes worth internalizing:
- The local worker only consumes
default. The specialized queues (webhooks,documents,audit,dlp,tasks) are consumed locally only by running Horizon (make artisan ARGS=horizon) — see Observability & ops for why a non-consumed queue is a silent failure. - MinIO backs the env-driven
s3disk (AWS_ENDPOINT=http://minio:9000, bucketstables-local— see.env.example). Helpers:make shell-s3(anmcshell with thelocalalias preconfigured),make s3-reset(drop + recreate the bucket),make logs-s3. Integration tests that need the real S3 driver skip themselves when MinIO is unreachable.
Key targets by group
| Group | Targets |
|---|---|
| Stack | setup up up-fg up-xdebug down down-clean restart(-app/-worker/-scheduler) ps status build rebuild |
| Logs | logs logs-app logs-worker logs-scheduler logs-vite logs-db logs-cache logs-s3 pail |
| Shells | shell shell-worker shell-scheduler shell-db (psql) shell-cache (valkey-cli) shell-s3 (mc) |
| Artisan/Composer | artisan ARGS="..." tinker routes composer ARGS="..." composer-install |
| DB & migrations | migrate migrate-fresh migrate-rollback migrate-status seed test-db — the migrate targets also apply to the test DB and regenerate RLS policies |
| Tests & gates | test-parallel test test-filter FILTER=... test-coverage lint lint-check stan — see Testing & the gates |
| Queue & cache | queue-failed queue-retry queue-flush cache-clear config-clear |
| API artifacts | openapi-export abilities-export |
| Audit ops | audit-verify-chain audit-export-worm audit-verify-worm |
| Frontend | npm ARGS="..." npm-install build-assets |
Xdebug
Installed in docker/Dockerfile.dev but off by default (XDEBUG_MODE=off). Enable
per-session with make up-xdebug (or XDEBUG_MODE=debug,develop docker compose up). Each
container dials back to a dedicated IDE port — configure three debug configurations:
| Container | IDE listen port |
|---|---|
app | 9003 |
worker | 9004 |
scheduler | 9005 |
Concurrency & environment caveats
- Shared test database. All sessions/worktrees share
stables_testing; concurrent full-suite runs contend and produce failures that look like isolation bugs. Stagger full-suite runs across parallel sessions. (make test-paralleladditionally provisions per-workerstables_testing_test_{token}databases — within one run, workers are isolated.) - Root-owned files. Files created inside a container are root-owned on the host —
chownthem before editing. - Secondary git worktrees.
makemay not resolve paths from a worktree —docker execinto the running app container directly (the repo is mounted at/app). - Production Docker files are out of bounds for local work:
docker/production/is used exclusively by the production CI/CD build.