Главный принцип траблшутинга 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%)
Чистка - от мягкого к жёсткому. Безопасный вариант, который трогает только мусор:
Код: Выделить всё
docker system prune
Код: Выделить всё
docker system prune -a --volumes
Код: Выделить всё
docker builder prune --filter "until=72h"
Код: Выделить всё
{
"log-driver": "json-file",
"log-opts": { "max-size": "10m", "max-file": "3" }
}

Docker не запускается: разбор отказа демона
Сценарий "docker не запускается" пугает новичков, потому что ломается всё разом: любая команда отвечает Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running? Тут важно разделить две вещи: демон не стартует вообще или ты просто не в группе docker.
Сначала проверь, жив ли сервис:
Код: Выделить всё
systemctl status docker
Код: Выделить всё
journalctl -u docker --no-pager -n 50
Образ не тянется: 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
История вторая, для нас актуальнее - доступность 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"]
}
Контейнер падает сразу: 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, оркестратор).
Порт занят и сеть не работает
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
OOM и код 137: когда памяти не хватило
Контейнер внезапно умер, в docker ps статус Exited (137). Код 137 - это 128 плюс 9, то есть процесс получил SIGKILL. В подавляющем большинстве случаев это OOM Killer ядра: контейнер уперся в лимит памяти, и cgroups его прибили. Проверяешь без догадок:
Код: Выделить всё
docker inspect <container> --format '{{.State.OOMKilled}}'
Дерево решений
Сведём всё в маршрут, по которому идёшь сверху вниз. Команда вообще не выполняется и ругается на сокет - проблема не в контейнере: либо демон не стартует (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 ошибка раскладывается на узкий набор классов - место, демон, сеть, права, память, запуск образа - и для каждого есть точная команда, которая отдаёт факт вместо догадки. Держи в голове дерево решений, двигайся от внешнего слоя к внутреннему, и ночные инциденты перестанут быть лотереей.