Перейти к содержанию
Learning Platform
Глоссарий
Troubleshooting

Troubleshooting — Docker для Junior Data Engineer

База знаний типичных ошибок курса Docker для Junior Data Engineer.

Показано 35 из 35 ошибок

Симптомы

  • При вызове любой docker-команды без sudo: `Got permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock: Get "http://%2Fvar%2Frun%2Fdocker.sock/v1.43/containers/json": dial unix /var/run/docker.sock: connect: permission denied`

Причина

Группа `docker` не назначена текущему пользователю. Сокет принадлежит `docker:docker` с правами 660 — читать/писать может только root и члены группы docker.

Решение

  1. sudo usermod -aG docker $USER && newgrp docker (или перелогиниться — newgrp стартует subshell). Проверить: id | grep docker. ВНИМАНИЕ: членство в docker-группе фактически даёт root на хосте (через bind mount /). Безопасная альтернатива — rootless установка.

Симптомы

  • `Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running?` — независимо от sudo.

Причина

Сервис `dockerd` остановлен. На Linux — это systemd-unit `docker.service`. На macOS/Windows daemon крутится внутри VM Docker Desktop / OrbStack / Rancher Desktop, и эта VM может быть выключена.

Решение

  1. Linux: sudo systemctl status docker и при необходимости sudo systemctl start docker && sudo systemctl enable docker. macOS: открыть Docker Desktop / OrbStack из Applications. Логи daemon: sudo journalctl -u docker.service -n 100 --no-pager.

Симптомы

  • Docker Desktop на Windows стартует с ошибкой `WSL 2 installation is incomplete` или `The WSL kernel version is too low`. Контейнеры не стартуют.

Причина

Не включена компонента Windows Subsystem for Linux 2 или не обновлено WSL-ядро. Docker Desktop на Windows 10/11 использует WSL2-backend для запуска Linux-контейнеров.

Решение

  1. Запустить PowerShell от админа: wsl --install (для свежей установки) или wsl --update (обновить ядро). Затем wsl --set-default-version 2. После перезагрузки — заново запустить Docker Desktop. Проверка: wsl -l -v должна показать минимум один дистрибутив с VERSION 2.

Симптомы

  • На macOS установлены оба — OrbStack и Docker Desktop. `docker ps` показывает контейнеры одного, а GUI второго ничего не видит. `docker context ls` показывает два контекста.

Причина

Оба продукта переопределяют unix-сокет через `~/.docker/run/docker.sock` symlink и переключают активный context. Какой запустился последним — того и сокет.

Решение

  1. Выбрать один: docker context use orbstack или docker context use desktop-linux. Если нужны оба — переключаться явно через DOCKER_CONTEXT=orbstack docker ps. Удалить один из продуктов: brew uninstall --cask docker (Docker Desktop) или OrbStack удалить через Settings -- Uninstall.

Симптомы

  • `failed to start daemon: listen tcp 0.0.0.0:2375: bind: address already in use` при запуске dockerd. Старый daemon-процесс остался висеть либо у вас включён Docker через TCP и порт занят другим сервисом.

Причина

Чаще всего — два процесса dockerd. Бывает после некорректного `kill -9 dockerd` без снятия pidfile. Или вы экспортировали Docker по TCP в `/etc/docker/daemon.json` (`hosts: [tcp://0.0.0.0:2375]`) и кто-то занял порт.

Решение

  1. sudo ss -tlnp | grep -E ':(2375|2376)' — найти захватчика. sudo systemctl stop docker && sudo rm /var/run/docker.pid && sudo systemctl start docker. Если порт нужен другому сервису — убрать TCP-listener из /etc/docker/daemon.json и оставить только unix-сокет.

Симптомы

  • `toomanyrequests: You have reached your pull rate limit. You may increase the limit by authenticating and upgrading: https://www.docker.com/increase-rate-limit`

Причина

Анонимный pull с Docker Hub лимитирован 100 запросами в 6 часов на IP. В CI-runner'ах множество jobs тянут с одного egress-IP и быстро упираются в потолок.

Решение

  1. Авторизоваться: docker login — лимит вырастет до 200/6h на бесплатном аккаунте. Долгосрочно: завести pull-through cache (Harbor, AWS ECR Pull-Through, GHCR) или перенести base images в свой реестр. В CI добавить retry на toomanyrequests с экспоненциальной паузой.

Симптомы

  • `docker pull someimage:tag` падает с `no matching manifest for linux/arm64/v8 in the manifest list entries` (на Apple Silicon) или для linux/amd64 (на ARM-сервере).

Причина

Образ собран только под одну архитектуру. На Apple Silicon Docker по умолчанию пытается тянуть linux/arm64; если такого варианта нет — падает.

Решение

  1. Запустить под эмуляцией: docker run --platform=linux/amd64 someimage:tag. На M1/M2/M3 будет работать через QEMU (медленнее в 2-5 раз). Долгосрочно: попросить автора образа собрать multi-arch через docker buildx build --platform linux/amd64,linux/arm64 --push, либо форкнуть Dockerfile и собрать самому.

Симптомы

  • `failed to register layer: write /usr/lib/...: no space left on device`. При этом `df -h /` показывает свободное место.

Причина

Закончилось место конкретно в каталоге Docker (`/var/lib/docker` на Linux, отдельный диск VM на macOS/Windows). Накопились dangling images, остановленные контейнеры, неиспользуемые volumes, build cache.

Решение

  1. Проверить занятость: docker system df и docker system df -v. Очистить аккуратно: docker container prune (остановленные), docker image prune -a (висячие + неиспользуемые), docker volume prune (ВНИМАНИЕ: уничтожит данные!), docker builder prune. Одной командой всё: docker system prune -a --volumes — но это сносит ВСЁ.

Симптомы

  • `docker pull myorg/app:v1.2.3` падает с `manifest for myorg/app:v1.2.3 not found: manifest unknown`.

Причина

Тег не существует в registry: опечатка, ещё не опубликован, либо удалён. Иногда — это приватный реестр, а вы не залогинены (registry отвечает 401, который CLI показывает как manifest unknown).

Решение

  1. Проверить теги: curl https://registry/v2/myorg/app/tags/list (или Docker Hub UI). Перелогиниться: docker logout && docker login <registry>. Для AWS ECR — обновить токен: aws ecr get-login-password --region us-east-1 | docker login --username AWS --password-stdin <account>.dkr.ecr.us-east-1.amazonaws.com.

Симптомы

  • После многих часов работы `docker push <account>.dkr.ecr.../repo:tag` падает с `denied: Your authorization token has expired. Reauthenticate and try again.`

Причина

AWS ECR-токен живёт 12 часов. После этого нужно получить новый через AWS API.

Решение

  1. aws ecr get-login-password --region <region> | docker login --username AWS --password-stdin <account>.dkr.ecr.<region>.amazonaws.com. В CI делать это в начале каждого job. Для долгоживущих deploy-скриптов — обернуть push в retry с переавторизацией при 401.

Симптомы

  • Базовый `python:3.13-slim` + `RUN apt-get install -y libpq-dev` даёт образ на 800 МБ вместо ожидаемых 200.

Причина

Без `--no-install-recommends` apt тянет рекомендуемые пакеты (man-pages, документация, ненужные tooling). Плюс если не удалить `/var/lib/apt/lists/*` — индексы apt остаются в слое.

Решение

  1. RUN apt-get update && apt-get install -y --no-install-recommends libpq-dev && rm -rf /var/lib/apt/lists/* — всё в одном RUN, иначе очистка не работает (кэшированный слой выше остаётся). Hadolint правило DL3015. Hadolint в CI ловит это до merge.

Симптомы

  • `COPY ./data /app/data` копирует не файлы, а битые symlinks. Внутри контейнера `ls -la /app/data` показывает ссылки на пути хоста, которых внутри не существует.

Причина

По умолчанию `COPY` сохраняет symlinks как есть (не следует за ними). Если в build context'е лежит symlink на `/Users/me/dataset/big.csv` — внутри образа эта ссылка указывает в никуда.

Решение

  1. Резолвить symlinks ДО сборки: cp -rL ./data ./data-resolved && docker build . — флаг -L следует за ссылками. Или явно скопировать файл-цель: cp $(readlink -f symlink) ./data/file.csv. На macOS readlink -f нет — поставить coreutils: brew install coreutils && greadlink -f ....

Симптомы

  • `docker build .` зависает на минуту перед стартом со строкой `Sending build context to Docker daemon 2.1GB`.

Причина

Нет `.dockerignore` или он неполный. CLI пакует весь каталог сборки и отправляет daemon'у: `node_modules/`, `.git/`, `__pycache__/`, `*.parquet` дампы данных, виртуальные окружения `.venv/` — всё летит в daemon, даже если в Dockerfile не используется.

Решение

  1. Создать .dockerignore в корне build context: .git, node_modules, __pycache__, *.pyc, .venv, venv, *.log, .env*, dist, build, .pytest_cache, .mypy_cache. Проверить, что подействовало: du -sh $(cat .dockerignore | grep -v '^#' | head -5) сверить — после .dockerignore контекст должен быть в МБ, не ГБ.

Симптомы

  • `docker build` падает с `runtime: out of memory: cannot allocate ... bytes` или процесс buildkit убивается OOM-killer'ом. Часто на тяжёлых Python-зависимостях (pandas, numpy, torch) или Node-сборке.

Причина

На macOS/Windows Docker крутится в VM с фиксированным лимитом памяти (по умолчанию 2 ГБ). На Linux — это лимит хоста или cgroup. BuildKit при многоступенчатой параллельной сборке держит несколько контейнеров одновременно.

Решение

  1. macOS: Docker Desktop -- Settings -- Resources -- Memory увеличить до 6-8 ГБ. OrbStack: Settings -- Resources -- Memory. Linux: проверить cgroup-лимиты daemon. Альтернатива — ограничить параллелизм: docker build --build-arg BUILDKIT_INLINE_CACHE=1 и не использовать --parallel для multi-stage.

Симптомы

  • В multi-stage Dockerfile: `COPY --from=builder /app/dist /app` падает с `failed to compute cache key: "/app/dist" not found`.

Причина

Путь `/app/dist` не существует в стейдже `builder`. Возможные причины: имя стейджа опечатано (`FROM ... AS biulder`), сборка `dist/` идёт через relative path и фактически лежит не там, npm/python build упал тихо без exit code 1.

Решение

  1. Проверить имя: FROM node:22 AS builder точно совпадает с --from=builder. Залезть в стейдж: docker build --target builder -t debug ., потом docker run --rm -it debug sh и ls /app. Добавить RUN ls -la /app/dist ДО COPY --from, чтобы build упал именно там, где файла нет.

Симптомы

  • Контейнер упал, `docker ps -a` показывает `Exited (137)`. `docker inspect <id> --format '{{.State.OOMKilled}}'` возвращает `true`.

Причина

Процесс PID 1 контейнера превысил лимит памяти (`--memory=2g` или дефолтный лимит хоста) — kernel OOM killer прислал SIGKILL (128 + 9 = 137). Частая причина в DE: pandas пытается прочитать parquet больше доступной RAM, либо Java/JVM не учитывает cgroup-лимиты.

Решение

  1. Поднять лимит: docker run --memory=4g ... или в compose deploy.resources.limits.memory: 4g. Для JVM — добавить -XX:+UseContainerSupport -XX:MaxRAMPercentage=75 (с JDK 17+ — по умолчанию). Для pandas — читать chunked: pd.read_csv(..., chunksize=100000). Мониторить: docker stats <container>.

Симптомы

  • Контейнер падает с `Exited (139)`. Логи обрываются на середине.

Причина

Процесс получил SIGSEGV (128 + 11 = 139) — segmentation fault. Корневая причина — баг в нативном коде: C-расширение Python (numpy/pyarrow) собрано под другую glibc/musl, несовместимая версия libssl, попытка запустить amd64-бинарь на arm64 без эмуляции.

Решение

  1. Проверить архитектуру: docker run --rm <image> uname -m vs ожидаемая. Если alpine + Python — переключиться на python:3.13-slim (glibc-совместимый). Включить core dumps: docker run --ulimit core=-1 ... и проанализировать через gdb. Часто помогает обновление base image на более свежий patch-релиз вида python:3.13.5-slim вместо python:3.13-slim.

Симптомы

  • `docker ps` показывает `Restarting (1) 2 seconds ago` и колонка статус мигает. `docker logs` показывает один и тот же стектрейс снова и снова.

Причина

Установлен `restart: always` или `restart: unless-stopped`, процесс падает на старте (нет ENV, нет миграций, нет доступа к БД), daemon перезапускает контейнер раз за разом.

Решение

  1. Временно убрать restart-policy: docker compose stop <svc> + правка compose restart: "no" + docker compose up <svc> — даст контейнеру упасть и оставить логи. Прочитать docker logs --since=5m <id> и найти причину. После починки вернуть restart: unless-stopped.

Симптомы

  • В `docker ps` колонка STATUS — `Up 5 minutes (unhealthy)`. Контейнер работает, но HEALTHCHECK падает.

Причина

HEALTHCHECK-команда внутри контейнера возвращает ненулевой exit. Частые причины: `curl` отсутствует в slim/distroless образе; сервис слушает не на localhost, а на конкретном интерфейсе; путь `/health` ещё не готов в первые секунды (`start_period` слишком короткий).

Решение

  1. Посмотреть, что вернул healthcheck: docker inspect --format='{{json .State.Health}}' <id> | jq. Если curl: not found — заменить на wget --spider или python -c 'import urllib.request; urllib.request.urlopen("http://localhost:8080/health")'. Поднять start_period: 30s для медленно стартующих сервисов (Airflow webserver).

Симптомы

  • `docker ps -a` показывает `Exited (1) 2 seconds ago`, `docker logs <id>` ничего не выводит.

Причина

Приложение пишет логи в файл внутри контейнера (например, `/var/log/myapp.log`) или буферизует stdout. Без `PYTHONUNBUFFERED=1` Python с маленькими сообщениями буферизует и теряет их при быстром падении.

Решение

  1. Добавить ENV PYTHONUNBUFFERED=1 в Dockerfile или -e PYTHONUNBUFFERED=1 при запуске. Для Java — -Dlog4j.configurationFile=... редиректить в stdout. Для bash-entrypoint — exec command "$@" вместо command "$@" (без exec PID 1 это shell, который глотает сигналы и буферы). Проверить запуск интерактивно: docker run --rm -it <image>.

Симптомы

  • Поменяли `POSTGRES_DB=newdb` или `POSTGRES_PASSWORD`, перезапустили compose — старые креды и старая БД. Init-скрипт из `docker-entrypoint-initdb.d/` не выполняется.

Причина

Named volume `pgdata` уже инициализирован: контейнер видит непустой `PGDATA` и пропускает initdb. Init-скрипты выполняются ровно ОДИН раз — при первом запуске на пустом volume.

Решение

  1. ВНИМАНИЕ: уничтожит данные. docker compose down -v (флаг -v сносит volumes) и затем docker compose up. Безопасный путь — изменить креды через SQL: docker compose exec postgres psql -U postgres -c "ALTER USER postgres WITH PASSWORD 'new';". Для миграций использовать отдельный init-контейнер с condition: service_completed_successfully.

Симптомы

  • Внутри контейнера приложение не может писать в `/app/data` несмотря на `volumes: [./data:/app/data]`. На Linux. На macOS работает.

Причина

На Linux UID процесса внутри контейнера сохраняется на хосте. Если в контейнере `USER node` (UID 1000), а директория `./data` на хосте принадлежит UID 1001 — нет прав записи. macOS прячет это через osxfs/virtiofs c userland-маппингом.

Решение

  1. Согласовать UID: RUN useradd -u $(id -u) -m app с передачей --build-arg HOST_UID=$(id -u). Альтернатива — chown директории на хосте под нужный UID: sudo chown -R 1000:1000 ./data. Или запустить контейнер от того же UID: docker run --user $(id -u):$(id -g) ....

Симптомы

  • После `docker run --rm -v pgdata:/var/lib/postgresql/data postgres` и завершения работы данные исчезают. Volume удаляется.

Причина

Флаг `-v` при `docker run --rm` или `docker rm` явно сносит анонимные volumes. Если вы не уверены, что volume назван — он мог быть создан как anonymous.

Решение

  1. Использовать docker run БЕЗ --rm для stateful-сервисов или явно создать named volume: docker volume create pgdata && docker run -v pgdata:/var/lib/postgresql/data .... Проверить: docker volume ls и docker volume inspect pgdata. Named volume не удаляется при docker rm <container> без -v.

Симптомы

  • На macOS `docker compose up` с bind mount исходников: запуск Django dev-server занимает 30 секунд вместо 2, hot-reload зависает.

Причина

Bind mount на macOS идёт через VM (HyperKit, virtiofs, gRPC-FUSE — зависит от backend). Каждый файловый syscall — round-trip через VM. На больших деревьях (`node_modules`, `.venv`) это убивает производительность.

Решение

  1. Использовать :cached или :delegated consistency-флаги (legacy): volumes: [./src:/app/src:cached]. Современный backend Docker Desktop / OrbStack использует virtiofs — быстрее, но всё равно медленнее native. Альтернативы: OrbStack (быстрый virtiofs), Mutagen (двунаправленная синхронизация), не монтировать node_modules (использовать named volume для них).

Симптомы

  • Внутри контейнера приложение конфигурировано на `postgres://localhost:5432/db` и падает с `connection refused`. На хосте Postgres работает на 5432.

Причина

В сетевом namespace контейнера `localhost` (127.0.0.1) — это loopback самого контейнера, а не хоста. На контейнере нет Postgres, поэтому никто не отвечает.

Решение

  1. На Linux использовать --add-host=host.docker.internal:host-gateway (Docker 20.10+) и адрес host.docker.internal. На macOS/Windows этот хост работает из коробки. Лучше — поднять Postgres тоже в compose и использовать DNS-имя сервиса: postgres://db:5432/db.

Симптомы

  • В compose файле два сервиса (app + db). В app `DATABASE_URL=postgres://127.0.0.1:5432/db` — `connection refused`. Меняем на `db:5432` — работает.

Причина

Каждый контейнер имеет свой network namespace. 127.0.0.1 в app — это loopback app-контейнера, там нет Postgres. db и app общаются через user-defined bridge, который Compose создаёт автоматически и резолвит DNS по имени сервиса.

Решение

  1. Всегда использовать имя сервиса как hostname: DATABASE_URL=postgres://db:5432/mydb. Это работает потому, что Compose создаёт DNS-запись для каждого service в общей сети. Проверить связь: docker compose exec app getent hosts db — должен вернуть IP.

Симптомы

  • На macOS код успешно резолвит `host.docker.internal` в IP хоста. На Linux-серверах (CI, production) — `getaddrinfo: Name or service not known`.

Причина

До Docker 20.10 на Linux этого DNS не было. С 20.10+ — есть, но нужно явно прокинуть: `--add-host=host.docker.internal:host-gateway`. В compose это `extra_hosts`.

Решение

  1. В compose: services: app: extra_hosts: ["host.docker.internal:host-gateway"]. Для docker run: --add-host=host.docker.internal:host-gateway. Альтернатива — использовать IP моста: ip addr show docker0 | grep inet (обычно 172.17.0.1). В production-окружении лучше явно настраивать сервис-discovery, а не полагаться на host.

Симптомы

  • В Dockerfile стоит `EXPOSE 5432`, контейнер запущен через `docker run postgres`, но `psql -h localhost -p 5432` с хоста не подключается.

Причина

EXPOSE — это только метаданные, документация о том, какой порт слушает контейнер. Реальная публикация порта на хост происходит ТОЛЬКО через флаг `-p` или `ports:` в compose.

Решение

  1. Запускать с -p 5432:5432 (или -P для автоматической публикации всех EXPOSE-портов на случайные хостовые). В compose — ports: ["5432:5432"]. Проверка: docker port <container> показывает фактически опубликованные.

Симптомы

  • В compose `depends_on: [postgres]`. При `docker compose up` app падает в логе с `psycopg2.OperationalError: could not connect to server: Connection refused`.

Причина

Базовый `depends_on` ждёт только СТАРТА контейнера postgres (PID 1 запущен), но не готовности Postgres принимать соединения (Postgres сам стартует ~3-5 секунд: запускает walwriter, открывает порт).

Решение

  1. Добавить HEALTHCHECK в postgres-сервис: healthcheck: { test: ["CMD-SHELL", "pg_isready -U postgres"], interval: 2s, timeout: 3s, retries: 10 }. В app поменять depends_on: [postgres] на depends_on: { postgres: { condition: service_healthy } }. Применимо к Compose v2.1+.

Симптомы

  • В `.env` файле `POSTGRES_PASSWORD=secret`, в compose `environment: [POSTGRES_PASSWORD=${POSTGRES_PASSWORD}]`. После `docker compose up` Postgres стартует, но логин падает.

Причина

Compose читает `.env` ТОЛЬКО из каталога, где лежит compose-файл (или явно указанного через `--env-file`). Если запустили `docker compose -f deploy/compose.yml up` из корня проекта — Compose ищет `.env` в `deploy/`, а не в корне.

Решение

  1. Проверить, что видит compose: docker compose config — раскроет все переменные. Указать env-file явно: docker compose --env-file ./.env -f deploy/compose.yml up. Не путать с env_file: в services — это для загрузки env в контейнер; .env (без двоеточия) — для подстановки в сам compose.yml.

Симптомы

  • Локально (без указания файлов) compose поднимает один набор сервисов с volume-mount'ами кода. В CI (`-f docker-compose.yml`) этих mount'ов нет, тесты падают.

Причина

По умолчанию Compose автоматически подцепляет `docker-compose.override.yml` если он есть. При явном `-f docker-compose.yml` override НЕ применяется. В override лежали development-only volume mount'ы.

Решение

  1. Проверить итоговую конфигурацию: docker compose config (или -f base.yml -f override.yml config). Для prod-сборки явно: docker compose -f docker-compose.yml -f docker-compose.prod.yml up. Не класть критичные настройки в override — только локальные overrides.

Симптомы

  • В compose сервис `kafka-ui` с `profiles: [debug]`. После `docker compose up` всё кроме kafka-ui стартует.

Причина

Сервисы с `profiles:` не запускаются по умолчанию — только при явной активации профиля.

Решение

  1. docker compose --profile debug up или указать сервис явно: docker compose up kafka-ui. Можно активировать сразу несколько: --profile debug --profile monitoring. Постоянно: COMPOSE_PROFILES=debug,monitoring в env.

Симптомы

  • `FROM python:3.13-alpine` плюс `RUN pip install pandas numpy` собирается 15-20 минут на каждом build, итоговый образ 800 МБ.

Причина

PyPI отдаёт pre-built wheels для `manylinux` (glibc) и macOS, а для musl (alpine) — обычно нет. Pip падает на binary install и собирает из исходников: тянет gcc, g++, gfortran, ставит python-dev, потом компилирует C/C++/Fortran код numpy/pandas. Это медленно и распухает образ временными dev-зависимостями.

Решение

  1. Перейти на python:3.13-slim (debian-based, glibc) — wheels работают, pip install pandas — 5 секунд. Если alpine критичен — использовать alpine-edge который имеет py3-pandas через apk add py3-pandas (но версии могут отставать).

Симптомы

  • Каждый build перекачивает все wheel'ы с PyPI. Хочется кэшировать, но `pip install --no-cache-dir` явно отключает.

Причина

Дилемма: `--no-cache-dir` уменьшает размер слоя (pip wheel cache не остаётся в образе ~50-200 МБ), но при повторном build всё качается заново.

Решение

  1. BuildKit cache mount — оба плюса сразу: RUN --mount=type=cache,target=/root/.cache/pip pip install -r requirements.txt. Кэш живёт между build'ами на хосте, но НЕ попадает в финальный слой. Включается флагом # syntax=docker/dockerfile:1.7 в первой строке Dockerfile.

Симптомы

  • В requirements.txt есть `boto3==1.34.0`, `pip install` отрабатывает успешно. В runtime: `ModuleNotFoundError: No module named 'boto3'`.

Причина

Один из вариантов: (a) `pip install` поставил в один python (системный), а `CMD python app.py` запускает другой (venv); (b) multi-stage Dockerfile установил пакеты в стейдже `builder`, но не скопировал `site-packages` в финальный стейдж; (c) поломанный кэш слоёв скрыл актуальный requirements.txt.

Решение

  1. Проверить, чем запускается: docker run --rm --entrypoint sh image -c 'which python && python -m pip list | grep boto3'. Multi-stage — COPY --from=builder /usr/local/lib/python3.13/site-packages /usr/local/lib/python3.13/site-packages. Принудительный rebuild без кэша: docker build --no-cache ..