Server Management (Unmanaged)

Dev Mode with BindMounts + Hot Reload (HMR): The Complete Guide

By the Domain India teamPublished 9 min read
Knowledge base article
Contents (10 sections)

Developing inside containers gives every developer the same runtime as production, but a naive setup means rebuilding the image after every change. Dev mode fixes that: your source code stays on your machine, a bind mount shows it inside the container, and the framework's dev server reloads the change in about a second. This guide shows a current, working setup with Docker Compose, the settings that make file watching reliable on macOS, Windows and Linux, and how to keep dev mode out of production.

Key takeaways

Bind-mount your source code into the container, keep node_modules in a separate volume, run the framework's dev server (next dev, vite, nest start --watch) bound to 0.0.0.0, and publish its ports on 127.0.0.1 only. If file changes are missed, enable polling or use docker compose watch, which syncs files into the container without a bind mount. Production uses a separate, built image with no mounts and no dev server.

This is a local-development guide

Dev mode runs on your own computer, or on your own VPS for a shared development server. It is not something you run in production, and shared cPanel, DirectAdmin and Webuzo hosting cannot run Docker at all. Section 10 covers where the finished app goes.

1. The terms in one minute

  • Bind mount: maps a folder on your computer into the container. Edits on your machine appear inside the container at once.
  • Named volume: storage managed by Docker. Used here for node_modules, so the container keeps its own dependencies built for Linux.
  • File watcher: the part of a dev server that notices a file has changed.
  • HMR (Hot Module Replacement) / Fast Refresh: the dev server swaps only the changed module in the browser, often keeping component state. Back ends usually do a fast restart instead.

2. Dev mode vs production mode

AspectDev modeProduction mode
Source codeBind-mounted from your machineCopied into the image at build time
ProcessDev server with watchers and HMRBuilt output: next start, node dist/main.js
ImageLarger, with dev dependencies and toolsSmall, production dependencies only
SecurityLocal and trustedNon-root user, no dev tools, no source mounts

3. A reference layout

text
acme-app/
  apps/
    web/          # Next.js or Vite front end
    api/          # NestJS or Express back end
  deploy/
    Caddyfile     # local reverse proxy
    Dockerfile.web
    Dockerfile.api
  compose.yaml
  compose.dev.yaml

A reverse proxy in front gives you one stable address, http://localhost, with /api going to the back end, so the browser never has to know which port each service uses.

4. The dev Compose file

Modern Compose files need no version: line. Keep the production definition in compose.yaml and add dev behaviour in an override file:

yaml
# compose.dev.yaml
services:
  web:
    build:
      context: .
      dockerfile: deploy/Dockerfile.web
      target: dev
    command: npm run dev
    working_dir: /app
    volumes:
      - ./apps/web:/app
      - web_node_modules:/app/node_modules
    environment:
      NODE_ENV: development
      WATCHPACK_POLLING: "true"   # only if changes are missed
    ports:
      - "127.0.0.1:3000:3000"

  api:
    build:
      context: .
      dockerfile: deploy/Dockerfile.api
      target: dev
    command: npm run start:dev
    working_dir: /app
    volumes:
      - ./apps/api:/app
      - api_node_modules:/app/node_modules
    environment:
      NODE_ENV: development
    ports:
      - "127.0.0.1:3001:3001"
      - "127.0.0.1:9229:9229"     # Node.js debugger

  proxy:
    image: caddy:2
    volumes:
      - ./deploy/Caddyfile:/etc/caddy/Caddyfile:ro
    ports:
      - "127.0.0.1:80:80"
    depends_on: [web, api]

volumes:
  web_node_modules:
  api_node_modules:

Start it with:

bash
docker compose -f compose.yaml -f compose.dev.yaml up --build

Mounting each app folder rather than the whole repository keeps file watching fast. In a monorepo with shared packages, mount the shared folder as well.

5. The dev Dockerfile stage

Use one Dockerfile with a dev target and a production target, so dev and production share the same base image:

dockerfile
# deploy/Dockerfile.web
FROM node:24-slim AS base
WORKDIR /app

FROM base AS dev
RUN chown node:node /app
USER node
COPY --chown=node:node apps/web/package*.json ./
RUN npm ci
CMD ["npm", "run", "dev"]

FROM base AS build
COPY apps/web/package*.json ./
RUN npm ci
COPY apps/web/ .
RUN npm run build && npm prune --omit=dev

FROM base AS prod
ENV NODE_ENV=production
COPY --from=build /app ./
USER node
CMD ["npm", "start"]

npm ci in the dev stage fills the named node_modules volume on first start. After you change package.json, rebuild with --build or run docker compose exec web npm install.

6. Front end and back end dev servers

Next.js. next dev provides Fast Refresh. Inside a container the dev server must listen on all interfaces: use next dev -H 0.0.0.0 -p 3000. Changes to next.config.js or environment variables need a restart; that is normal.

Vite (React, Vue, Svelte). Set the server to listen on all interfaces and, behind a proxy, tell the browser where the HMR WebSocket is:

javascript
// vite.config.js
export default {
  server: {
    host: true,
    port: 3000,
    watch: { usePolling: process.env.VITE_POLLING === 'true' },
    hmr: { clientPort: 80 }
  }
};

NestJS. nest start --watch recompiles and restarts on each change. For plain Node.js, node --watch src/server.js does the same without extra packages. To debug, start Node.js with --inspect=0.0.0.0:9229 and attach your editor to localhost:9229.

7. The local reverse proxy

text
# deploy/Caddyfile
:80 {
  handle_path /api/* {
    reverse_proxy api:3001
  }
  handle {
    reverse_proxy web:3000
  }
}

handle_path strips /api before passing the request on. Caddy forwards WebSocket connections automatically, so HMR works through the proxy.

8. Making file watching fast and reliable

Your machineWhat to do
LinuxBind mounts are native and fast. If you see "ENOSPC" or missed changes, raise the inotify limits (below)
macOS (Docker Desktop)Use the VirtioFS file-sharing option, mount only the app folders, keep node_modules in a volume. Enable polling only if changes are missed
WindowsKeep the project inside the WSL 2 file system (for example ~/code in Ubuntu), not on C:. File events then work without polling

Raise the Linux inotify limits:

bash
sudo sysctl fs.inotify.max_user_watches=524288
sudo sysctl fs.inotify.max_user_instances=1024
# make permanent
echo 'fs.inotify.max_user_watches=524288' | sudo tee /etc/sysctl.d/99-inotify.conf

Compose Watch is the alternative to bind mounts when mounts are slow. It syncs changed files into the container and can rebuild when package.json changes:

yaml
services:
  web:
    develop:
      watch:
        - action: sync
          path: ./apps/web/src
          target: /app/src
        - action: rebuild
          path: ./apps/web/package.json

Run it with docker compose watch. Also exclude heavy folders (.git, dist, .next, coverage) from your watchers.

9. Troubleshooting and security

SymptomLikely causeFix
Edits are not picked upWatcher misses events through the mountEnable polling, or use Compose Watch; check the mount path matches the watched folder
Full page reload instead of HMRConfig or env change, or the production server is runningNormal for config edits; make sure you run the dev command, not start
Browser cannot reach the dev serverServer listens on 127.0.0.1 inside the containerListen on 0.0.0.0 (-H 0.0.0.0, host: true)
"invalid ELF header" or native module errorsHost node_modules leaked into the containerKeep node_modules in a volume and reinstall inside the container
Files owned by root on your machineContainer runs as rootRun as the node user, or set user: "1000:1000" to match your UID
Keep dev mode private

Dev servers expose source maps, debug ports and error pages that reveal your code. Publish their ports on 127.0.0.1 only, never on a public IP, and use test credentials rather than production ones. Keep .env files out of Git.

10. From dev mode to production on Domain India

Dev mode ends on your machine. For production you build the app and run the production image or output, with no bind mounts and no watchers.

WhereHow the app runs
App PlatformPush your code; Node.js apps are detected and built automatically, and any other stack builds from your Dockerfile. Deploy with Deploy Now or a deploy token from CI. No SSH
VPSSelf-managed with root access: run the production Compose file behind a reverse proxy with HTTPS
Shared hostingNo Docker. Small Node.js apps can run through the panel's Node.js tool; upload the built output

Start with getting started with the App Platform, or read Docker on cPanel and DirectAdmin if you were hoping to run containers on shared hosting. For image best practices, see crafting the perfect Dockerfile.

App Starter
₹100/mo + GST
  • 512 MB RAM per app
  • 1 vCPU
  • 5 GB NVMe SSD
  • PostgreSQL Database
See plan details

Support is on 24/7 live chat, and tickets get a first response within 15 minutes; there is no phone support.

Do I need Docker to use hot reload?

No. Dev servers such as Next.js, Vite and NestJS hot-reload natively on your machine. Docker adds the same runtime as production and a repeatable setup for the whole team.

Why are my file changes not detected inside the container?

File-change events often do not pass through Docker Desktop's file sharing on macOS and Windows. Enable your watcher's polling option, use docker compose watch, or on Windows keep the project inside the WSL 2 file system.

Should I bind-mount node_modules?

No. Keep node_modules in a named volume inside the container, so dependencies are built for Linux and file sharing stays fast. Bind-mount only your source folders.

Why can't my browser reach the dev server in the container?

The dev server is probably listening on 127.0.0.1 inside the container. Make it listen on 0.0.0.0, for example next dev -H 0.0.0.0 or host: true in Vite, and publish the port.

Do I still need the version line in a Compose file?

No. Current Docker Compose ignores the top-level version line, so leave it out.

Can I run this dev setup on Domain India shared hosting?

No. Shared hosting cannot run Docker. Develop on your own computer and deploy the built app to the App Platform, a VPS, or for small Node.js apps, the Node.js tool on shared hosting.

Ready to deploy what you built? See App Platform plans, choose a VPS to run your production Compose file yourself, or open a support ticket if you are unsure which fits.

Ship your app without managing a server

Deploy from GitHub or with a deploy token, with PostgreSQL and free SSL included in every plan.

See App Platform plans

Ready when you are

Get VPS from ₹552.65/mo + GST

See plans

Was this article helpful?

Your answer helps us decide what to improve next.

Still need help? Open a support ticket and our team will reply.

Prefer an app? Add this site to your home screen.Get the app
Docker Dev Mode: Bind Mounts and Hot Reload | Domain India