docker/compose · error
%s hook exited with status %d
Error message
%s hook exited with status %d
What it means
compose runs a service lifecycle hook by creating an exec in the running container, streaming its output, then calling ExecInspect. If the hook process finished with a non-zero exit code, this error reports the service name and the hook's exit status. It is compose's way of gating service startup/teardown on hook success.
Source
Thrown at pkg/compose/hook.go:94
return err
}
defer attach.Close()
if service.Tty {
_, err = io.Copy(wOut, attach.Reader)
} else {
_, err = stdcopy.StdCopy(wOut, wOut, attach.Reader)
}
if err != nil {
return err
}
inspected, err := s.apiClient().ExecInspect(ctx, exec.ID, client.ExecInspectOptions{})
if err != nil {
return err
}
if inspected.ExitCode != 0 {
return fmt.Errorf("%s hook exited with status %d", service.Name, inspected.ExitCode)
}
return nil
}
func (s *composeService) runWaitExec(ctx context.Context, execID string, service types.ServiceConfig, listener api.ContainerEventListener) error {
_, err := s.apiClient().ExecStart(ctx, execID, client.ExecStartOptions{
Detach: listener == nil,
TTY: service.Tty,
})
if err != nil {
return err
}
// We miss a ContainerExecWait API
tick := time.NewTicker(100 * time.Millisecond)
for {
select {
case <-ctx.Done():View on GitHub (pinned to ddc4b044b6)
Solutions
- Reproduce manually: docker compose exec <service> <hook-command>; echo $? to see the real exit code and stderr
- Fix the hook command/script so it exits 0 on success (shebang, executable bit, absolute paths)
- If the hook waits on a dependency, add bounded retry inside the hook instead of failing immediately
- Ensure any binary the hook invokes exists in the service image (build a custom image if needed)
Defensive patterns
Strategy: try-catch
Try / catch
if err := compose.Up(ctx, project, opts); err != nil {
if strings.Contains(err.Error(), "hook exited with status") {
// hook failure: surface the service name + status, point user at hook logs
}
} Prevention
- Test every hook command manually in the service image before adding it to compose
- Keep hooks idempotent and give them internal bounded retries for dependencies
- Use absolute paths and ensure binaries exist in the hook's execution environment
When it happens
Trigger: docker compose up (or run) on a service with a hook whose command returns non-zero; after the attached exec's output is drained via StdCopy, ExecInspect(ctx, exec.ID) returns ExitCode != 0.
Common situations: Hook script runs a readiness check (e.g. pg_isready, curl) against a dependency that never becomes ready; hook binary missing from the image (127); script lacks +x or wrong path (126); hook logic intentionally exits 1 on validation failure.
Related errors
- service %q pre_start[%d] exited with code %d
- exit code %d
- process still running
- interactive exec is not supported in dry-run mode
- deleting paths in %s: %w
AI-assisted analysis of docker/compose@ddc4b044b6 (2026-08-15).
Data as JSON: /api/errors/7f1a92c6ccf96ccf.
Report an issue: GitHub.