Пошук уроків, статей та іншого контенту
Налаштуєте live reload для Node.js і Next.js через bind mounts, змінні середовища та коректне спостереження за файлами.
Hot reload, або live reload, — це автоматичне перезавантаження процесу розробки після зміни файлів на хості.
У Docker це потребує узгодженої роботи трьох компонентів:
Bind mount передає файли з локального комп’ютера в контейнер.
Dev-сервер стежить за змінами у файловій системі.
Змінні середовища або параметри watcher-а вмикають сумісний режим спостереження.
Docker самостійно не перезапускає Node.js чи Next.js. Він лише монтує файли. Перезапуском займаються nodemon, Next.js або інший dev-сервер.
У compose.yaml bind mount записується так:
volumes:
- ./api:/appЦе означає:
./api — каталог на хості;
/app — каталог усередині контейнера;
зміни у ./api одразу стають доступними в /app.
Однак монтування всього проєкту має важливий наслідок: воно перекриває файли, які були скопійовані в образ під час docker build.
Тому залежності потрібно монтувати окремим volume:
volumes:
- ./api:/app
- api_node_modules:/app/node_modulesУ цьому випадку:
вихідний код береться з хоста;
node_modules зберігається в окремому Docker volume;
локальний node_modules не перекриває залежності контейнера.
Створимо проєкт із двома сервісами:
docker-hot-reload/
├── compose.yaml
├── api/
│ ├── Dockerfile.dev
│ ├── package.json
│ └── server.js
└── web/
├── Dockerfile.dev
├── package.json
├── app/
│ ├── layout.js
│ └── page.js
└── .dockerignoreapi буде простим Node.js API, а web — Next.js-застосунком.
api/package.json{
"name": "hot-reload-api",
"version": "1.0.0",
"private": true,
"scripts": {
"dev": "nodemon --legacy-watch server.js"
},
"dependencies": {
"express": "4.21.1"
},
"devDependencies": {
"nodemon": "3.1.7"
}
}Параметр --legacy-watch вмикає polling-режим у nodemon. Він особливо корисний у Docker Desktop, WSL та інших середовищах, де стандартні події файлової системи можуть не доходити до контейнера.
api/server.jsconst express = require("express");
const app = express();
const port = process.env.PORT || 3000;
app.get("/", (_req, res) => {
res.json({
message: "API працює",
environment: process.env.NODE_ENV || "development"
});
});
app.listen(port, "0.0.0.0", () => {
console.log(`API запущено на порту ${port}`);
});Адреса 0.0.0.0 важлива для доступу до процесу ззовні контейнера. Якщо сервер слухатиме лише localhost, порт може бути недоступним з хоста.
api/Dockerfile.devFROM node:20-bookworm-slim
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 3000
CMD ["npm", "run", "dev"]Під час складання образу залежності встановлюються в /app/node_modules. Пізніше цей каталог буде збережений окремим volume, щоб bind mount вихідного коду його не приховав.
web/package.json{
"name": "hot-reload-web",
"version": "1.0.0",
"private": true,
"scripts": {
"dev": "next dev"
},
"dependencies": {
"next": "14.2.15",
"react": "18.3.1",
"react-dom": "18.3.1"
}
}web/app/layout.jsexport const metadata = {
title: "Docker Hot Reload"
};
export default function RootLayout({ children }) {
return (
<html lang="uk">
<body>{children}</body>
</html>
);
}web/app/page.jsexport default function HomePage() {
return (
<main>
<h1>Next.js працює в Docker</h1>
<p>Змініть цей текст і перевірте hot reload.</p>
</main>
);
}web/Dockerfile.devFROM node:20-bookworm-slim
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 3000
CMD ["npm", "run", "dev", "--", "--hostname", "0.0.0.0"]Next.js за замовчуванням запускається на порту 3000 і зазвичай слухає локальний інтерфейс контейнера. Параметр --hostname 0.0.0.0 робить сервер доступним через опублікований Docker-порт.
Створіть файл compose.yaml у корені проєкту:
services:
api:
build:
context: ./api
dockerfile: Dockerfile.dev
working_dir: /app
volumes:
- ./api:/app
- api_node_modules:/app/node_modules
environment:
NODE_ENV: development
CHOKIDAR_USEPOLLING: "true"
CHOKIDAR_INTERVAL: "500"
ports:
- "3000:3000"
command: npm run dev
web:
build:
context: ./web
dockerfile: Dockerfile.dev
working_dir: /app
volumes:
- ./web:/app
- web_node_modules:/app/node_modules
- web_next:/app/.next
environment:
NODE_ENV: development
WATCHPACK_POLLING: "true"
ports:
- "3001:3000"
command: npm run dev -- --hostname 0.0.0.0
volumes:
api_node_modules:
web_node_modules:
web_next:Для api:
- ./api:/app
- api_node_modules:/app/node_modulesДля web:
- ./web:/app
- web_node_modules:/app/node_modules
- web_next:/app/.nextweb_next зберігає кеш і результати роботи Next.js окремо від вихідного коду. Це не є обов’язковим для самого hot reload, але не дозволяє каталогу .next створюватися на хості та змішуватися з файлами проєкту.
CHOKIDAR_USEPOLLING=true вмикає polling для інструментів, які використовують Chokidar.
CHOKIDAR_INTERVAL=500 задає інтервал перевірки у мілісекундах. Менше значення дає швидшу реакцію, але збільшує навантаження.
WATCHPACK_POLLING=true вмикає polling для механізму спостереження, який використовує Next.js у режимі розробки.
Polling перевіряє файли через певні інтервали, а не покладається на події файлової системи. Це повільніше й може споживати більше ресурсів, але часто стабільніше у змонтованих каталогах.
У корені проєкту виконайте:
docker compose up --buildПісля запуску:
API буде доступний на http://localhost:3000;
Next.js буде доступний на http://localhost:3001.
Змініть текст у api/server.js або web/app/page.js. Dev-сервер усередині відповідного контейнера має помітити зміну й перезапустити застосунок.
Для запуску у фоновому режимі використовуйте:
docker compose up --build -dДля перегляду логів:
docker compose logs -f api
docker compose logs -f webДля зупинки контейнерів:
docker compose down.dockerignore для dev-образівСтворіть api/.dockerignore і web/.dockerignore з таким вмістом:
node_modules
.next
.git
npm-debug.logЦе запобігає копіюванню локальних залежностей, кешу Next.js та службових файлів у Docker-образ.
.dockerignore впливає на контекст складання образу, але не вимикає bind mount. Файли все одно будуть доступні контейнеру через volumes.
На Linux стандартний watcher часто працює без додаткових параметрів:
environment:
NODE_ENV: developmentЦей режим зазвичай має менше навантаження.
Polling варто спробувати, якщо:
зміни на хості не викликають перезавантаження;
використовується Docker Desktop;
проєкт розташований у змонтованій файловій системі WSL;
код знаходиться на мережевому диску;
контейнер запускається у віртуальній машині.
Для Node.js із nodemon використовується:
environment:
CHOKIDAR_USEPOLLING: "true"
CHOKIDAR_INTERVAL: "500"і параметр:
{
"scripts": {
"dev": "nodemon --legacy-watch server.js"
}
}Для Next.js:
environment:
WATCHPACK_POLLING: "true"Не потрібно вмикати polling без потреби: на великих проєктах він може створювати відчутне навантаження на процесор.
Зміна файлу застосунку зазвичай не потребує перебудови образу:
server.js → достатньо hot reload
app/page.js → достатньо hot reloadПеребудова потрібна після зміни файлів, які використовуються під час складання образу, наприклад:
Dockerfile.dev
package.json
package-lock.jsonПісля таких змін виконайте:
docker compose up --buildЯкщо змінився лише package.json, уже запущений контейнер не встановить нову залежність автоматично. Потрібно перебудувати образ і перезапустити сервіс.
node_modulesНевдалий варіант:
volumes:
- ./api:/appBind mount перекриває весь /app, зокрема /app/node_modules, які були встановлені під час складання образу.
Кращий варіант:
volumes:
- ./api:/app
- api_node_modules:/app/node_moduleslocalhostНевдалий код:
app.listen(3000, "127.0.0.1");У контейнері цей процес буде доступний лише всередині самого контейнера.
Використовуйте:
app.listen(3000, "0.0.0.0");Якщо файли змінюються на хості, але застосунок не перезапускається, перевірте polling:
environment:
CHOKIDAR_USEPOLLING: "true"
WATCHPACK_POLLING: "true"Для nodemon також можна додати:
{
"scripts": {
"dev": "nodemon --legacy-watch server.js"
}
}Всередині контейнера Next.js слухає порт 3000. Якщо на хості використовується порт 3001, mapping має бути таким:
ports:
- "3001:3000"Формат — порт_хоста:порт_контейнера.
Polling, nodemon, bind mounts і npm install для розробки не повинні автоматично переноситися у production-образ.
У production зазвичай:
код копіюється в образ;
залежності встановлюються у production-режимі;
dev-сервер і file watcher не запускаються;
bind mounts не використовуються.
Hot reload складається з bind mount, dev-сервера та watcher-а.
Bind mount ./api:/app синхронізує вихідний код із контейнером.
node_modules потрібно зберігати в окремому Docker volume.
Для Node.js з nodemon можна використовувати --legacy-watch і змінні Chokidar.
Для Next.js у проблемних файлових системах допомагає WATCHPACK_POLLING=true.
Сервери в контейнері мають слухати 0.0.0.0.
Зміна вихідного коду не потребує docker compose build.
Зміна Dockerfile або залежностей потребує повторного складання образу.