docker
node.js
docker-compose
code reloading
development environment

Reloading code in a dockerized node.js app with docker-compose

System Design practice on Codemia

Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.

Practice system design

Introduction

Fast code reload in a Dockerized Node.js app is essential for an efficient development loop. Without proper volume mounts and file watchers, every change requires rebuilding containers, which is slow and frustrating. A good setup keeps dependencies inside the container while syncing source code from host to container.

Development Architecture for Hot Reload

Use one development container that runs nodemon or the Node watch mode. Mount your project directory as a bind volume so file changes are visible instantly.

Project files:

  • Dockerfile.dev for development runtime
  • docker-compose.yml for service wiring and volumes
  • package.json with a dev script that starts a watcher

Configure package.json for Reload

Set a script that restarts server when source files change.

json
1{
2  "name": "docker-node-dev",
3  "version": "1.0.0",
4  "scripts": {
5    "dev": "nodemon --legacy-watch src/index.js"
6  },
7  "dependencies": {
8    "express": "^4.19.2"
9  },
10  "devDependencies": {
11    "nodemon": "^3.1.0"
12  }
13}

--legacy-watch helps on some mounted filesystems where native change events are unreliable.

Build a Development Dockerfile

Install dependencies once and run the dev command.

dockerfile
1FROM node:20-alpine
2WORKDIR /usr/src/app
3COPY package*.json ./
4RUN npm install
5COPY . .
6EXPOSE 3000
7CMD ["npm", "run", "dev"]

In development, mounted source will override copied source for live edits.

Compose Setup with Correct Volumes

The most common issue is accidentally overwriting container node_modules with host state. Use an anonymous volume for dependencies.

yaml
1version: "3.9"
2services:
3  api:
4    build:
5      context: .
6      dockerfile: Dockerfile.dev
7    ports:
8      - "3000:3000"
9    environment:
10      - NODE_ENV=development
11      - CHOKIDAR_USEPOLLING=true
12    volumes:
13      - .:/usr/src/app
14      - /usr/src/app/node_modules

Bring it up:

bash
docker compose up --build

Edit src/index.js on host and the container should restart automatically.

Quick Verification Endpoint

Use a tiny endpoint and modify its response text to confirm hot reload is actually happening, not just container restart logs.

javascript
1const express = require('express');
2const app = express();
3
4app.get('/health', (_req, res) => res.json({ status: 'ok', version: 'dev-1' }));
5app.listen(3000, () => console.log('listening on 3000'));

After editing version to dev-2, refresh the endpoint and verify the new value appears without rebuilding the image.

Optimize Watch Reliability on Different Hosts

File watching behavior differs across macOS, Windows, Linux, and WSL. If reload misses changes:

  • enable polling with CHOKIDAR_USEPOLLING=true
  • reduce polling interval only if CPU impact is acceptable
  • avoid extremely deep watch trees in large monorepos

For modern Node versions, built in watch mode can replace nodemon in simple apps.

bash
node --watch src/index.js

Separate Development and Production Compose Paths

Do not reuse hot reload settings in production. Production containers should run compiled artifacts with immutable images.

Typical split:

  • docker-compose.yml for base services
  • docker-compose.dev.yml adds bind mounts and watch env vars
  • docker-compose.prod.yml disables source mounts and runs optimized command

This prevents accidental deployment of slow watcher processes.

Common Pitfalls

A common pitfall is bind mounting the entire project including host node_modules, causing binary mismatch errors between host and container.

Another issue is forgetting to install nodemon in the container image, so reload appears configured but never triggers restarts.

A third issue is mounting to the wrong container path. If code is edited outside the process working directory, no reload occurs.

Finally, frequent full restarts on tiny file changes may hide stateful bugs. Add health checks and request replay in development to validate behavior after each restart.

Summary

  • Use bind mounts plus a watcher process for fast Dockerized Node.js reload
  • Keep node_modules managed inside container to avoid host mismatch
  • Enable polling on filesystems where native watch events are unreliable
  • Split dev and prod compose configs to avoid configuration leakage
  • Validate reload behavior with real endpoint checks, not logs alone

Related reading
Course
Beginner
27 lessons
10 hours
System Design Fundamentals

Build a strong foundation in designing scalable, reliable distributed systems.

View the course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

System Design practice on Codemia

Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.

Practice system design

All Rights Reserved.