# Dev loop — hot reload Both stacks point at the same PostgreSQL and the same rows. The only difference is how fast a change becomes running code. ## Numbers this buys | Change | Built stack | Dev stack | |---|---|---| | Go source (`.go`) | ~110 s — full image rebuild: `go mod download` + cold `go build` | **~1 s** — `air` partial rebuild + restart | | POS / ERP (`src/`) | `vite build` + nginx image rebuild | **HMR** — save and the page updates | | B2B (`app/`) | `nuxt build`, ~200 s | **HMR** — `nuxt dev` | The first `up --build` after this is still slow: it populates the Go module and build caches. They are named volumes, so it is slow once. ## Switching ```sh cd keluargamart-backend # dev loop (hot reload) docker compose -f docker-compose.yml -f docker-compose.dev.yml up -d --build # back to built images docker compose -f docker-compose.yml -f docker-compose.dev.yml down docker compose -f docker-compose.yml -f docker-compose.localbuild.yml up -d --build ``` Only one backend can hold `container_name: keluargamart-backend`, so the two stacks cannot run at once. The `up` replaces it; the `down` releases it. The public sites are unaffected either way. `pos-km`, `erp-km` and `b2b-km` keep serving the built assets from their own nginx containers, whichever stack is up. ## Dev server ports | Service | Host port | Notes | |---|---|---| | `pos-dev` | 5175 | Vite | | `erp-dev` | 5173 | Vite | | `b2b-dev` | 3011 | Nuxt. Not 3001 — ERP's built container already holds 3001, and a collision looks like a successful deploy of the wrong thing | | `backend` | 3004 | Same port as the built stack; they never run together | ## Three things that are not obvious **The proxy target must be a hostname, not `localhost`.** `vite.config.js` in POS and ERP read `VITE_API_PROXY_TARGET` and default to `http://localhost:8080`, which is right for a dev server on a laptop and wrong inside a container — where `localhost` is the container itself and every `/api` call 404s. The dev compose sets it to `http://backend:8080`. B2B already read `NUXT_API_PROXY_TARGET`, so only the variable was needed there. **The dev servers must be on the `keluargamart` network.** The backend is attached to it explicitly with a `backend` alias, because the nginx containers `proxy_pass` to `http://backend:8080` and podman-compose does not apply a service name as an alias on its own. A dev server left on the project default network cannot resolve `backend` at all, and the symptom is a 502 on every `/api` call — which reads as "the API is down" rather than "wrong network". **B2B uses Bun.** It locks with `bun.lock`, so `npm ci` fails outright with `EUSAGE`. The dev service uses `oven/bun`, the same image as its Dockerfile. ## What the dev stack is not `read_only` is off for the backend: a watcher has to write its binary and `build-errors.log`. That is the cost of a save-to-running loop, and it is acceptable on a development host but would not be acceptable in a deployed service. The built stack in `docker-compose.yml` is unchanged and still `read_only: true` with only `/tmp` as tmpfs. `APP_ENV` is forced to `development` in the dev overlay rather than interpolated, so a stray `APP_ENV=production` cannot make the "dev" server report itself as production — `ENVIRONMENT.md` says to stop write-testing the moment a host claims to be production. The variable has no behavioural effect anywhere: it feeds the startup log and the `/health` marker only.