Summary
The only shipped frontend container (frontend/Dockerfile) runs the Vite development server (npm run dev) as its production entrypoint instead of building static assets and serving them. package.json defines a working build script that is never used by the Docker image or docker-compose.yml.
Evidence
- frontend/Dockerfile:12:
CMD ["npm", "run", "dev", "--", "--host", "0.0.0.0", "--port", "3000"]
- frontend/package.json:7-9:
"dev": "vite", "build": "tsc && vite build", "preview": "vite preview" — the build and preview scripts exist but are never invoked anywhere in the Docker image, docker-compose.yml, or CI workflow.
- docker-compose.yml:109-123: the
frontend service builds from this Dockerfile, bind-mounts ./frontend/src and ./frontend/index.html into the running container, and exposes port 3000:3000 directly to the host — consistent with a dev-server setup, not a static-asset deployment.
.github/workflows/ci.yml only runs npm run build as a CI check (to catch compile errors); it never produces the artifact that the Docker image actually serves.
- There is only one
docker-compose.yml in the repo (confirmed via find . -iname "*compose*") — there is no separate production/build variant.
Why this matters
- Vite's dev server is explicitly not designed or hardened for production traffic: no asset minification/bundling, no production-grade caching headers, slower page loads, larger memory footprint, and it includes dev-only middleware and HMR machinery that have no place in front of real users.
npm run build, the project's own documented production path, is dead weight from the running application's perspective — it's exercised only in CI as a compile check, never as the thing that actually gets deployed.
- Anyone following
docs/quickstart.md/README.md and running docker compose up gets a dev server, not the production build the project appears to intend, which undermines docs/production-readiness.md's framing of the frontend as build-tested and ready.
Attack or failure scenario
Under any non-trivial concurrent load, the dev server will perform and scale worse than a static build served by nginx/serve, increasing latency and memory pressure on the frontend container disproportionately to traffic. There is also a maintainability trap: a contributor debugging a "production" issue locally via docker compose up is actually debugging dev-server behavior (different module resolution/HMR behavior than the built bundle), which can mask or misrepresent real production-build issues.
Root cause
The Dockerfile was written for local development convenience (bind-mounted source + hot reload) and was never replaced with a multi-stage build that runs npm run build and serves the resulting dist/ directory, even though the rest of the stack (nginx/nginx.conf) is structured as if a built app were being served behind a reverse proxy.
Recommended fix
Convert frontend/Dockerfile to a multi-stage build: stage 1 runs npm ci && npm run build; stage 2 copies the dist/ output into a minimal static server (e.g. nginx:alpine or serve) and serves it on port 3000. Remove the ./frontend/src and ./frontend/index.html bind mounts from the frontend service in docker-compose.yml for that build, or keep a separate docker-compose.dev.yml for local development with hot reload.
Acceptance criteria
docker compose up serves the built, minified frontend bundle, not the Vite dev server.
- A local-development path (hot reload) still exists, but is opt-in (e.g. a separate compose file or
make dev target) rather than the default production path.
docs/production-readiness.md and docs/quickstart.md accurately describe which path is used by default.
Suggested labels
bug, docker, production-readiness
Priority
P2
Severity
Medium — does not expose a specific CVE by itself, but it is a real production-readiness gap: the shipped container does not run the application the way the project's own tooling and documentation imply it does.
Confidence
Confirmed — verified directly from frontend/Dockerfile, frontend/package.json, and docker-compose.yml.
Summary
The only shipped frontend container (frontend/Dockerfile) runs the Vite development server (
npm run dev) as its production entrypoint instead of building static assets and serving them.package.jsondefines a workingbuildscript that is never used by the Docker image ordocker-compose.yml.Evidence
CMD ["npm", "run", "dev", "--", "--host", "0.0.0.0", "--port", "3000"]"dev": "vite","build": "tsc && vite build","preview": "vite preview"— thebuildandpreviewscripts exist but are never invoked anywhere in the Docker image,docker-compose.yml, or CI workflow.frontendservice builds from this Dockerfile, bind-mounts./frontend/srcand./frontend/index.htmlinto the running container, and exposes port3000:3000directly to the host — consistent with a dev-server setup, not a static-asset deployment..github/workflows/ci.ymlonly runsnpm run buildas a CI check (to catch compile errors); it never produces the artifact that the Docker image actually serves.docker-compose.ymlin the repo (confirmed viafind . -iname "*compose*") — there is no separate production/build variant.Why this matters
npm run build, the project's own documented production path, is dead weight from the running application's perspective — it's exercised only in CI as a compile check, never as the thing that actually gets deployed.docs/quickstart.md/README.mdand runningdocker compose upgets a dev server, not the production build the project appears to intend, which underminesdocs/production-readiness.md's framing of the frontend as build-tested and ready.Attack or failure scenario
Under any non-trivial concurrent load, the dev server will perform and scale worse than a static build served by nginx/serve, increasing latency and memory pressure on the frontend container disproportionately to traffic. There is also a maintainability trap: a contributor debugging a "production" issue locally via
docker compose upis actually debugging dev-server behavior (different module resolution/HMR behavior than the built bundle), which can mask or misrepresent real production-build issues.Root cause
The Dockerfile was written for local development convenience (bind-mounted source + hot reload) and was never replaced with a multi-stage build that runs
npm run buildand serves the resultingdist/directory, even though the rest of the stack (nginx/nginx.conf) is structured as if a built app were being served behind a reverse proxy.Recommended fix
Convert
frontend/Dockerfileto a multi-stage build: stage 1 runsnpm ci && npm run build; stage 2 copies thedist/output into a minimal static server (e.g.nginx:alpineorserve) and serves it on port 3000. Remove the./frontend/srcand./frontend/index.htmlbind mounts from thefrontendservice indocker-compose.ymlfor that build, or keep a separatedocker-compose.dev.ymlfor local development with hot reload.Acceptance criteria
docker compose upserves the built, minified frontend bundle, not the Vite dev server.make devtarget) rather than the default production path.docs/production-readiness.mdanddocs/quickstart.mdaccurately describe which path is used by default.Suggested labels
bug, docker, production-readiness
Priority
P2
Severity
Medium — does not expose a specific CVE by itself, but it is a real production-readiness gap: the shipped container does not run the application the way the project's own tooling and documentation imply it does.
Confidence
Confirmed — verified directly from
frontend/Dockerfile,frontend/package.json, anddocker-compose.yml.