Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «Як налагоджувати (debug) проблеми у контейнері?». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)**Debugging-чекліст для Docker:** спочатку логи, потім exit code, потім exec всередину, якщо running, override entrypoint, якщо одразу падає. Чотири команди, що розв'язують 90% кейсів: ```bash docker logs --tail 100 -f <name> # що сказав? docker inspect <name> # повний стан, exit code, OOM flag docker exec -it <name> sh # подивитися всередині (якщо running) docker run -it --entrypoint sh <image> # якщо container крашиться до того, як підключишся ``` **Головне:** трактуй exit code як первинний сигнал. 137 = SIGKILL/OOM, 143 = SIGTERM, 1 = app error, 139 = segfault. Тоді триангулюй логами, inspect і exec. Для image, що одразу крашиться, override entrypoint shell.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення**Debugging Docker container** це flowchart: починай з найзагальнішого сигналу (логи, exit code), звужуй до специфічного (exec всередину, entrypoint override), бери спеціалізовані tools, коли більш нічого не допомагає. Правильний порядок економить години. ## Теорія ### TL;DR Debugging-flowchart, у порядку: 1. **`docker ps -a`** — який стан container? 2. **`docker logs --tail 200 <name>`** — що друкував? 3. **`docker inspect <name>`** — exit code, OOM flag, error message, mount, мережі. 4. **`docker exec -it <name> sh`** — якщо running, подивися всередині live. 5. **`docker run -it --entrypoint sh <image>`** — якщо крашиться надто швидко для attach. 6. **Override зламаного:** `--entrypoint`, `--user root`, `--cap-add SYS_PTRACE` для strace. 7. **Спеціалізовані tools:** `dive` для шарів, `tcpdump` для мережі, `docker stats` для ресурсів. ### Крок 1: стан ```bash $ docker ps -a --filter name=api CONTAINER ID IMAGE STATUS NAMES a3f9d2b8c1e4 myapp Exited (1) 3 seconds ago api ``` Три факти в одному рядку: крашнувся, exit 1, 3 секунди тому. Більшість failure-шляхів виявляють себе на цьому етапі. ### Крок 2: логи ```bash # Останній вивід docker logs --tail 200 api # Live-tail (для поточних проблем) docker logs -f --tail 50 api # Time-обмежено docker logs --since 10m api # З timestamp docker logs -t --tail 50 api ``` **Важливо:** `docker logs` читає stdout/stderr PID 1. Якщо застосунок логує у файл всередині container, тут буде порожньо. Фікс: змусь застосунок логувати у stdout (12-factor норма). Для verbose-output capture: `docker logs api > app.log 2>&1`. ### Крок 3: inspect Повний JSON стану container. Через `--format` витягуєш конкретні поля: ```bash # Status-квартет, зазвичай відповідає на «що сталося?» docker inspect api --format \ '{{.State.Status}} (exit={{.State.ExitCode}}) OOM={{.State.OOMKilled}} Err={{.State.Error}}' # exited (137) OOM=true Err= ← OOM killer # exited (1) OOM=false Err= ← app error code 1 # exited (139) OOM=false Err= ← segfault ``` ```bash # Що змонтовано? docker inspect api --format '{{range .Mounts}}{{.Type}}: {{.Source}} -> {{.Destination}}\n{{end}}' # Яка мережа? docker inspect api --format '{{range $k, $v := .NetworkSettings.Networks}}{{$k}}: {{$v.IPAddress}}\n{{end}}' # Health-check лог (останні 5 спроб) docker inspect api --format '{{json .State.Health}}' ``` ### Крок 4: exec всередину (якщо running) ```bash # Drop у shell docker exec -it api sh # або bash, якщо доступний docker exec -it api bash # Перевірити, що файли існують docker exec api ls -la /app # Перевірити env docker exec api env | grep DATABASE # Перевірити connectivity до сусіда docker exec api wget -O- http://db:5432 # Що робить процес docker exec api ps aux ``` ### Крок 5: entrypoint-override (якщо крашиться надто швидко) Якщо container виходить до того, як ти можеш `exec` всередину: ```bash # Скіпни реальний застосунок, drop у shell docker run -it --rm --entrypoint sh myimage # Те саме але з оригінальними env-змінними + volume docker run -it --rm \ --entrypoint sh \ -e DATABASE_URL=... \ -v ./data:/data \ myimage # Або запусти оригінальну команду, але pause спочатку для attach docker run -it --rm --entrypoint /bin/sh myimage -c "sleep 3600 & node server.js" ``` `--entrypoint` міняє entrypoint image на те, що ти задаєш. У комбінації з `-it` отримуєш інтерактивний prompt замість умираючого застосунку. ### Крок 6: тактичні override ```bash # Запуск як root для діагностики (дефолтний user може не мати permission) docker exec -it -u root api sh # Mount strace з host або встанови у image docker run -it --cap-add=SYS_PTRACE myimage strace -p 1 # Вимкни healthcheck-driven restart-цикли docker run --health-cmd=NONE ... # Запуск з restart=no, щоб тримати failed-state видимим docker run --restart=no ... # Форсувати IPv4 для DNS-проблем docker run --dns=8.8.8.8 ... ``` ### Distroless-специфічний debug Distroless image не мають shell. Щоб дебажити: ```bash # Більшість проектів публікують :debug варіант з busybox docker run -it --entrypoint sh gcr.io/distroless/base:debug # Або скопіюй busybox у твій debug-container docker run -it --entrypoint /busybox/sh gcr.io/distroless/base:debug ``` Для прод distroless image: збери окремий `:debug` tag з тим самим контентом + busybox-шар, деплой debug лише коли треба. ### Типові помилки **Шукати log-файли у writable-шарі** ```bash $ docker exec web cat /var/log/myapp.log # (часто порожньо або застаріло; застосунок має логувати у stdout) ``` Застосунки, що логують у файли всередині container, не видні для `docker logs`. Або переконфігуруй застосунок під stdout, або змонтуй volume для логів і читай з host. **Запуск без `-t` і отримання порожнього виводу** ```bash $ docker exec api sh # (іноді висить або одразу виходить) ``` Потрібен `-t` для виділення TTY. Завжди `-it` для інтерактивних shell. **Забути, що `docker stop` міг вбити застосунок мід-cleanup** Якщо застосунок помирає під час `docker stop`, exit 143 (SIGTERM) або 137 (SIGKILL після grace), логи можуть бути truncated. Збільши `--time` на stop або перехоплюй SIGTERM у застосунку. **Плутати docker-proxy помилки з app-помилками** ``` Error starting userland proxy: listen tcp 0.0.0.0:8080: bind: address already in use ``` Це від Docker, не від твого застосунку, порт 8080 уже зайнятий на host. Перевір `lsof -i :8080` або обери інший host-порт. ### Спеціалізовані debugging-tools ```bash # Image layer-аналіз dive myimage # інтерактивний layer-by-layer view docker history --no-trunc myimage # хто додав що # Network debugging docker exec api tcpdump -i any -nn -c 20 docker exec api ss -tnlp # listening-порти docker exec api ip route # Process tracing docker run --cap-add=SYS_PTRACE myimage strace -p 1 # Resource debugging docker stats --no-stream docker inspect --format '{{.HostConfig.Memory}}' container # Live filesystem changes docker diff <container> # що змінилося у writable-шарі ``` ### Реальне застосування - **«Мій застосунок одразу виходить»:** перевір exit code → перевір логи → exec в через `--entrypoint sh`, щоб переконатися, що бінар існує і виконуваний. - **«Локально працює, у CI ні»:** порівняй env-змінні, mount-шляхи, мережу. `docker inspect` це rosetta-камінь. - **«Container unhealthy, але я не знаю чому»:** `docker inspect --format '{{json .State.Health}}'` показує останні 5 healthcheck-спроб з виводом. - **«Out-of-memory крашиться»:** `docker inspect` на `OOMKilled: true`. Розмір? Недавно бампнуто? Memory-leak? `docker stats` за час. - **«Не дістає інший container»:** exec в, ping по service-імені, перевір `/etc/resolv.conf`, переконайся, що обидва container на тій самій мережі. ### Питання для поглиблення **Q:** Як отримати логи container, що видалено? **A:** Не можна, `docker rm` видаляє log-файли container. Якщо передбачаєш потребу, налаштуй віддалений log-driver (`json-file`-логи переживають container, але `syslog`/`journald`/`fluentd` шиппять offsite). Або завжди `docker logs > backup.log` перед `rm`. **Q:** Що означає exit code 125? **A:** Помилка Docker daemon до старту container. Зазвичай проблема Docker-config (зламане image, поганий mount, конфлікт порту). Дивися daemon-лог (`journalctl -u docker`). **Q:** Що означає exit code 126 vs 127? **A:** 126 = команду знайдено, але не executable (permission або не та arch). 127 = команду не знайдено взагалі. Часто typo або відсутній бінар у image. **Q:** Як дебажити Compose-сервіс? **A:** Усі ті самі команди працюють через `docker compose`: `docker compose logs api`, `docker compose exec api sh`, `docker compose run --rm api sh` для one-off. Compose-обгортки знають твій project-контекст. **Q:** (Senior) Як дебажити container, що крашиться на kubelet, але працює локально? **A:** Відтвори точний run-config kubelet: той самий image-digest, ті самі env, той самий entrypoint, той самий UID, та сама network-policy. Pod-spec → `docker run` флаги це відоме перетворення. Часто різниця: K8s крутиться як non-root UID за замовчуванням, твій локал ні. Або K8s інжектить sidecar, що блокує трафік. Або K8s монтує secrets/configmaps, що твій локал не має. Бери `kubectl debug` для інтерактивного container у тому самому pod-контексті. ## Приклади ### Повна debugging-сесія ```bash $ docker ps -a --filter name=api STATUS NAMES Exited (137) 5 seconds ago api $ docker logs --tail 50 api ... багато виводу ... ERROR: connect ECONNREFUSED 172.18.0.5:5432 $ docker inspect api --format '{{.State.OOMKilled}} {{.State.ExitCode}} {{.HostConfig.Memory}}' false 137 536870912 # OOM=false, exit 137 → не пам'ять; SIGKILL прийшов звідкись $ docker logs --tail 50 db 2026-04-30 ... database system is shut down # db вийшов; api не зміг підключитися → SIGTERM каскадом. # Фікс: додай depends_on з healthcheck у compose, переконайся, що db переживає ``` Чотири команди тріангулювали проблему: api було вбито, бо db впав першим. ### Дебаг image, що одразу крашиться ```bash $ docker run myimg # (виходить за 0.1 секунди) $ docker run --rm myimg --version # (теж виходить одразу, без виводу) # Override entrypoint, щоб дослідити $ docker run -it --rm --entrypoint sh myimg / # which myapp /usr/local/bin/myapp / # ls -la /usr/local/bin/myapp -rwxr-xr-x 1 root root 12M ... / # /usr/local/bin/myapp Segmentation fault # → не та архітектура! Скоріш за все x86-бінар у ARM-image. $ docker inspect --format '{{.Architecture}}' myimg amd64 $ uname -m aarch64 # Підтверджено: image-arch != host-arch. ``` Без `--entrypoint sh` segfault бінаря був невидимим; `docker logs` нічого не мав, бо процес помер до stdio. ### Дебаг healthcheck ```bash $ docker ps --format '{{.Names}}: {{.Status}}' api: Up 5 minutes (unhealthy) $ docker inspect api --format '{{range .State.Health.Log}}{{.End}}: exit={{.ExitCode}} out={{.Output}}\n{{end}}' 2026-04-30T10:00:00Z: exit=0 out=ok 2026-04-30T10:01:00Z: exit=0 out=ok 2026-04-30T10:02:00Z: exit=7 out=connect: connection refused 2026-04-30T10:03:00Z: exit=7 out=connect: connection refused 2026-04-30T10:04:00Z: exit=7 out=connect: connection refused # → health-endpoint припинив відповідати 3 хвилини тому. Застосунок мабуть завис. $ docker exec api ps aux # Дивися на головний процес; чи він running, але не responsive? # Якщо так, застосунок застряг (deadlock, нескінченний цикл). Захопи stack trace. ``` Healthcheck-лог золото, п'ять спроб з exit-кодами і виводом.Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.