Пошук уроків, статей та іншого контенту
Описуватимете кілька сервісів у Docker Compose та керуватимете їхнім запуском однією конфігурацією.
Docker Compose дає змогу описати кілька пов’язаних контейнерів в одному YAML-файлі та керувати ними як одним локальним середовищем.
Наприклад, застосунок може складатися з:
API-сервісу;
бази даних PostgreSQL;
окремих томів для збереження даних;
внутрішньої мережі між контейнерами.
Замість запуску кожного контейнера окремими командами достатньо виконати:
docker compose upDocker Compose прочитає конфігурацію, створить необхідні мережі й томи, запустить сервіси та покаже їхні логи.
Приклад локального проєкту матиме таку структуру:
project/
├── compose.yaml
├── Dockerfile
├── package.json
└── src/
└── server.jsФайл Compose зазвичай називають compose.yaml або docker-compose.yml. Сучасна команда Docker використовує синтаксис:
docker composeСтарий варіант із дефісом:
docker-composeтакож зустрічається, але залежить від окремої старої версії Compose.
Розглянемо локальне середовище для невеликого Node.js API, яке використовує PostgreSQL.
compose.yamlservices:
db:
image: postgres:16-alpine
environment:
POSTGRES_DB: app_db
POSTGRES_USER: app_user
POSTGRES_PASSWORD: local_password
ports:
- "5432:5432"
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U app_user -d app_db"]
interval: 5s
timeout: 5s
retries: 10
api:
build:
context: .
environment:
PORT: 3000
DATABASE_URL: postgres://app_user:local_password@db:5432/app_db
ports:
- "3000:3000"
volumes:
- .:/app
- /app/node_modules
depends_on:
db:
condition: service_healthy
command: node --watch src/server.js
volumes:
postgres_data:Файл описує два сервіси:
db — контейнер із PostgreSQL;
api — контейнер із Node.js API.
servicesКожен ключ усередині services є назвою сервісу:
services:
db:
...
api:
...Ці назви використовуються для взаємодії між контейнерами. Наприклад, API підключається до PostgreSQL за адресою db, а не localhost.
Compose автоматично створює внутрішню мережу для сервісів. У цій мережі ім’я сервісу працює як DNS-ім’я контейнера.
imageДля бази даних використовується готовий образ:
image: postgres:16-alpineDocker завантажить його з реєстру, якщо образ ще не існує локально.
buildДля API образ потрібно створити з локального Dockerfile:
build:
context: .Крапка означає, що контекстом збірки є поточна директорія. Docker зможе використовувати файли з цієї директорії під час виконання Dockerfile.
Параметри PostgreSQL задаються через environment:
environment:
POSTGRES_DB: app_db
POSTGRES_USER: app_user
POSTGRES_PASSWORD: local_passwordЦі змінні використовує офіційний образ PostgreSQL під час першого створення бази даних.
Для API передається рядок підключення:
environment:
DATABASE_URL: postgres://app_user:local_password@db:5432/app_dbУ цьому рядку:
app_user — користувач бази даних;
local_password — пароль;
db — ім’я сервісу PostgreSQL у мережі Compose;
5432 — порт PostgreSQL;
app_db — назва бази даних.
Усередині Compose-контейнерів не потрібно використовувати
localhostдля звернення до іншого сервісу.localhostвказує на поточний контейнер.
ports:
- "5432:5432"Формат запису:
порт_на_комп’ютері:порт_у_контейнеріДля API:
ports:
- "3000:3000"Після запуску API буде доступне на http://localhost:3000.
Порт бази даних проброшено для зручного підключення до PostgreSQL із локального клієнта. Сам API міг би працювати з базою і без цього пробросу, оскільки обидва сервіси перебувають в одній внутрішній мережі.
Створіть файл Dockerfile:
FROM node:22-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 3000
CMD ["node", "src/server.js"]Цей Dockerfile:
використовує Node.js на базі Alpine Linux;
встановлює робочу директорію /app;
копіює файли залежностей;
встановлює залежності;
копіює код застосунку;
оголошує порт API;
задає стандартну команду запуску.
Команда з compose.yaml:
command: node --watch src/server.jsзамінює CMD із Dockerfile під час локального запуску. Завдяки --watch Node.js перезапускає процес після зміни файлів.
Створіть файл package.json:
{
"name": "compose-local-api",
"version": "1.0.0",
"private": true,
"scripts": {
"start": "node src/server.js"
},
"dependencies": {
"express": "^5.1.0",
"pg": "^8.16.0"
}
}Створіть файл src/server.js:
const express = require("express");
const { Pool } = require("pg");
const app = express();
const port = Number(process.env.PORT || 3000);
const pool = new Pool({
connectionString: process.env.DATABASE_URL,
});
app.use(express.json());
app.get("/health", async (_request, response) => {
try {
await pool.query("SELECT 1");
response.json({ status: "ok", database: "connected" });
} catch (error) {
response.status(503).json({ status: "error", database: "unavailable" });
}
});
app.get("/todos", async (_request, response) => {
const result = await pool.query(
"SELECT id, title, completed FROM todos ORDER BY id"
);
response.json(result.rows);
});
app.post("/todos", async (request, response) => {
const { title } = request.body;
if (!title || typeof title !== "string") {
return response.status(400).json({
error: "Поле title є обов’язковим",
});
}
const result = await pool.query(
"INSERT INTO todos (title) VALUES ($1) RETURNING id, title, completed",
[title]
);
response.status(201).json(result.rows[0]);
});
async function start() {
// Створюємо таблицю під час запуску локального середовища.
await pool.query(`
CREATE TABLE IF NOT EXISTS todos (
id SERIAL PRIMARY KEY,
title TEXT NOT NULL,
completed BOOLEAN NOT NULL DEFAULT FALSE
)
`);
app.listen(port, () => {
console.log(`API запущено на порту ${port}`);
});
}
start().catch((error) => {
console.error("Не вдалося запустити API:", error);
process.exit(1);
});Перед запуском можна перевірити, як Compose обробляє файл:
docker compose configКоманда покаже підсумкову конфігурацію. Вона допомагає виявити помилки YAML, неправильні відступи або проблеми зі змінними середовища.
Перший запуск виконується командою:
docker compose up --buildПараметр --build змушує Docker перебудувати образ API перед запуском.
Після успішного запуску можна перевірити API:
curl http://localhost:3000/healthОчікувана відповідь:
{
"status": "ok",
"database": "connected"
}Створення запису:
curl -X POST http://localhost:3000/todos \
-H "Content-Type: application/json" \
-d '{"title":"Вивчити Docker Compose"}'Отримання записів:
curl http://localhost:3000/todosЩоб переглянути запущені сервіси:
docker compose psПрикладом результату будуть два контейнери:
контейнер API;
контейнер PostgreSQL.
Для перегляду логів усіх сервісів:
docker compose logsДля перегляду логів конкретного сервісу:
docker compose logs apiСтежити за новими повідомленнями в реальному часі можна за допомогою:
docker compose logs -f apiЗупинити середовище без видалення контейнерів:
docker compose stopЗапустити вже створені контейнери знову:
docker compose startЗупинити та видалити контейнери, мережі й інші ресурси проєкту:
docker compose downУ конфігурації API має залежність від бази даних:
depends_on:
db:
condition: service_healthyА база даних має healthcheck:
healthcheck:
test: ["CMD-SHELL", "pg_isready -U app_user -d app_db"]
interval: 5s
timeout: 5s
retries: 10depends_on визначає порядок запуску, але саме по собі не гарантує, що база вже готова приймати підключення.
condition: service_healthy змушує Compose дочекатися успішного healthcheck перед запуском API.
Це важливо, оскільки контейнер PostgreSQL може бути вже запущений, але ще виконувати початкову ініціалізацію.
У конфігурації використано іменований том:
volumes:
postgres_data:Він підключається до PostgreSQL:
volumes:
- postgres_data:/var/lib/postgresql/dataДані бази зберігаються в томі та не зникають після:
docker compose downТом буде видалений лише за явної вказівки:
docker compose down -vЦя команда видаляє також іменовані томи. Після неї локальна база даних буде створена заново під час наступного запуску.
Для API використано bind mount:
volumes:
- .:/app
- /app/node_modulesЗапис:
- .:/appпідключає локальну директорію проєкту до /app у контейнері. Тому зміни у файлах одразу доступні всередині контейнера.
Другий запис:
- /app/node_modulesзберігає node_modules контейнера окремо від локальної файлової системи. Це запобігає ситуації, коли bind mount приховує встановлені в образі залежності.
Bind mount зручний для локальної розробки, але зазвичай не використовується як основний спосіб постачання коду в production-образах.
Якщо змінено лише код API, node --watch зазвичай перезапустить процес автоматично.
Якщо змінено Dockerfile або package.json, потрібно перебудувати образ:
docker compose up --buildЯкщо змінено compose.yaml, Compose повторно застосує конфігурацію під час наступного запуску:
docker compose upДля повного перезапуску середовища:
docker compose down
docker compose up --build.envЗначення, які часто змінюються, можна винести у файл .env:
POSTGRES_DB=app_db
POSTGRES_USER=app_user
POSTGRES_PASSWORD=local_passwordТоді compose.yaml може використовувати їх так:
services:
db:
image: postgres:16-alpine
environment:
POSTGRES_DB: ${POSTGRES_DB}
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}Файл .env призначений для локальних значень. Якщо в ньому зберігаються справжні паролі, його не слід додавати до системи контролю версій.
Зазвичай до репозиторію додають файл-приклад, наприклад .env.example:
POSTGRES_DB=app_db
POSTGRES_USER=app_user
POSTGRES_PASSWORD=change_melocalhost для бази данихНеправильно:
DATABASE_URL=postgres://app_user:local_password@localhost:5432/app_dbУ контейнері API localhost вказує на сам контейнер API.
Правильно:
DATABASE_URL=postgres://app_user:local_password@db:5432/app_dbdb — це назва сервісу в compose.yaml.
Сам факт запуску контейнера PostgreSQL не означає, що база вже готова до підключень. Якщо API запускається раніше за базу, воно може завершитися з помилкою.
Для локального середовища використовуйте healthcheck і залежність із умовою service_healthy.
Команда:
docker compose down -vвидаляє томи разом із даними. Її варто використовувати лише тоді, коли потрібно повністю скинути локальну базу.
--buildЯкщо змінено Dockerfile або залежності, простого перезапуску контейнера може бути недостатньо:
docker compose upУ такому випадку перебудуйте образ:
docker compose up --buildYAML використовує відступи для структури. Наприклад, environment має бути вкладений у конкретний сервіс:
services:
api:
environment:
PORT: 3000Змішування табуляцій і пробілів або неправильний рівень відступу призведе до помилки під час читання конфігурації.
Docker Compose описує кілька сервісів в одному YAML-файлі.
services містить контейнери застосунку та його залежностей.
image використовує готовий образ, а build створює образ із Dockerfile.
Сервіси Compose можуть звертатися один до одного за іменами, наприклад db.
ports публікує порти контейнера на локальному комп’ютері.
Іменовані томи зберігають дані між перезапусками контейнерів.
healthcheck перевіряє готовність сервісу.
depends_on визначає залежності й порядок запуску.
docker compose up --build запускає локальне середовище.
docker compose down зупиняє та видаляє ресурси Compose, але без -v зберігає дані томів.