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:
docker compose -f docker-compose.yml -f docker-compose.dev.yml up -dFrontend: 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:
...(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_platformSource 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.