Multi-stage Dockerfile builds the Vite app in Node 22 and serves static assets from nginx:alpine. Includes gzip compression, SPA fallback routing, immutable cache headers for hashed assets, and configurable port mapping (default 8080). Deploy with `docker compose up -d`.
3.7 KiB
ADDED Requirements
Requirement: Multi-stage Docker build
The Dockerfile SHALL use a multi-stage build where stage 1 installs dependencies and builds the Vite app, and stage 2 copies only the built static assets into an nginx:alpine image.
Scenario: Build produces a working image
- WHEN
docker compose buildis run from the project root - THEN a Docker image is produced that contains only nginx and the built static files from
app/dist/
Scenario: Node.js is not present in the final image
- WHEN the final Docker image is inspected
- THEN it MUST NOT contain Node.js, npm, or
node_modules
Requirement: Single-command deployment with docker compose
The project SHALL include a docker-compose.yml that builds and runs the app with docker compose up.
Scenario: Start the app
- WHEN
docker compose up -dis run from the project root - THEN the app is built (if needed) and served on the configured host port
Scenario: Rebuild after code changes
- WHEN
docker compose up -d --buildis run after source code changes - THEN the image is rebuilt with the latest code and the container is replaced
Requirement: nginx serves the SPA correctly
nginx SHALL serve the app's static files and fall back to index.html for all non-file routes.
Scenario: Root path serves the app
- WHEN a browser requests
/ - THEN nginx responds with
index.htmland HTTP 200
Scenario: Hashed asset files are served
- WHEN a browser requests a path under
/assets/(e.g.,/assets/index-abc123.js) - THEN nginx responds with the file and HTTP 200
Scenario: Unknown paths fall back to index.html
- WHEN a browser requests a path that does not match any file (e.g.,
/some/route) - THEN nginx responds with
index.htmland HTTP 200
Requirement: Gzip compression enabled
nginx SHALL serve gzip-compressed responses for text-based content types.
Scenario: JavaScript files are compressed
- WHEN a browser requests a
.jsfile withAccept-Encoding: gzip - THEN the response MUST include
Content-Encoding: gzip
Scenario: CSS files are compressed
- WHEN a browser requests a
.cssfile withAccept-Encoding: gzip - THEN the response MUST include
Content-Encoding: gzip
Requirement: Cache headers for hashed and unhashed assets
nginx SHALL set long-lived cache headers for content-hashed assets and no-cache for index.html.
Scenario: Hashed assets get immutable caching
- WHEN a browser requests a file under
/assets/ - THEN the response MUST include
Cache-Control: public, max-age=31536000, immutable
Scenario: index.html is not cached
- WHEN a browser requests
/or/index.html - THEN the response MUST include
Cache-Control: no-cache
Requirement: .dockerignore excludes unnecessary files
A .dockerignore file SHALL prevent large or irrelevant directories from being included in the Docker build context.
Scenario: node_modules excluded
- WHEN Docker reads the build context
- THEN
node_modules/directories MUST be excluded
Scenario: .git excluded
- WHEN Docker reads the build context
- THEN the
.git/directory MUST be excluded
Requirement: Port mapping via docker-compose
The docker-compose.yml SHALL map a host port to the container's port 80, defaulting to host port 8080.
Scenario: Default port mapping
- WHEN
docker compose upis run without overrides - THEN the app MUST be accessible at
http://localhost:8080
Scenario: Custom port via environment variable
- WHEN the user sets an environment variable or edits the compose file to change the host port
- THEN the app MUST be accessible on the configured port