Skip to content

Frontend Docker image runs the Vite dev server, not a production build #2

@tg12

Description

@tg12

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions