Пошук уроків, статей та іншого контенту
Організуєте live reload, монтування коду, перевизначення конфігурації та зручні команди для локальної розробки.
Docker Compose дає змогу описати середовище розробки як набір декларативних налаштувань:
код монтується з хоста в контейнер;
зміни файлів одразу доступні застосунку;
сервер автоматично перезапускається через live reload;
залежності зберігаються в окремому Docker volume;
змінні середовища та команди можна перевизначати;
усі розробники запускають проєкт однаковими командами.
Зазвичай базова конфігурація описується у compose.yaml, а локальні зміни — у compose.override.yaml.
Docker Compose автоматично завантажує compose.override.yaml, якщо виконати:
docker compose upОбидва файли об’єднуються перед запуском контейнерів.
Приклад матиме таку структуру:
project/
├── .dockerignore
├── Dockerfile
├── compose.yaml
├── compose.override.yaml
├── package.json
└── src/
└── server.jsDockerfile описує образ застосунку. Він встановлює залежності, копіює код і задає стандартну команду запуску.
FROM node:22-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 3000
CMD ["npm", "start"]Для локальної розробки цей образ буде перевикористаний, але команда запуску та монтування коду зміняться через Compose override.
package.json{
"name": "compose-development-example",
"version": "1.0.0",
"private": true,
"scripts": {
"start": "node src/server.js",
"dev": "nodemon --legacy-watch src/server.js"
},
"dependencies": {
"express": "^5.1.0"
},
"devDependencies": {
"nodemon": "^3.1.10"
}
}Параметр --legacy-watch змушує nodemon використовувати polling. Це часто робить відстеження змін надійнішим, коли код змонтований у контейнер з файлової системи хоста.
src/server.jsconst express = require("express");
const app = express();
const port = Number(process.env.PORT) || 3000;
app.get("/", (req, res) => {
res.send("Development container is running");
});
app.listen(port, () => {
console.log(`Server is listening on port ${port}`);
});Сервер читає порт зі змінної середовища PORT, але використовує 3000, якщо змінну не передано.
У compose.yaml можна розмістити налаштування, спільні для різних способів запуску:
services:
app:
build:
context: .
ports:
- "3000:3000"
environment:
NODE_ENV: production
PORT: 3000
command: npm startТут описано один сервіс app:
build.context вказує, де знаходиться Dockerfile;
"3000:3000" публікує порт контейнера на хості;
environment передає змінні середовища;
command визначає команду запуску застосунку.
Цей файл не монтує код із хоста. Після побудови образу застосунок використовує файли, скопійовані під час виконання Dockerfile.
Створимо compose.override.yaml:
services:
app:
environment:
NODE_ENV: development
PORT: 3000
command: npm run dev
volumes:
- .:/app
- node_modules:/app/node_modules
volumes:
node_modules:Під час запуску Compose об’єднає цей файл із compose.yaml.
У результаті сервіс отримає:
змінну NODE_ENV=development;
команду npm run dev;
монтування поточного каталогу в /app;
окремий volume для /app/node_modules.
Запис:
volumes:
- .:/appозначає:
ліва частина . — каталог проєкту на хості;
права частина /app — каталог усередині контейнера.
Зміни у файлах на хості одразу з’являються в /app контейнера. nodemon помічає ці зміни та перезапускає Node.js-сервер.
Наприклад, якщо змінити текст відповіді:
res.send("Updated development container");сервер перезапуститься без повторної побудови образу.
node_modulesМонтування .:/app приховує весь вміст /app, який був створений під час побудови образу. Це стосується і каталогу /app/node_modules.
Тому без додаткового volume залежності з образу можуть стати недоступними:
- .:/app
- node_modules:/app/node_modulesДругий рядок монтує Docker volume поверх /app/node_modules. Залежності залишаються всередині Docker-середовища, а не змішуються із залежностями хоста.
Це також зменшує кількість проблем, пов’язаних із різними операційними системами або нативними модулями.
.dockerignoreЩоб не копіювати зайві файли в Docker-образ, створіть .dockerignore:
node_modules
npm-debug.log
.git
.gitignore
Dockerfile
compose.yaml
compose.override.yamlФайл node_modules особливо важливий: залежності встановлюються всередині образу командою npm install і зберігаються через окремий volume.
У каталозі проєкту виконайте:
docker compose up --buildПрапорець --build змушує Compose перебудувати образ перед запуском.
Після запуску застосунок буде доступний на:
http://localhost:3000Щоб запустити контейнери у фоновому режимі:
docker compose up --build -dПерегляд журналів:
docker compose logs -f appЗупинка контейнерів:
docker compose downdocker compose down видаляє контейнери та мережі, але не видаляє named volumes. Тому node_modules залишиться доступним під час наступного запуску.
Для запуску shell у вже запущеному контейнері використовуйте:
docker compose exec app shПісля цього можна перевірити файли та залежності:
pwd
ls
ls node_modulesВийти із shell:
exitОкрему команду можна виконати без відкриття shell:
docker compose exec app npm --versionДля одноразового контейнера використовуйте run:
docker compose run --rm app npm installПрапорець --rm видалить одноразовий контейнер після завершення команди.
Якщо ви додали залежність до package.json, є два типові варіанти.
docker compose exec app npm install expressЗалежність буде встановлена у volume node_modules.
Якщо volume містить застарілі залежності, його можна видалити:
docker compose down -v
docker compose up --buildПрапорець -v видаляє named volumes. Наступного разу залежності будуть встановлені заново під час побудови або першого запуску контейнера.
Не використовуйте down -v без потреби, якщо volume містить дані, які потрібно зберегти.
Автоматичне завантаження працює саме для файлу з назвою compose.override.yaml.
Якщо локальна конфігурація має іншу назву, файли можна вказати явно:
docker compose \
-f compose.yaml \
-f compose.dev.yaml \
up --buildФайли застосовуються зліва направо. Налаштування з пізніших файлів мають вищий пріоритет.
Наприклад, якщо в базовому файлі є:
environment:
NODE_ENV: productionа в override:
environment:
NODE_ENV: developmentпісля об’єднання буде використано:
environment:
NODE_ENV: developmentПеревірити підсумкову конфігурацію можна командою:
docker compose configЦе корисно, коли потрібно з’ясувати, які саме команди, змінні та volume отримав сервіс після об’єднання файлів.
Після початкового налаштування робочий процес має такий вигляд:
Запустити середовище:
docker compose upЗмінити файл у локальному редакторі.
Перевірити, що nodemon перезапустив сервер у журналі:
docker compose logs -f appЯкщо потрібно виконати команду в контейнері:
docker compose exec app npm testЗавершити роботу:
docker compose downДля змін у файлах застосунку перебудова образу не потрібна, оскільки код змонтований через bind mount.
Перебудова потрібна, коли змінюються:
Dockerfile;
базовий образ;
команди встановлення залежностей;
файли, які копіюються в образ;
конфігурація, що впливає на етап побудови.
Причина — монтування:
- .:/appприховує /app/node_modules, створений під час побудови образу.
Виправлення:
- .:/app
- node_modules:/app/node_modulesПеревірте, що в development-конфігурації запускається саме nodemon:
command: npm run devТакож переконайтеся, що локальний каталог змонтований у правильний шлях:
- .:/appДля деяких систем допомагає polling-режим:
{
"scripts": {
"dev": "nodemon --legacy-watch src/server.js"
}
}Перевірте підсумкову конфігурацію:
docker compose configУ сервісу app має бути:
command: npm run devТакож переконайтеся, що файл називається саме compose.override.yaml, якщо ви покладаєтеся на автоматичне завантаження.
Якщо порт 3000 використовується іншим процесом, змініть порт хоста:
ports:
- "3001:3000"Перший порт — порт на хості, другий — порт усередині контейнера. Після цього застосунок буде доступний на http://localhost:3001, але всередині контейнера залишиться порт 3000.
package.json не вплинули на контейнерNamed volume node_modules може містити попередній набір залежностей. Виконайте:
docker compose exec app npm installабо повністю пересоздайте volume:
docker compose down -v
docker compose up --buildcompose.yaml містить базову конфігурацію сервісів.
compose.override.yaml автоматично додає локальні налаштування розробки.
Bind mount .:/app робить код із хоста доступним у контейнері.
nodemon забезпечує live reload після змін файлів.
Окремий volume node_modules:/app/node_modules захищає залежності від перекриття bind mount.
docker compose config показує підсумок об’єднання Compose-файлів.
docker compose exec запускає команди в запущеному контейнері.
Після змін у коді перебудова образу не потрібна.
Після змін у Dockerfile або способі встановлення залежностей потрібно виконати docker compose up --build.