Stage 1 (build): node:22-alpine, installs all deps with npm ci, copies source, runs npm run build (tsc + vite build) producing dist/
Stage 2 (serve): node:22-alpine, installs all deps again (needed for vite preview), copies dist/ and vite.config.ts from build stage, exposes port 4173, runs npm run preview
# Build the image
docker build -t zauberwald .
# Run with config.json mounted from host
docker run -v /path/to/config.json:/app/dist/config.json:ro -p 4173:4173 zauberwald
The app is then accessible at http://localhost:4173.
Key Decisions
All project dependencies are devDependencies (vite, typescript, etc.). The serve stage must run npm ci (without --omit=dev) so that vite is available for npm run preview.
public/config.json is intentionally NOT excluded from .dockerignore — it must be baked into dist/ during build. At runtime, users can override it by mounting their own config.json to /app/dist/config.json.
vite.config.ts is copied into the serve stage because Vite reads it to apply the host: "0.0.0.0" and port: 4173 preview settings.