Docker Dev Mode: Hot Reload for the Full Stack

A docker-compose.dev.yml overlay for instant feedback on frontend and backend changes without rebuilding containers.

A docker-compose.dev.yml overlay that gives instant feedback on both frontend and backend changes, without rebuilding containers.


The problem

The platform runs 11+ services in Docker: backend, frontend, nginx, Keycloak, Polaris, OPA, SQE, Langflow, Jaeger, PostgreSQL, S3. Rebuilding the frontend image takes 15-20 seconds. Rebuilding the backend takes longer. For the rapid iteration cycle we needed during the UI migration, this was too slow.

The solution

A docker-compose.dev.yml overlay that replaces the production containers with development-friendly versions:

Terminal window
docker compose -f docker-compose.yml -f docker-compose.dev.yml up -d

Frontend: Vite dev server with HMR

The production frontend runs nginx serving pre-built static files. The dev overlay replaces it with a Vite dev server:

  • Node 24 Alpine image with pnpm and dependencies baked in at build time
  • Source mounted from host: frontend/src/ to /app/src/
  • node_modules in the image (not mounted), so native binaries (esbuild, rollup) are correct for the container’s platform
  • Hot Module Replacement through nginx via WebSocket proxy

The key challenge: Vite’s HMR WebSocket needs to connect through nginx (port 443) to the Vite server (port 3000). We configured this with environment variables:

vite.config.ts
...(process.env.DEV_HMR_PORT && {
hmr: {
clientPort: Number(process.env.DEV_HMR_PORT), // 443
protocol: process.env.DEV_HMR_PROTOCOL, // wss
},
}),

And nginx dev config proxies the frontend with WebSocket upgrade headers:

location / {
proxy_pass http://frontend:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}

Backend: watchfiles auto-restart

The backend uses watchfiles (bundled with uvicorn[standard]) to watch for Python file changes:

command:
- python
- -m
- watchfiles
- --filter
- python
- python -m data_platform.main
- /app/data_platform

Source directories are mounted from the host. Edit a .py file, the backend restarts in about 2 seconds.

What we learned

esbuild needs to run its postinstall script. pnpm v10’s security feature blocks build scripts by default. We had to whitelist esbuild with onlyBuiltDependencies: ["esbuild"] in package.json.

The frontend needs 1GB of RAM. Vite’s dependency pre-bundling (esbuild processing all imports on first request) needs more memory than the production nginx container (256MB). The dev override sets mem_limit: 1g.

Vite 7+ rejects non-localhost Host headers. When nginx proxies with Host: chameleon.local, Vite returns 403. We set DEV_ALLOWED_HOSTS=all to bypass the check in dev mode.


Docker dev mode was added in April 2026 during the React 19 UI migration. Configuration: quickstart/sqe/docker-compose.dev.yml + quickstart/assets/nginx/nginx-dev.conf.

All posts