Короткий ответ
Если контейнер сервиса web проекта app сразу Exited, не поднимайте его снова через up -d в цикле. Запустите тот же образ без detach, чтобы stdout/stderr пришли в ваш терминал: docker compose run --rm --no-deps web или docker start -a. Пустой docker logs чаще значит, что процесс не писал в stdout (буфер Python/Java) или PID 1 — shell, который умер до exec. Не лечите restart: always и не трогайте volume app_data, пока не увидели текст ошибки.
Цикл Restarting — та же смерть плюс политика: перезапуск.
Симптомы и как отличить
docker compose up -dмгновенно «Created»,ps→Exited (1);docker logs app-web-1пустой или одна строкаstandard_init_linux.go;- entrypoint
bash -c './start.sh'и скрипт сset -eна первой команде; - путают с «портом занят»: тогда контейнер часто не создаётся, ошибка у compose, не Exited.
| Сигнал | Скорее | Статья |
|---|---|---|
| YAML/networks | стек не поднялся | Compose |
error mounting volume | bind/named | volume |
Permission denied на data | UID | права |
| Up + unhealthy | процесс жив | healthcheck |
Возможные причины
- CMD/ENTRYPOINT указали неверный путь (
python app.pyбез файла). - Конфиг обязателен, файла нет на volume или bind.
- Shell-форма vs exec-форма: сигнал и код выхода «не те», скрипт падает на пробеле.
- Интерактивный бинарь ждёт TTY (
-tнет в compose). stdin_open: trueбез процесса, читающего stdin — реже; чаще наоборот, java без-XX:+UseContainerSupportтут ни при чём.command:в YAML перетёр ENTRYPOINT образа так, что осталсяnginxбез-g 'daemon off;'— nginx демонтизировался, PID 1 умер, контейнер Exited 0. Это выглядит как «сразу завершился» при успехе.- Редко:
read_only: trueбез tmpfs на/tmp.
Диагностика
Хост host.example, каталог /opt/app.
1. Состояние без догадок
cd /opt/app
docker compose ps -a
docker inspect app-web-1 --format 'status={{.State.Status}} exit={{.State.ExitCode}} err={{.State.Error}} pid={{.State.Pid}} oom={{.State.OOMKilled}}'
docker inspect app-web-1 --format '{{json .Config.Entrypoint}} {{json .Config.Cmd}}'Pid 0 и exit != 0 — процесс уже мёртв. Error с OCI runtime — не приложение.
2. Запуск без detach
Именно это и есть рабочий приём, не «ещё один up -d».
docker compose run --rm --no-deps --entrypoint '' web sh -c 'id; ls -l /app; echo ENTRY; command -v python3'
docker compose run --rm --no-deps webВторая команда повторяет штатный ENTRYPOINT. Вывод сразу в SSH-сессию. --no-deps отсекает шум от db, если падает сам web. Если нужен DNS-имя db — уберите --no-deps и поднимите зависимость отдельно.
Эквивалент на уже созданном контейнере:
docker start -a app-web-1Не добавляйте -d.
3. Почему logs пустые
docker inspect app-web-1 --format '{{.HostConfig.LogConfig.Type}}'
docker compose run --rm --no-deps -e PYTHONUNBUFFERED=1 -e NODE_DEBUG=cluster webДля Java часто мешает буфер; для nginx в daemon mode логов в контейнере не будет, потому что процесса уже нет.
4. Mount и рабочий каталог
docker inspect app-web-1 --format '{{json .Mounts}}'
docker inspect app-web-1 --format '{{.Config.WorkingDir}}'Bind на несуществующий файл хоста Docker иногда создаёт директорию вместо файла — приложение читает «каталог» и падает. Это классика ./nginx.conf.
Решение
Сценарий A. Видно исключение в foreground
Исправляете конфиг/секрет/путь. Пересборка образа, если правили код:
cd /opt/app
docker compose build web
docker compose up -d --no-deps web
docker compose logs --tail 50 webNamed volume app_data не трогайте.
Сценарий B. nginx/apache сразу Exited 0
В образе должен остаться foreground. В compose:
command: ["nginx", "-g", "daemon off;"]Не service nginx start внутри скрипта без exec.
Сценарий C. executable file not found / 127
docker compose run --rm --no-deps --entrypoint sh web -c 'ls -l /usr/bin /app'Windows CRLF в start.sh даёт bad interpreter. sed -i 's/\r$//'. Не «переустановите docker».
Сценарий D. Падает только в compose, run вручную жив
Сравните docker compose config с тем, что вы передали в docker run: environment, user:, read_only, command. Часто виноват переопределённый command: в YAML.
Сценарий E. Нужна зависимость
Поднимите db, дождитесь healthy, затем foreground web:
docker compose up -d db
docker compose ps db
docker compose run --rm webНе вставляйте sleep 30 без верхней границы и без проверки порта.
Как проверить, что проблема устранена
docker compose ps
docker inspect app-web-1 --format '{{.State.Running}} {{.State.ExitCode}} {{.State.Pid}}'
docker logs --tail 20 app-web-1Running true, PID ≠ 0, в логах — штатный баннер приложения, не stacktrace. Повторный compose up -d не должен сразу дать Exited.
Если не помогло
State.Errorпро seccomp: точечныйcap_add/security_opt, не privileged.- TTY:
tty: trueтолько если бинарь реально требует; иначе оставьте выкл. - Multi-stage образ: в runtime-stage забыли скопировать бинарь — foreground сразу 127.
- PID 1 —
dumb-init+ дочерний упал: смотрите код именно дочернего в логе. - Контейнер Exited 0 через 1 с — ищите daemonize, не «успех бизнеса».
Профилактика
- В CI:
docker compose run --rm --no-deps web /app/health-or-versionбез-d. execв скриптах entrypoint, чтобы PID 1 был приложением.- Не глотать ошибки
set +e. - Bind-файлы проверять
[ -f ]на хосте до up. - Для Python/Node — unbuffered в образе, не только в wiki.
FAQ
Чем run отличается от up?
up создаёт долгоживущий сервис по политике restart и сети проекта. run — разовый контейнер, по умолчанию без того же имени. Для отладки падения стартового — run или start -a, не десятый up -d.
Почему -d в cron «удобнее»?
Cron не видит stderr. Вы получите Exited и пустое письмо. Либо start -a с таймаутом, либо сразу logs.
Нужен ли --rm?
Для отладки да, чтобы не копить анонимные контейнеры. На named сервисе web используйте start -a, --rm удалит одноразовый run.
Можно ли docker logs --details вместо foreground?
Дополняет, не заменяет. Если процесс не стартовал, details не покажут traceback приложения.
Exited 0 — это ошибка?
Для long-running — да. Для migrate — нет. Смотрите роль сервиса, не только код.