Траблшутинг Docker: типичные ошибки и как чинить

Рейтинг: 79.6% · 15 голосов
Практический курс по Docker: образы, контейнеры, тома, сети, Compose и продакшен. Уроки по главам с обсуждением.
Ответить
Аватара пользователя
Marina_DevOps
Сообщения: 54
Зарегистрирован: 11 май 2026, 05:31

Траблшутинг Docker: типичные ошибки и как чинить

Сообщение Marina_DevOps »

Оглавление курса (46)
  1. Что такое Docker и какие задачи он решает
  2. Установка Docker и запуск первого контейнера
  3. Образы: слои, теги и реестр Docker Hub
  4. Пишем свой Dockerfile
  5. Тома и хранение данных: volumes и bind mounts
  6. Сети в Docker: связываем контейнеры между собой
  7. Переменные окружения и конфигурация контейнеров
  8. Docker Compose: поднимаем многоконтейнерное приложение
  9. Оптимизация образов: multi-stage сборка, размер и кэш слоёв
  10. Логи, отладка и мониторинг контейнеров
  11. Базовая безопасность контейнеров
  12. Подготовка к продакшену: что важно учесть
  13. Dockerfile глубже: ENTRYPOINT и CMD, HEALTHCHECK, .dockerignore, запуск не от root
  14. Реестры образов: приватные registry, push и pull, теги и digest, imagePullSecrets
  15. BuildKit и buildx: multi-arch сборки, секреты сборки, экспорт кэша
  16. Docker в CI/CD: автосборка, сканирование образов (Trivy, Docker Scout), публикация
  17. Итоговый проект и куда расти: от Dockerfile до прода, обзор оркестрации (Kubernetes, Podman, OCI)
  18. Архитектура Docker: dockerd, containerd, runc и OCI
  19. Как работает изоляция: namespaces и cgroups
  20. Образы изнутри: слои, OverlayFS и storage driver
  21. Жизненный цикл контейнера и политики перезапуска
  22. Отладка контейнеров: exec, attach, nsenter, debug
  23. Сети Docker глубже: bridge, overlay, macvlan и iptables
  24. Docker Compose глубже: profiles, healthcheck-зависимости, override
  25. Данные и тома глубже: драйверы, бэкап, права
  26. Секреты и конфигурация: как не светить пароли
  27. Ограничение ресурсов: CPU, память, OOM и cgroups
  28. Здоровье и автозапуск: HEALTHCHECK, init и сигналы
  29. Логирование контейнеров: драйверы, ротация, сбор
  30. Мониторинг контейнеров: stats, cAdvisor, Prometheus
  31. Безопасность глубже: rootless, user namespaces, seccomp, capabilities
  32. Supply chain: сканирование, SBOM и подпись образов
  33. Глубокая оптимизация образов: distroless, scratch, кэш
  34. Multi-arch и сборка под ARM: buildx и эмуляция
  35. Реестры в продакшене: Harbor и облачные registry
  36. Docker без Docker: Podman, containerd, nerdctl, Buildah
  37. От Compose к оркестрации: когда контейнеров становится много
  38. Docker Swarm: встроенная оркестрация
  39. Сборка образов в CI без демона: kaniko, BuildKit, Buildah
  40. Траблшутинг Docker: типичные ошибки и как чинить (вы здесь)
  41. Производительность и эксплуатация Docker-хоста
  42. Docker Desktop, WSL2 и альтернативы на Windows и Mac
  43. Контейнеризация приложений: бэкенд, фронтенд, БД
  44. Docker и Kubernetes: containerd, миграция, kompose
  45. Лучшие практики и антипаттерны Docker
  46. Сквозной проект: production-ready стек и путь дальше
Три часа ночи, прод лежит, в чате паника, а у тебя в терминале короткая злая строчка: docker: Error response from daemon. И всё. Никакого стектрейса, никакой подсказки, что именно сломалось. Знакомо? Тогда этот урок для тебя. Большинство docker errors на практике - это не экзотика, а десяток одних и тех же сценариев, которые повторяются из проекта в проект: кончилось место, демон не стартует, образ не тянется, контейнер падает сразу после старта, занят порт, permission denied на томе, контейнер прибило по памяти. Разберём каждый класс до механики - не "введи команду и молись", а почему так происходит и где смотреть факты. К концу урока у тебя в голове будет дерево решений, по которому ты за минуту локализуешь любую docker ошибка вместо часа гадания.

Главный принцип траблшутинга Docker: не угадывай - спрашивай систему. У тебя есть три источника правды. Логи самого приложения (docker logs), состояние объекта (docker inspect), и логи демона/ядра (journalctl, dmesg). 90 процентов разборов сводятся к тому, чтобы посмотреть в правильный источник, а не в случайный.

No space left on device: куда уходят гигабайты

Самая частая боль на длинной дистанции - docker no space left. Симптом: build падает на середине, контейнер не стартует, push отваливается, в логах write /var/lib/docker/...: no space left on device. Почти всегда виноват не диск целиком, а раздутый /var/lib/docker. Docker по своей природе накапливает мусор: остановленные контейнеры, висячие (dangling) образы, неиспользуемые тома и - главный пожиратель - build cache от BuildKit.

Первое, что делаешь - смотришь раскладку, а не df -h наугад:

Код: Выделить всё

docker system df
Вывод с разбором полей:

Код: Выделить всё

TYPE            TOTAL   ACTIVE  SIZE      RECLAIMABLE
Images          42      6       18.7GB    14.2GB (75%)
Containers      11      3       240MB     180MB (74%)
Local Volumes   9       2       6.1GB     5.4GB (88%)
Build Cache     310     0       22.3GB    22.3GB (100%)
Читай так: RECLAIMABLE - сколько можно безопасно освободить. Build Cache 22 ГБ с reclaimable 100 процентов - вот он, виновник. ACTIVE - сколько реально используется живыми контейнерами. Если хочешь детализацию по каждому объекту, добавь docker system df -v - увидишь размер каждого образа и тома поимённо.

Чистка - от мягкого к жёсткому. Безопасный вариант, который трогает только мусор:

Код: Выделить всё

docker system prune
Он удалит остановленные контейнеры, висячие образы, неиспользуемые сети и build cache. Но по умолчанию НЕ трогает тома (там твои данные) и НЕ трогает образы, которые просто не привязаны к контейнеру, но имеют тег. Хочешь агрессивнее - флаги:

Код: Выделить всё

docker system prune -a --volumes
Вот тут осторожно. Флаг -a сносит ВСЕ образы без запущенного контейнера, а --volumes - все неиспользуемые тома. На проде с --volumes можно случайно снести базу, если она в анонимном томе и контейнер в этот момент остановлен. Правило: --volumes на проде только руками и адресно. Если давит именно build cache, чисть точечно:

Код: Выделить всё

docker builder prune --filter "until=72h"
Это удалит кэш старше трёх суток, не сломав свежие сборки. И ещё один тихий пожиратель места - логи контейнеров. По умолчанию драйвер json-file пишет stdout/stderr в /var/lib/docker/containers/<id>/<id>-json.log без ограничения. Болтливый контейнер за месяц нагенерит десятки гигабайт одного лог-файла. Лечится ротацией в /etc/docker/daemon.json:

Код: Выделить всё

{
  "log-driver": "json-file",
  "log-opts": { "max-size": "10m", "max-file": "3" }
}
После правки - systemctl restart docker. Учти: настройка применяется к НОВЫМ контейнерам, старые надо пересоздать.

Изображение

Docker не запускается: разбор отказа демона

Сценарий "docker не запускается" пугает новичков, потому что ломается всё разом: любая команда отвечает Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running? Тут важно разделить две вещи: демон не стартует вообще или ты просто не в группе docker.

Сначала проверь, жив ли сервис:

Код: Выделить всё

systemctl status docker
Если видишь Active: failed или активный restart-цикл - демон реально падает. Идёшь в его логи, это единственный надёжный источник:

Код: Выделить всё

journalctl -u docker --no-pager -n 50
Типичные причины из практики. Первая - битый daemon.json: одна лишняя запятая в JSON, и dockerd при старте кричит unable to configure the Docker daemon with file /etc/docker/daemon.json: invalid character. Лечение - провалидировать JSON (хоть python3 -m json.tool /etc/docker/daemon.json) и поправить. Вторая - повреждённое хранилище в /var/lib/docker после жёсткого выключения или закончившегося места: в логах failed to start daemon ... error initializing graphdriver. Тут крайняя мера - остановить демон и убрать каталог, но это сносит ВСЕ образы и контейнеры, поэтому только когда данные не жалко. Третья - конфликт сокета или занятый порт, если в конфиге прописан hosts с TCP. Если же systemctl status docker показывает active (running), а команды всё равно отбиваются по правам - демон тут ни при чём, переходи к разделу про permission denied ниже.

Образ не тянется: rate limit и доступность из РФ

docker pull зависает или отдаёт ошибку - это две принципиально разные истории, и важно не путать.

История первая - docker rate limit. Симптом дословный: toomanyrequests: You have reached your pull rate limit. По актуальным на 2026 год правилам Docker Hub отдаёт анонимам 100 пуллов за 6 часов, а залогиненному бесплатному аккаунту (Docker Personal) - 200 за 6 часов. Считается по IP для анонимов, поэтому на CI-раннере или за общим корпоративным NAT лимит выжигается мгновенно: десятки сборок делят один внешний адрес. Лечение - аутентификация, она сразу поднимает лимит и считает его на аккаунт, а не на IP:

Код: Выделить всё

docker login
На CI логинься через секреты до первого pull. Платные планы лимит снимают.

История вторая, для нас актуальнее - доступность Docker Hub из России. С конца мая 2024 года registry-1.docker.io отдаёт российским IP отказ, ссылаясь на санкции, и эта блокировка к 2026 году никуда не делась. Симптом отличается от rate limit: не toomanyrequests, а таймаут, 403 или connection reset при пуле. Лечится зеркалом. Прописываешь registry-mirrors в /etc/docker/daemon.json:

Код: Выделить всё

{
  "registry-mirrors": ["https://mirror.gcr.io", "https://cr.yandex/mirror"]
}
После systemctl restart docker демон будет ходить за анонимными образами Docker Hub через зеркало. Важная тонкость: registry-mirror работает ТОЛЬКО для образов Docker Hub и только для pull. Образы из приватных реестров и push через зеркало не идут. Для своих образов в RU-реалиях держи их в Yandex Container Registry или VK Cloud (cr.yandex/<id>/myimage) и тянь напрямую оттуда полным путём - тогда зеркало вообще не нужно. И всегда проверяй сам адрес: typo вроде nginx:latetst даст manifest unknown, а это уже не про сеть.

Контейнер падает сразу: exit code и проблема PID 1

Запустил - и docker ps пусто, контейнер в Exited через секунду. Это классический docker failed, и тут безотказная связка из двух команд. Сначала логи:

Код: Выделить всё

docker logs <container>
Потом - код выхода и причина, которую часто пропускают:

Код: Выделить всё

docker inspect <container> --format '{{.State.ExitCode}} {{.State.OOMKilled}} {{.State.Error}}'
Коды выхода - это язык, на котором контейнер объясняет смерть. Запомни главные:
  • 0 - штатное завершение. Если "упал" с нулём - значит, процесс честно доработал и вышел, контейнеру просто нечего делать дальше.
  • 1 / 2 - ошибка приложения. Смотри docker logs, там стектрейс или сообщение.
  • 125 - ошибка САМОГО docker run (битый флаг, нет такого образа). Контейнер даже не стартовал.
  • 126 - файл entrypoint найден, но не исполняемый (забыл chmod +x на скрипте).
  • 127 - команда/бинарь не найдены внутри образа (нет в PATH, не та архитектура).
  • 137 - убит SIGKILL, почти всегда OOM. Подробно ниже.
  • 139 - segmentation fault, баг в коде приложения, не в ресурсах.
  • 143 - SIGTERM, штатная остановка снаружи (docker stop, оркестратор).
Отдельно стоит самая коварная причина мгновенного выхода с кодом 0 - проблема foreground-процесса и PID 1. Контейнер живёт ровно столько, сколько живёт его главный процесс с PID 1. Если ты в CMD запускаешь сервис в фоне или через демонизацию (nginx без daemon off, service start, любой & в конце) - стартовый процесс отрабатывает, возвращает управление, PID 1 завершается, и Docker честно гасит контейнер. Правило железное: в контейнере процесс должен работать в foreground и не отдавать управление. Для nginx это daemon off;, для многих демонов - флаг вроде -F или --foreground. Антипаттерн, который видишь в чужих Dockerfile, - CMD ["service", "nginx", "start"]: оно стартует и сразу выходит. И ещё про PID 1: процесс под этим номером не получает дефолтных обработчиков сигналов и не реапит зомби-процессы. Поэтому твой Node/Python как PID 1 может игнорировать SIGTERM и висеть до SIGKILL на каждом docker stop по 10 секунд. Лечится флагом docker run --init (подкладывает крошечный init tini) или явным tini в образе.

Порт занят и сеть не работает

docker run -p 8080:80 отвечает Bind for 0.0.0.0:8080 failed: port is already allocated. Перевод: на хосте порт 8080 уже кем-то держится - другим контейнером, забытым старым контейнером или сервисом хоста. Находишь, кто сидит:

Код: Выделить всё

docker ps --filter "publish=8080"
ss -tlnp | grep 8080
Первая команда покажет контейнер, опубликовавший порт, вторая - любой процесс хоста на этом порту. Дальше либо гасишь старый контейнер, либо вешаешь новый на другой порт.

Сложнее, когда порт опубликован, контейнер жив, а снаружи не отвечает. Тут три типичных грабли. Первая: приложение внутри слушает 127.0.0.1, а не 0.0.0.0. Тогда publish порта бесполезен - трафик с хоста приходит на интерфейс контейнера, но процесс его не принимает. Лечение в конфиге приложения: слушать 0.0.0.0. Вторая: межконтейнерная связь по localhost. Внутри контейнера localhost - это сам контейнер, а не сосед. Контейнеры в одной сети должны обращаться друг к другу по ИМЕНИ сервиса (в compose это автоматический DNS), а не по 127.0.0.1. Третья, на голом Linux: Docker сам управляет правилами iptables (цепочка DOCKER), и если на хосте включён firewalld или кто-то руками чистил iptables, проброс ломается. Проверка - docker network inspect bridge и состояние фаервола; на проде Docker и firewalld нужно мирить осознанно, а не отключать фаервол.

Permission denied: тома, UID и SELinux

Два разных permission denied, которые путают. Первый - на сам docker.sock: got permission denied while trying to connect to the Docker daemon socket. Это не про контейнер, а про твоего юзера: ты не в группе docker. Лечение - usermod -aG docker $USER и перелогиниться (группа подхватывается в новой сессии). Без sudo это самый частый затык новичка.

Второй, интереснее - permission denied внутри контейнера при работе с примонтированным томом. Причина в том, что Linux сопоставляет права по числовому UID, а не по имени пользователя. Если в контейнере процесс работает под UID 1000 (или вообще под non-root по требованиям безопасности), а файлы в смонтированном каталоге хоста принадлежат другому UID - получаешь отказ на запись. Чинится согласованием UID: запускаешь контейнер с нужным числовым идентификатором через docker run --user 1000:1000 либо правишь владельца на хосте/в томе. На системах с SELinux (RHEL, Fedora, Rocky) добавляется отдельный слой: метки безопасности. Том без правильной метки даёт permission denied, даже когда UID совпадает. Решение - суффикс :z или :Z к bind-mount:

Код: Выделить всё

docker run -v /opt/data:/data:z myimage
Строчная z ставит общую метку (том могут делить несколько контейнеров), заглавная Z - приватную, эксклюзивную для одного контейнера. На системах без SELinux эти суффиксы просто игнорируются, так что в кросс-дистрибутивных инструкциях их часто оставляют.

OOM и код 137: когда памяти не хватило

Контейнер внезапно умер, в docker ps статус Exited (137). Код 137 - это 128 плюс 9, то есть процесс получил SIGKILL. В подавляющем большинстве случаев это OOM Killer ядра: контейнер уперся в лимит памяти, и cgroups его прибили. Проверяешь без догадок:

Код: Выделить всё

docker inspect <container> --format '{{.State.OOMKilled}}'
Если true - это память, точка. Для подтверждения на уровне ядра смотри dmesg, там будет строка вида Memory cgroup out of memory: Killed process ... с именем процесса. Причин две. Либо ты сам поставил жёсткий лимит docker run -m 512m, и приложению его мало - тогда либо поднимай лимит, либо чини утечку/прожорливость. Либо лимита нет, и контейнер съел всю память хоста, после чего ядро прибило самый жирный процесс в системе (не обязательно даже виноватый). Поэтому на проде лимиты памяти выставляют ВСЕГДА - чтобы один контейнер не утянул за собой соседей. Важно понимать разницу: OOMKilled true - это контейнер превысил свой cgroup-лимит, а если OOMKilled false при коде 137 - значит, SIGKILL прилетел снаружи (docker kill, оркестратор не дождался graceful shutdown). Это два разных диагноза с разным лечением.

Дерево решений

Сведём всё в маршрут, по которому идёшь сверху вниз. Команда вообще не выполняется и ругается на сокет - проблема не в контейнере: либо демон не стартует (systemctl status docker, journalctl -u docker), либо ты не в группе docker. Команда выполнилась, но контейнер в Exited - смотри код выхода через inspect и логи: 137 ведёт к OOM, 125-127 к проблеме запуска самого образа, 0 при мгновенном выходе - к PID 1 без foreground-процесса. no space left - docker system df, потом prune от мягкого к жёсткому. Образ не тянется - различай toomanyrequests (login) и таймаут/403 из РФ (зеркало). Сеть - сначала port is already allocated через ss и docker ps, потом слушает ли процесс 0.0.0.0 и не мешает ли firewalld. Permission denied - разведи сокет (группа) и том (UID плюс SELinux :z). Этот порядок экономит часы: ты не тычешь наугад, а движешься от самого внешнего слоя к самому внутреннему.

Мини-лаба

Воспроизведи грабли руками - это лучший способ запомнить симптомы.
  • Сделай контейнер с PID 1 в фоне: docker run --name t1 nginx:alpine sh -c "nginx" (без daemon off). Увидишь мгновенный Exited. Потом запусти правильно через nginx -g "daemon off;" и сравни.
  • Поймай 137: docker run -m 64m --memory-swap 64m python:3-slim python -c "a=[0]*10**9" - контейнер упрётся в лимит. Проверь docker inspect ... OOMKilled и найди строку в dmesg.
  • Раздуй и почисти место: запусти пару docker build с разными слоями, посмотри docker system df, обрати внимание на Build Cache, потом docker builder prune и сравни цифры.
  • Сделай port is already allocated: подними два контейнера на один -p 8080:80 и найди оба через docker ps --filter "publish=8080".
Контрольные вопросы
  • Контейнер мгновенно выходит с кодом 0. В чём почти наверняка причина и как связана с PID 1?
  • Чем отличается диагностика и лечение toomanyrequests от таймаута docker pull на российском IP?
  • Exit code 137, но docker inspect показывает OOMKilled false. Что это значит и где искать причину?
  • Чем опасен docker system prune -a --volumes на проде и как чистить место безопаснее?
Итог

Траблшутинг Docker - это не магия, а дисциплина источников: docker logs для приложения, docker inspect для состояния и кода выхода, journalctl/dmesg для демона и ядра. Любая docker ошибка раскладывается на узкий набор классов - место, демон, сеть, права, память, запуск образа - и для каждого есть точная команда, которая отдаёт факт вместо догадки. Держи в голове дерево решений, двигайся от внешнего слоя к внутреннему, и ночные инциденты перестанут быть лотереей.
👍6 ❤️2 🔥2 😄 🤔2
Аватара пользователя
satsuse
Сообщения: 1
Зарегистрирован: 12 май 2026, 13:41

Re: Траблшутинг Docker: типичные ошибки и как чинить

Сообщение satsuse »

А я-то думал контейнер с nginx падает из-за бага, а оказалось забыл daemon off и оно само выходило с кодом 0. Спасибо, разъяснило
👍2 ❤️1 🔥 😄 🤔1
Аватара пользователя
PythonGeek
Сообщения: 1
Зарегистрирован: 21 май 2026, 13:44

Re: Траблшутинг Docker: типичные ошибки и как чинить

Сообщение PythonGeek »

Поймал 137 на CI, OOMKilled был false - по уроку понял что это раннер прибивал по таймауту, а не память. Полез смотреть graceful shutdown
👍2 ❤️2 🔥 😄 🤔
Ответить
← Предыдущая глава
Сборка образов в CI без демона: kaniko, BuildKit, Buildah
Следующая глава →
Производительность и эксплуатация Docker-хоста

Все главы курса «Docker: контейнеризация от основ до продакшена»

Поделиться темой: ✈ Telegram VK
Похожие запросы: docker команды шпаргалка для новичкаdocker run как запустить контейнер с флагамиdocker images как посмотреть и удалить образыdocker desktop и wsl2 на windows как настроитьdocker контейнер падает сразу после запуска как найти причинуdocker stats мониторинг контейнеров как смотреть

Вернуться в «Docker с нуля»

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и 0 гостей