`post_start` / `pre_stop` hook stdout/stderr is discarded in `up -d` and `up --wait` — only attached mode shows it, and nothing persists it
Description
Service lifecycle hooks (post_start, pre_stop) run their command, but their stdout/stderr is only forwarded to the terminal when docker compose up runs attached (foreground). In every non-interactive form — up -d, up --wait — the hook output is dropped:
- success: completely silent, no indication the hook ran.
- failure: a single summary line on stderr (
<service> hook exited with status <N>) plus a non-zero exit code, but the hook's own output (the actual error from the migration tool / script) is never shown.
The output is also not recoverable after the fact from any other source:
| Source | Has hook output? |
|---|---|
docker compose up (attached) |
✅ streamed, prefixed svc-1 -> | |
docker compose up -d |
❌ (only <svc> hook exited with status N on failure) |
docker compose up --wait |
❌ same as -d |
docker compose logs / docker logs <ctr> |
❌ |
docker inspect <ctr> |
❌ |
docker events payload |
❌ |
dockerd logs (journalctl -u docker) |
❌ |
This makes lifecycle hooks effectively undebuggable for any tool or CI pipeline that shells out docker compose up -d (the standard non-interactive invocation).
When a post_start migration/seed/warm-up step fails, the operator sees hook exited with status 1 and has no way to find out why.
Related but distinct issues (hooks not running in certain paths): #13593 (compose run), #13318 (compose restart), #12513 (service.Up API). This report is about hooks that do run, but whose output goes nowhere.
Steps To Reproduce
compose.yaml:
services:
ok:
image: alpine:3.20
command: sh -c "sleep 600"
post_start:
- command: sh -c "echo HOOK_STDOUT; echo HOOK_STDERR 1>&2; exit 0"
bad:
image: alpine:3.20
command: sh -c "sleep 600"
post_start:
- command: sh -c "echo WHY_IT_FAILED; exit 7"Attached — output is visible:
$ docker compose up --no-color ... ok-1 -> | HOOK_STDOUT ok-1 -> | HOOK_STDERR bad-1 -> | WHY_IT_FAILED bad hook exited with status 7Detached — output is gone:
$ docker compose up -d ; echo "exit=$?" ... Container proj-ok-1 Started bad hook exited with status 7 exit=1HOOK_STDOUT,HOOK_STDERR, andWHY_IT_FAILEDare nowhere. Same with--wait.Nothing recovers it afterwards:
$ docker compose logs # main container output only $ docker logs proj-bad-1 # main container output only $ docker inspect proj-bad-1 # no hook output / exit code field
Also observed: a failing post_start hook does not mark the container unhealthy (health check keeps passing, FailingStreak=0), so --wait cannot surface the failure via health state either — the only signal is the exit code of up itself.
Expected behavior
A hook that runs and fails should leave its output somewhere retrievable. At minimum one of:
- When a hook exits non-zero, its stdout/stderr is included in the CLI output alongside the existing
<service> hook exited with status <N>line (this is the actual bug — the failure is reported without the reason). - Hook output is written to a retrievable location — e.g. surfaced through
docker compose logsfor the owning service, or an ephemeral hook container whose logs survive like a failedpre_starthook's do. up -d/up --waitforwardpost_start/pre_stophook stdout/stderr to the CLI (prefixed like attached mode), so a wrapping process can capture it.
Compose Version
Docker Compose version v5.1.2Docker Environment
Docker Engine 29.4.0
OS: macOS (Darwin 25.6.0)Anything else?
No response
Source: docker/compose