{"record":{"id":"ee95df68974381e3","repo":"docker/compose","slug":"service-q-pre-start-d-exited-with-code-d","errorCode":null,"errorMessage":"service %q pre_start[%d] exited with code %d","messagePattern":"service %q pre_start\\[(.+?)\\] exited with code (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/compose/pre_start.go","lineNumber":254,"sourceCode":"\t\t\t}\n\t\t\treturn preStartResultErr(serviceName, index, res)\n\t\tcase err := <-errCh:\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\t// nil on Error: stream closed cleanly. Disable this case so a\n\t\t\t// closed channel can't fire repeatedly.\n\t\t\terrCh = nil\n\t\t}\n\t}\n}\n\nfunc preStartResultErr(serviceName string, index int, res container.WaitResponse) error {\n\tif res.Error != nil {\n\t\treturn fmt.Errorf(\"service %q pre_start[%d] wait error: %s\", serviceName, index, res.Error.Message)\n\t}\n\tif res.StatusCode != 0 {\n\t\treturn fmt.Errorf(\"service %q pre_start[%d] exited with code %d\", serviceName, index, res.StatusCode)\n\t}\n\treturn nil\n}\n\n// streamPreStartLogs returns a channel that is closed once the hook log stream\n// has been fully drained (or never opened). Callers must wait on it before\n// returning so the goroutine cannot outlive the hook.\nfunc (s *composeService) streamPreStartLogs(ctx context.Context, containerID string, service types.ServiceConfig, index int, listener api.ContainerEventListener) <-chan struct{} {\n\tdone := make(chan struct{})\n\tif listener == nil {\n\t\tclose(done)\n\t\treturn done\n\t}\n\tsource := fmt.Sprintf(\"%s pre_start[%d] ->\", service.Name, index)\n\tlogs, err := s.apiClient().ContainerLogs(ctx, containerID, client.ContainerLogsOptions{\n\t\tShowStdout: true,\n\t\tShowStderr: true,\n\t\tFollow:     true,","sourceCodeStart":236,"sourceCodeEnd":272,"githubUrl":"https://github.com/docker/compose/blob/ddc4b044b62e9f715212ea4143fa830fac76382f/pkg/compose/pre_start.go#L236-L272","documentation":"The pre_start hook container finished cleanly but with a non-zero status code. Compose reports service, hook index, and exit code, and gates the service from starting. This is the pre_start analogue of a failing init container in other orchestrators.","triggerScenarios":"runPreStartHook's wait returns StatusCode != 0 with no daemon error: the hook command itself failed (missing binary, bad script, failed migration, etc.).","commonSituations":"Migration script failing on schema conflict; hook image missing the tool it invokes; wrong working_dir/command in the pre_start entry; script exiting on first error via set -e.","solutions":["Reproduce the hook manually in a container with the same image/volumes and inspect its output","Fix the hook command/entrypoint (absolute paths, executable bit, correct working dir)","Make migrations idempotent so re-runs succeed after partial failure","Ensure the hook image actually contains the binaries the command references"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"if err := upWithPreStart(ctx, project); err != nil {\n    if strings.Contains(err.Error(), \"pre_start\") && strings.Contains(err.Error(), \"exited with code\") {\n        // re-run the hook manually in the same image/volumes to debug\n    }\n}","preventionTips":["Make pre_start commands idempotent (migrations especially) so re-runs succeed","Verify the hook image contains its dependencies before promoting to compose files"],"tags":["pre-start","hooks","exit-code","init"],"backgroundTag":null,"analyzedSha":"ddc4b044b62e9f715212ea4143fa830fac76382f","analyzedAt":"2026-08-15T13:31:42.319Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}