Troubleshooting — Docker для Junior Data Engineer
База знаний типичных ошибок курса Docker для Junior Data Engineer.
Симптомы
- При вызове любой 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.
Решение
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 может быть выключена.
Решение
- 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-контейнеров.
Решение
- Запустить 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. Какой запустился последним — того и сокет.
Решение
- Выбрать один:
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]`) и кто-то занял порт.
Решение
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 и быстро упираются в потолок.
Решение
- Авторизоваться:
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; если такого варианта нет — падает.
Решение
- Запустить под эмуляцией:
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.
Решение
- Проверить занятость:
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).
Решение
- Проверить теги:
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.
Решение
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 остаются в слое.
Решение
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` — внутри образа эта ссылка указывает в никуда.
Решение
- Резолвить symlinks ДО сборки:
cp -rL ./data ./data-resolved && docker build .— флаг-Lследует за ссылками. Или явно скопировать файл-цель:cp $(readlink -f symlink) ./data/file.csv. На macOSreadlink -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 не используется.
Решение
- Создать
.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 при многоступенчатой параллельной сборке держит несколько контейнеров одновременно.
Решение
- 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.
Решение
- Проверить имя:
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-лимиты.
Решение
- Поднять лимит:
docker run --memory=4g ...или в composedeploy.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 без эмуляции.
Решение
- Проверить архитектуру:
docker run --rm <image> uname -mvs ожидаемая. Если 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 перезапускает контейнер раз за разом.
Решение
- Временно убрать restart-policy:
docker compose stop <svc>+ правка composerestart: "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` слишком короткий).
Решение
- Посмотреть, что вернул 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 с маленькими сообщениями буферизует и теряет их при быстром падении.
Решение
- Добавить
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.
Решение
- ВНИМАНИЕ: уничтожит данные.
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-маппингом.
Решение
- Согласовать 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.
Решение
- Использовать
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`) это убивает производительность.
Решение
- Использовать
:cachedили:delegatedconsistency-флаги (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, поэтому никто не отвечает.
Решение
- На 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 по имени сервиса.
Решение
- Всегда использовать имя сервиса как 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`.
Решение
- В 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.
Решение
- Запускать с
-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, открывает порт).
Решение
- Добавить 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/`, а не в корне.
Решение
- Проверить, что видит 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'ы.
Решение
- Проверить итоговую конфигурацию:
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:` не запускаются по умолчанию — только при явной активации профиля.
Решение
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-зависимостями.
Решение
- Перейти на
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 всё качается заново.
Решение
- 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.
Решение
- Проверить, чем запускается:
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 ..