У попередній статті ми навчилися запускати контейнери командою docker run та виконувати базові операції. Але щоб ефективно працювати з Docker, потрібно глибше розуміти, що відбувається з контейнером протягом його життя — від моменту створення до остаточного видалення.
Контейнер — це не статична сутність. Він проходить через різні стани, реагує на сигнали, виконує процеси, генерує логи та споживає ресурси. Розуміння цього життєвого циклу критично важливе для налагодження проблем, оптимізації продуктивності та забезпечення надійності застосунків у продакшені.
У цій статті ми детально розглянемо стани контейнера, дізнаємося про роль процесу PID 1, навчимося виконувати команди всередині працюючих контейнерів, інспектувати їхню конфігурацію, моніторити споживання ресурсів та керувати даними. Це знання перетворить вас з користувача Docker на того, хто справді розуміє, як працюють контейнери.
docker run, docker ps, docker stop з попередньої статті. Тут ми заглибимося в деталі та розглянемо просунуті сценарії.Контейнер Docker може перебувати в одному з кількох станів. Розуміння цих станів та переходів між ними — ключ до ефективного управління контейнерами.
Стан Created виникає, коли ви використовуєте команду docker create замість docker run:
docker create --name my-nginx nginx
Що відбувається:
Контейнер існує, але нічого не виконується. Це корисно, коли потрібно створити контейнер заздалегідь та запустити його пізніше:
# Створення контейнера
docker create --name web -p 8080:80 nginx
# Пізніше: запуск
docker start web
Перевірка стану:
docker ps -a --filter "status=created"
Стан Running означає, що головний процес контейнера (PID 1) виконується. Це робочий стан контейнера, коли він виконує свою функцію.
docker run -d --name web nginx
docker ps # STATUS: Up X seconds/minutes
У цьому стані:
docker execСтан Paused — це особливий стан, коли всі процеси контейнера "заморожені" на рівні ядра через cgroup freezer. Процеси не виконуються, але залишаються в пам'яті.
# Призупинити контейнер
docker pause web
# Перевірка стану
docker ps --filter "status=paused"
# Відновити виконання
docker unpause web
Це відрізняється від docker stop:
Pause корисний для тимчасового звільнення CPU без втрати стану процесів у пам'яті.
Стан Exited означає, що головний процес контейнера завершився (з будь-яким exit code). Контейнер зупинено, але не видалено.
Контейнер може потрапити в стан Exited кількома способами:
Природне завершення процесу:
docker run --name test alpine echo "Hello"
# Процес echo завершується одразу після виведення
docker ps -a # STATUS: Exited (0) X seconds ago
Команда docker stop:
docker stop web
# Docker відправляє SIGTERM, чекає 10 секунд, потім SIGKILL
docker ps -a # STATUS: Exited (0 або 137) X seconds ago
Помилка в застосунку:
docker run --name broken alpine sh -c "exit 1"
docker ps -a # STATUS: Exited (1) X seconds ago
Exit code має значення:
Перегляд exit code:
docker inspect --format='{{.State.ExitCode}}' container_name
Це не стан контейнера, а його відсутність. Після docker rm контейнер повністю видаляється з системи:
docker rm web
Видаляється:
Але не видаляються:
Розуміння того, як працюють процеси всередині контейнера, критично важливе для налагодження та правильної архітектури застосунків.

У кожному контейнері є один головний процес, який отримує PID 1 у namespace контейнера. Цей процес особливий з кількох причин:
Життя контейнера прив'язане до PID 1: Коли процес PID 1 завершується, контейнер автоматично зупиняється. Всі інші процеси в контейнері — це дочірні процеси PID 1.
PID 1 отримує сигнали від Docker: Коли ви виконуєте docker stop, Docker відправляє SIGTERM саме процесу PID 1. Якщо процес не завершується за 10 секунд (за замовчуванням), Docker відправляє SIGKILL.
PID 1 відповідає за reaping zombie processes: У Unix-системах батьківський процес повинен "збирати" (reap) завершені дочірні процеси. Якщо PID 1 не робить цього правильно, можуть накопичуватися zombie-процеси.
Запустимо Nginx та подивимося на його процеси:
# Запуск Nginx
docker run -d --name web nginx
# Перегляд процесів з хоста
docker top web
Вивід:
UID PID PPID C STIME TTY TIME CMD
root 12345 12320 0 08:30 ? 00:00:00 nginx: master process nginx -g daemon off;
101 12380 12345 0 08:30 ? 00:00:00 nginx: worker process
101 12381 12345 0 08:30 ? 00:00:00 nginx: worker process
Тут:
Тепер подивимося зсередини контейнера:
docker exec web ps aux
Вивід:
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.0 0.1 10640 5432 ? Ss 08:30 0:00 nginx: master process nginx -g daemon off;
nginx 29 0.0 0.0 11076 2344 ? S 08:30 0:00 nginx: worker process
nginx 30 0.0 0.0 11076 2344 ? S 08:30 0:00 nginx: worker process
Зверніть увагу: всередині контейнера master process має PID 1, тоді як на хості він має інший PID. Це демонструє ізоляцію PID namespace.
Розглянемо типову помилку при написанні Dockerfile:
# ПОГАНО: shell form
CMD nginx -g "daemon off;"
Коли ви використовуєте shell form, Docker запускає команду через /bin/sh -c:
/bin/sh -c 'nginx -g "daemon off;"'
У цьому випадку PID 1 — це shell (/bin/sh), а не nginx. Це створює проблеми:
Сигнали не доходять до nginx: docker stop відправляє SIGTERM до shell, але shell може не передати його nginx. Результат — Docker чекає 10 секунд та відправляє SIGKILL, що призводить до некоректного завершення nginx.
Zombie processes: Якщо nginx створює дочірні процеси, shell може не збирати їх після завершення.
Правильний спосіб — використовувати exec form:
# ДОБРЕ: exec form
CMD ["nginx", "-g", "daemon off;"]
Або явно використовувати exec у shell form:
# ДОБРЕ: exec у shell form
CMD exec nginx -g "daemon off;"
Тепер nginx отримує PID 1 та коректно обробляє сигнали.
Коли ви виконуєте docker stop, відбувається наступне:
Ви можете змінити таймаут:
# Дати 30 секунд на graceful shutdown
docker stop -t 30 web
# Або встановити при запуску
docker run -d --stop-timeout 30 --name web nginx
Перевіримо, як швидко контейнер зупиняється:
time docker stop web
Якщо вивід близько 0.2-0.5 секунд — процес коректно обробив SIGTERM. Якщо близько 10 секунд — процес не обробив сигнал, і Docker використав SIGKILL.
CancellationToken у IHostApplicationLifetime.ApplicationStopping. Це дозволить вашому застосунку коректно завершитися при docker stop.Одна з найпотужніших можливостей Docker — виконання команд всередині працюючого контейнера без перезапуску.
docker exec [OPTIONS] CONTAINER COMMAND [ARG...]
# Перегляд змінних оточення
docker exec web env
# Перегляд процесів
docker exec web ps aux
# Перевірка версії Nginx
docker exec web nginx -v
# Перегляд файлів
docker exec web ls -la /etc/nginx/
Найчастіше docker exec використовується для відкриття shell у контейнері:
docker exec -it web bash
Прапорці -it (interactive + TTY) дозволяють інтерактивно працювати з shell. Тепер ви всередині контейнера і можете:
# Перегляд конфігурації Nginx
cat /etc/nginx/nginx.conf
# Перевірка логів
tail -f /var/log/nginx/access.log
# Тестування мережі
curl localhost
# Встановлення утиліт для діагностики
apt update && apt install -y curl vim
# Вихід (контейнер продовжує працювати)
exit
docker run -it, де вихід з shell зупиняє контейнер (якщо shell — це PID 1), docker exec створює новий процес всередині існуючого контейнера. Вихід з exec-shell не впливає на головний процес контейнера.За замовчуванням docker exec виконує команди від того ж користувача, що й головний процес (зазвичай root). Можна змінити:
# Виконати від користувача nginx (UID 101)
docker exec -u nginx web whoami
# Виконати від конкретного UID
docker exec -u 1000:1000 web id
# Виконати команду в конкретній директорії
docker exec -w /var/log/nginx web ls -la
Діагностика проблем з мережею:
# Встановлення інструментів
docker exec web apt update && apt install -y iputils-ping curl
# Перевірка з'єднання
docker exec web ping -c 3 google.com
docker exec web curl -I https://example.com
Перегляд логів застосунку:
# Якщо застосунок пише логи у файл
docker exec web tail -f /app/logs/application.log
Виконання міграцій бази даних:
# Для .NET застосунку з Entity Framework
docker exec web dotnet ef database update
Backup бази даних:
# PostgreSQL
docker exec postgres pg_dump -U postgres mydb > backup.sql
# MySQL
docker exec mysql mysqldump -u root -p mydb > backup.sql
docker exec (встановлення пакетів, редагування файлів), зберігаються лише в writable layer контейнера. Якщо ви видалите контейнер та створите новий з того ж образу, всі зміни зникнуть. Для постійних змін модифікуйте Dockerfile та пересоберіть образ.Команда docker inspect повертає повну конфігурацію та стан контейнера у форматі JSON. Це найпотужніший інструмент для діагностики.
docker inspect web
Вивід — величезний JSON-об'єкт з сотнями полів. Розглянемо найважливіші секції:
docker inspect --format='{{json .State}}' web | jq
Вивід:
{
"Status": "running",
"Running": true,
"Paused": false,
"Restarting": false,
"OOMKilled": false,
"Dead": false,
"Pid": 12345,
"ExitCode": 0,
"Error": "",
"StartedAt": "2026-04-14T08:15:30.123456789Z",
"FinishedAt": "0001-01-01T00:00:00Z"
}
Ключові поля:
docker inspect --format='{{json .NetworkSettings}}' web | jq
Показує:
Швидкий доступ до IP-адреси:
docker inspect --format='{{.NetworkSettings.IPAddress}}' web
docker inspect --format='{{json .Mounts}}' web | jq
Показує всі змонтовані томи та bind mounts:
docker inspect --format='{{json .Config.Env}}' web | jq
Показує всі змінні оточення, передані контейнеру.
docker inspect --format='{{json .HostConfig}}' web | jq | grep -A 5 Memory
Показує налаштовані ліміти CPU, пам'яті, I/O.
# Отримати лише IP-адресу
docker inspect -f '{{.NetworkSettings.IPAddress}}' web
# Отримати проброшені порти
docker inspect -f '{{json .NetworkSettings.Ports}}' web
# Отримати exit code
docker inspect -f '{{.State.ExitCode}}' web
# Отримати час запуску
docker inspect -f '{{.State.StartedAt}}' web
# Перевірити, чи контейнер працює
docker inspect -f '{{.State.Running}}' web
jq для зручної роботи з JSON-виводом docker inspect. Це дозволяє фільтрувати, форматувати та аналізувати дані: docker inspect web | jq '.State'Логи — це перше місце, куди потрібно дивитися при діагностиці проблем. Docker збирає STDOUT та STDERR головного процесу контейнера.
docker logs web
Показує весь вивід контейнера з моменту запуску.
Останні N рядків:
docker logs --tail 50 web
Слідкування в реальному часі (як tail -f):
docker logs -f web
З часовими мітками:
docker logs -t web
Вивід:
2026-04-14T08:15:30.123456789Z /docker-entrypoint.sh: Configuration complete
2026-04-14T08:15:30.234567890Z 2026/04/14 08:15:30 [notice] 1#1: start worker processes
Логи за період:
# За останню годину
docker logs --since 1h web
# З конкретного часу
docker logs --since 2026-04-14T08:00:00 web
# До конкретного часу
docker logs --until 2026-04-14T09:00:00 web
Комбінація опцій:
# Останні 100 рядків з часовими мітками, слідкування
docker logs -f -t --tail 100 web
За замовчуванням Docker зберігає логи у JSON-файлах:
# Шлях до логів (на Linux)
/var/lib/docker/containers/<container_id>/<container_id>-json.log
Можна переглянути безпосередньо:
sudo tail -f /var/lib/docker/containers/$(docker inspect -f '{{.Id}}' web)/$(docker inspect -f '{{.Id}}' web)-json.log
Docker підтримує різні log drivers для відправки логів у зовнішні системи:
# Використання syslog
docker run -d --log-driver syslog --name web nginx
# Використання json-file з ротацією
docker run -d \
--log-driver json-file \
--log-opt max-size=10m \
--log-opt max-file=3 \
--name web nginx
Доступні log drivers:
json-file (за замовчуванням)syslogjournaldgelf (Graylog)fluentdawslogs (AWS CloudWatch)none (вимкнути логування)json-file або journald, команда docker logs не працюватиме. Логи будуть доступні лише через відповідну систему логування.Команда docker stats показує споживання ресурсів контейнерами в реальному часі — аналог top для контейнерів.
docker stats
Вивід (оновлюється кожну секунду):
CONTAINER ID NAME CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O PIDS
a3f5c8d9e2b1 web 0.05% 12.5MiB / 7.775GiB 0.16% 1.2kB / 648B 0B / 0B 3
Розберемо колонки:
CPU % — відсоток використання CPU. Якщо у вас 4 ядра, максимум може бути 400% (якщо контейнер використовує всі ядра).
MEM USAGE / LIMIT — поточне споживання пам'яті та ліміт (якщо встановлено). Якщо ліміт не встановлено, показується загальна пам'ять системи.
MEM % — відсоток від ліміту (або від загальної пам'яті системи).
NET I/O — мережевий трафік (отримано / відправлено) з моменту запуску контейнера.
BLOCK I/O — дисковий I/O (читання / запис).
PIDS — кількість процесів у контейнері.
# Один контейнер
docker stats web
# Кілька контейнерів
docker stats web db redis
# Всі контейнери (включно зі зупиненими)
docker stats --all
# Показати статистику один раз та вийти
docker stats --no-stream
Корисно для скриптів, які збирають метрики.
# Кастомний формат
docker stats --format "table {{.Container}}\t{{.CPUPerc}}\t{{.MemUsage}}"
Виявлення контейнерів з високим споживанням CPU:
docker stats --no-stream --format "table {{.Name}}\t{{.CPUPerc}}" | sort -k2 -rn
Моніторинг споживання пам'яті:
docker stats --no-stream --format "table {{.Name}}\t{{.MemUsage}}\t{{.MemPerc}}"
Експорт метрик у CSV:
docker stats --no-stream --format "{{.Name}},{{.CPUPerc}},{{.MemUsage}},{{.NetIO}}" > stats.csv
Іноді потрібно скопіювати файли між хостом та контейнером. Команда docker cp працює як scp, але для контейнерів.
# Копіювання файлу
docker cp /path/on/host/file.txt web:/path/in/container/
# Копіювання директорії
docker cp /path/on/host/mydir web:/path/in/container/
# Копіювання файлу
docker cp web:/etc/nginx/nginx.conf ./nginx.conf
# Копіювання директорії
docker cp web:/var/log/nginx ./nginx-logs
Backup конфігурації:
# Зберегти конфігурацію Nginx
docker cp web:/etc/nginx/nginx.conf ./backup/nginx.conf
# Зберегти всю директорію конфігурації
docker cp web:/etc/nginx ./backup/nginx-config
Відновлення конфігурації:
# Відновити конфігурацію
docker cp ./backup/nginx.conf web:/etc/nginx/nginx.conf
# Перезавантажити Nginx для застосування змін
docker exec web nginx -s reload
Витягування логів:
# Якщо застосунок пише логи у файл
docker cp web:/app/logs ./app-logs
Розгортання коду (для розробки):
# Копіювання оновленого коду
docker cp ./src/updated-file.cs web:/app/src/
# Перезапуск застосунку
docker restart web
docker cp працює навіть зі зупиненими контейнерами. Це корисно для витягування даних з контейнера, який не запускається через помилку.Не зберігає права доступу: Файли копіюються з правами за замовчуванням. Якщо потрібні конкретні права, встановіть їх після копіювання через docker exec.
Не підходить для великих обсягів даних: Для великих файлів або баз даних краще використовувати томи або bind mounts.
Не замінює томи: Для постійного зберігання даних використовуйте Docker volumes, а не docker cp.
Команда docker top показує процеси, що виконуються в контейнері, з точки зору хоста.
docker top web
Вивід:
UID PID PPID C STIME TTY TIME CMD
root 12345 12320 0 08:30 ? 00:00:00 nginx: master process nginx -g daemon off;
101 12380 12345 0 08:30 ? 00:00:00 nginx: worker process
101 12381 12345 0 08:30 ? 00:00:00 nginx: worker process
# Показати додаткову інформацію
docker top web aux
# Показати дерево процесів
docker top web -ef
# З точки зору хоста (реальні PID)
docker top web
# З точки зору контейнера (PID в namespace)
docker exec web ps aux
Різниця:
docker top показує PID на хості (корисно для діагностики на рівні системи)docker exec ps показує PID всередині контейнера (корисно для розуміння структури застосунку)Docker дозволяє обмежувати споживання ресурсів контейнерами через cgroups. Це критично важливо для продакшену, щоб один контейнер не міг "з'їсти" всю систему.

# Жорсткий ліміт: 512 МБ
docker run -d --memory="512m" --name limited nginx
# Ліміт + swap
docker run -d --memory="512m" --memory-swap="1g" --name limited nginx
# Без swap (memory-swap = memory)
docker run -d --memory="512m" --memory-swap="512m" --name limited nginx
Що відбувається при досягненні ліміту:
Перевірка, чи був контейнер вбитий через OOM:
docker inspect --format='{{.State.OOMKilled}}' limited
# Обмежити до 50% одного ядра
docker run -d --cpus="0.5" --name limited nginx
# Обмежити до 2 ядер
docker run -d --cpus="2.0" --name limited nginx
# Встановити пріоритет (CPU shares)
docker run -d --cpu-shares=512 --name low-priority nginx
docker run -d --cpu-shares=1024 --name high-priority nginx
--cpus — жорсткий ліміт. Контейнер не може використовувати більше вказаної кількості CPU.
--cpu-shares — відносний пріоритет. Якщо система не завантажена, контейнер може використовувати більше CPU. Коли система завантажена, CPU розподіляється пропорційно shares.
# Обмежити швидкість читання (bytes per second)
docker run -d --device-read-bps /dev/sda:1mb --name limited nginx
# Обмежити швидкість запису
docker run -d --device-write-bps /dev/sda:1mb --name limited nginx
# Обмежити IOPS (operations per second)
docker run -d --device-read-iops /dev/sda:100 --name limited nginx
docker run -d \
--name production-app \
--memory="2g" \
--memory-swap="2g" \
--cpus="1.5" \
--restart=unless-stopped \
myapp:latest
# Оновити ліміт пам'яті
docker update --memory="1g" web
# Оновити ліміт CPU
docker update --cpus="2.0" web
# Оновити кілька параметрів
docker update --memory="1g" --cpus="2.0" web
DOTNET_GCHeapHardLimit (абсолютний ліміт) або DOTNET_GCHeapHardLimitPercent (відсоток від ліміту контейнера).У цій статті ми глибоко занурилися в життєвий цикл контейнера та інструменти для управління ним.
Ключові концепції:
docker exec дозволяє виконувати команди в працюючому контейнері без перезапускуdocker inspect надає повну інформацію про конфігурацію та стан контейнераdocker logs збирає STDOUT/STDERR головного процесуdocker stats показує споживання ресурсів у реальному часіdocker cp дозволяє копіювати файли між хостом та контейнеромВажливі команди:
docker ps -a — всі контейнери з їхніми станамиdocker exec -it container bash — інтерактивний shelldocker inspect container — детальна інформаціяdocker logs -f container — логи в реальному часіdocker stats — моніторинг ресурсівdocker top container — процеси контейнераdocker update --memory=1g container — оновлення обмеженьРозуміння життєвого циклу контейнера та вміння діагностувати проблеми — це фундамент для ефективної роботи з Docker у продакшені.
У наступній статті ми перейдемо до Docker-образів — розглянемо, що це таке, як вони влаштовані, як працюють шари та чому образи є незмінними.
Створіть контейнер та проведіть його через всі стани:
# 1. Створення без запуску
docker create --name lifecycle-test nginx
# 2. Перевірка стану
docker ps -a --filter "name=lifecycle-test"
# 3. Запуск
docker start lifecycle-test
# 4. Призупинення
docker pause lifecycle-test
# 5. Відновлення
docker unpause lifecycle-test
# 6. Зупинка
docker stop lifecycle-test
# 7. Перезапуск
docker start lifecycle-test
# 8. Видалення
docker stop lifecycle-test
docker rm lifecycle-test
Питання:
docker ps -a на кожному кроці?docker exec на призупиненому контейнері?docker rm на працюючому контейнері?Створіть два контейнери з різними підходами до PID 1:
# Shell wrapper (ПОГАНО)
docker run -d --name bad-pid alpine sh -c "sleep 3600"
# Exec form (ДОБРЕ)
docker run -d --name good-pid alpine sleep 3600
# Перевірка PID 1
docker exec bad-pid ps aux
docker exec good-pid ps aux
# Тест graceful shutdown
time docker stop bad-pid
time docker stop good-pid
Питання:
Запустіть контейнер з обмеженнями та навантажте його:
# Запуск з обмеженнями
docker run -d --name stress-test \
--memory="256m" \
--cpus="0.5" \
alpine sleep 3600
# Моніторинг
docker stats stress-test
# Створення навантаження (в іншому терміналі)
docker exec stress-test sh -c "yes > /dev/null"
# Спостереження за CPU
docker stats --no-stream stress-test
Питання:
Створіть контейнер, який падає, та продіагностуйте проблему:
# Контейнер, який падає
docker run -d --name broken alpine sh -c "echo Starting && sleep 5 && exit 1"
# Почекайте 6 секунд, потім діагностуйте
docker ps -a | grep broken
docker logs broken
docker inspect --format='{{.State.ExitCode}}' broken
docker inspect --format='{{.State.Error}}' broken
Завдання: