chore: make dev Dockerfiles self-contained, add bindless dev override #10
Loading…
Reference in a new issue
No description provided.
Delete branch "chore/dockerfile-self-contained"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Why
The dev
docker-compose.ymlassumes the Docker daemon can bind-mount the host repo (and several subpaths) at runtime, providing live source forgradle :backend:bootRunand Vite HMR. That works on a normal Linux/macOS host but breaks in:The failure mode is the daemon's mount namespace only sees compose-created named-volume subdirs at the bind source, not the real repo files. The backend then fails with
stat ./gradlew: no such file or directoryand the frontend fails withmount src=.../index.html, dst=.../index.html ... not a directory. The dev images themselves are empty of source — there are noCOPYlines in the dev Dockerfiles.Approach
Make the dev images self-sufficient by
COPY'ing the source at build time. The compose bind mount is kept (still the right thing for normal local dev with HMR), but it's no longer load-bearing. The image works standalone in any environment.Add a separate
docker-compose.dev-bindless.ymlfor environments where host bind mounts can't be used (DinD, CI, restricted Docker). It uses the same images (COPY'd source) but redefines the services with no host bind mounts — only the named cache volumes remain, so gradle and Vite caches persist betweenupcycles.Compose merge semantics caveat:
volumes:lists merge by concatenation, not by entry replacement, so the bindless workflow can't be expressed as a compose override on top ofdocker-compose.yml. A standalone file is required.Changes
docker/backend.Dockerfile— addCOPY gradlew settings.gradle build.gradle ./,COPY gradle/ gradle/,RUN chmod +x gradlew,COPY backend/ backend/,EXPOSE 8080. ENTRYPOINT unchanged.docker/frontend.Dockerfile— already COPY'd the source; addEXPOSE 3000and comments documenting the two-stage COPY pattern..dockerignore— expand to exclude docs, scripts, git, editor config, build outputs, test results, logs, env files, docker metadata. Cuts build context from MBs to ~800 KB.docker-compose.dev-bindless.yml(new) — standalone variant ofdocker-compose.ymlwith all host bind mounts removed. Same service definitions, same image tags, same env vars, same named cache volumes.docker-compose.e2e.yml,docker/*.e2e.Dockerfile) — unchanged. Were already self-contained.Compatibility with existing dev workflow
On a normal host where bind mounts work (the common case):
docker compose up -d(the existing command) keeps working unchanged. The bind mount on.:/appoverlays the COPY'd source at runtime, so HMR andgradle :backend:bootRunhot-reload work exactly like before.node_modules). Acceptable for dev.docker compose buildis slightly slower because it has to COPY the source. Subsequent builds cache well: the COPY layer invalidates only when source files change.Usage
docker compose up -ddocker compose -f docker-compose.dev-bindless.yml up -d --builddocker compose -f docker-compose.e2e.yml up -d(unchanged)Verified
GET /api/vehicles/ABC123 → 404 Inget fordon hittades).docker run --rm bilhej-backend-devanddocker run --rm bilhej-frontend-devboth work without any bind mounts.Risk
Low. The dev compose (
docker-compose.yml) is byte-for-byte unchanged — every existing user on a normal host sees the same workflow. The change is purely additive (one new compose file, plus image-content additions that get overlaid by the existing bind mount). E2E suite, prod stack, CI pipeline all unchanged.Refs: project
AGENTS.mdDocker section,gradle checkpre-commit hook.Why --- The dev compose (docker-compose.yml) assumes the Docker daemon can bind-mount the host repo (and several subpaths) at runtime, providing live source for `gradle :backend:bootRun` and Vite HMR. That works on a normal Linux/macOS host but breaks in: - Docker-in-Docker setups (e.g. the Hermes sandbox used for agent work) - rootless Docker with restricted mount paths - some CI runners The failure mode is the daemon's mount namespace only sees compose-created named-volume subdirs at the bind source, not the real repo files. The backend then fails with `stat ./gradlew: no such file or directory` and the frontend fails with `mount src=.../index.html, dst=.../index.html ... not a directory`. The image itself is empty of source — there are no `COPY` lines in the dev Dockerfiles. Approach -------- Make the dev images self-sufficient by COPYing the source at build time. The compose bind mount is kept (it's still the right thing for normal local dev with HMR), but it's no longer load-bearing. The image works standalone in any environment. Add a separate `docker-compose.dev-bindless.yml` for environments where host bind mounts can't be used (DinD, CI, restricted Docker). It uses the same images (COPY'd source) but redefines the services with no host bind mounts — only the named cache volumes remain, so gradle and Vite caches persist between `up` cycles. Compose merge semantics caveat: `volumes:` lists merge by concatenation, not by entry replacement, so the bindless workflow can't be expressed as a compose override on top of docker-compose.yml. A standalone file is required. Changes ------- * docker/backend.Dockerfile - Add `COPY gradlew settings.gradle build.gradle ./` - Add `COPY gradle/ gradle/` - Add `RUN chmod +x gradlew` - Add `COPY backend/ backend/` - Add `EXPOSE 8080` - Keep ENTRYPOINT unchanged. - New image is runnable with `docker run bilhej-backend-dev` (no bind mount needed) and works under `docker compose up -d` on any host. * docker/frontend.Dockerfile - Add comments documenting the two-stage COPY pattern (deps first for layer cache, then full source). - Keep the existing structure — it already COPYs the source, just wasn't being relied on. Now bind-mount failures (e.g. index.html type mismatch in DinD) don't kill the container; the COPY'd file is already in place. - Add `EXPOSE 3000` (was missing). * .dockerignore - Expand to exclude everything that isn't strictly needed at build or run time: docs, scripts, git, editor config, build outputs, test results, logs, env files, docker-related metadata, etc. - Cuts the build context from ~MBs to ~800 KB (verified). - Image contents are now: gradlew + wrapper, build.gradle, settings, gradle/, backend/ (for backend image); package.json, package-lock, src/, public/, index.html, node_modules (for frontend image). * docker-compose.dev-bindless.yml (new) - Standalone variant of docker-compose.yml with all host bind mounts removed. Same service definitions, same image tags, same env vars, same named cache volumes (pgdata, gradle-cache, backend-gradle- project, backend-build). Only differences: no `.:/app`, no `./frontend/src:/app/src`, no `./frontend/public:/app/public`, no `./frontend/index.html:/app/index.html`. - Usage: `docker compose -f docker-compose.dev-bindless.yml up -d` (no `--build` needed if images already exist; include `--build` on first run or after pulling changes). - Trade-off vs the default dev compose: image is "frozen" at build time, so editing source on the host doesn't trigger HMR. Edit + `docker compose up -d --build` (or just rebuild the relevant service) to pick up changes. Named cache volumes still keep gradle/npm caches warm across rebuilds. * e2e compose (docker-compose.e2e.yml, docker/*.e2e.Dockerfile) — unchanged. They were already self-contained and continue to work as before. Verified by running the full 90/90 Playwright suite in 54s. Compatibility with existing dev workflow ---------------------------------------- On a normal host where bind mounts work (the common case): - `docker compose up -d` (the existing command) keeps working unchanged. The bind mount on `.:/app` overlays the COPY'd source at runtime, so HMR and `gradle :backend:bootRun` hot-reload work exactly like before. - Image size grows (~50 MB backend, ~50 MB frontend on top of base image; ~200 MB including node_modules). Acceptable for dev. - First-time `docker compose build` is slightly slower because it has to COPY the source. Subsequent builds cache well: the COPY layer invalidates only when source files change. Verified -------- - Hermes DinD sandbox: bindless dev stack (`docker-compose.dev- bindless.yml`) brings up postgres + mailpit + backend + frontend with no bind mounts. Spring Boot starts in ~6s, Vite dev server in ~700ms. Backend serves real API responses (`GET /api/vehicles/ABC123 -> 404 Inget fordon hittades`). - Hermes DinD sandbox: e2e stack runs all 90 Playwright tests in ~54s, identical to pre-patch behavior. - Docker image self-sufficiency: `docker run --rm bilhej-backend-dev` and `docker run --rm bilhej-frontend-dev` both work without any bind mounts. Refs: project AGENTS.md (Docker section, gradle check pre-commit).Tack för reviewen — bra catch.
Pushed en fixup:
docker/meddocker/*.Dockerfileså attdocker/nginx.confochdocker/entrypoint.shfortfarande finns i build-context förfrontend.prod.Dockerfile.docker-compose*.ymli exkluderingen.COPY . /app.Verifierat:
docker compose -f docker-compose.prod.yml build frontendgår igenom och bådaCOPY docker/...-stegen slutförs.Kommit:
3d2db14