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.
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.
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
| Aspect | Dev mode | Production mode |
|---|---|---|
| Source code | Bind-mounted from your machine | Copied into the image at build time |
| Process | Dev server with watchers and HMR | Built output: next start, node dist/main.js |
| Image | Larger, with dev dependencies and tools | Small, production dependencies only |
| Security | Local and trusted | Non-root user, no dev tools, no source mounts |
3. A reference layout
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.yamlA 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:
# 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:
docker compose -f compose.yaml -f compose.dev.yaml up --buildMounting 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:
# 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:
// 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
# 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 machine | What to do |
|---|---|
| Linux | Bind 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 |
| Windows | Keep 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:
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.confCompose 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:
services:
web:
develop:
watch:
- action: sync
path: ./apps/web/src
target: /app/src
- action: rebuild
path: ./apps/web/package.jsonRun it with docker compose watch. Also exclude heavy folders (.git, dist, .next, coverage) from your watchers.
9. Troubleshooting and security
| Symptom | Likely cause | Fix |
|---|---|---|
| Edits are not picked up | Watcher misses events through the mount | Enable polling, or use Compose Watch; check the mount path matches the watched folder |
| Full page reload instead of HMR | Config or env change, or the production server is running | Normal for config edits; make sure you run the dev command, not start |
| Browser cannot reach the dev server | Server listens on 127.0.0.1 inside the container | Listen on 0.0.0.0 (-H 0.0.0.0, host: true) |
| "invalid ELF header" or native module errors | Host node_modules leaked into the container | Keep node_modules in a volume and reinstall inside the container |
| Files owned by root on your machine | Container runs as root | Run as the node user, or set user: "1000:1000" to match your UID |
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.
| Where | How the app runs |
|---|---|
| App Platform | Push 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 |
| VPS | Self-managed with root access: run the production Compose file behind a reverse proxy with HTTPS |
| Shared hosting | No 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.
- 512 MB RAM per app
- 1 vCPU
- 5 GB NVMe SSD
- PostgreSQL Database
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.
Deploy from GitHub or with a deploy token, with PostgreSQL and free SSL included in every plan.
See App Platform plans