{"record":{"id":"70e8aa02fb1f52be","repo":"Billionmail/BillionMail","slug":"failed-to-read-temporary-docker-compose-yml-file","errorCode":null,"errorMessage":"failed to read temporary docker-compose.yml file: file is empty or does not exist","messagePattern":"failed to read temporary docker-compose\\.yml file: file is empty or does not exist","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"core/internal/service/multi_ip_domain/config_manager.go","lineNumber":196,"sourceCode":"\t}\n\tdefer dk.Close()\n\n\t// Step 1: 复制宿主机的 docker-compose.yml 到宿主机的 core-data 目录\n\t// 注意：这里目标路径使用宿主机的路径，因为 chroot /host_root 看到的是宿主机文件系\n\tcopyCmd := []string{\n\t\t\"/bin/sh\", \"-c\",\n\t\tfmt.Sprintf(`chroot /host_root cp \"%s\" \"%s\"`, originalPath, hostTempDockerComposePath),\n\t}\n\tresult, err := dk.ExecHostCommand(ctx, copyCmd)\n\tif err != nil || result.ExitCode != 0 {\n\t\treturn gerror.New(\"failed to copy original configuration file, please check path and permissions\")\n\t}\n\n\t// Step 2: 从容器内路径读取复制的文件\n\t// 由于文件映射，容器内可以通过映射路径读到刚才复制的文件\n\tcontent := gfile.GetContents(tempDockerComposePath)\n\tif content == \"\" {\n\t\treturn fmt.Errorf(\"failed to read temporary docker-compose.yml file: file is empty or does not exist\")\n\t}\n\n\t// Cleanup temporary file\n\tdefer func() {\n\t\tif gfile.Exists(tempDockerComposePath) {\n\t\t\tos.Remove(tempDockerComposePath)\n\t\t\tg.Log().Debugf(ctx, \"Cleaned up temporary file: %s\", tempDockerComposePath)\n\t\t}\n\t}()\n\n\tg.Log().Debugf(ctx, \"Successfully read docker-compose.yml file, size: %d bytes\", len(content))\n\tnewContent, err := m.modifyDockerComposeText(ctx, content, configs)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to modify docker-compose content: %v\", err)\n\t}\n\n\t// Step 7: Write new file docker-compose_addnetwork.yml\n\ttempOutputPath := filepath.Join(containerDataPath, \"temp_docker-compose_addnetwork.yml\")","sourceCodeStart":178,"sourceCodeEnd":214,"githubUrl":"https://github.com/Billionmail/BillionMail/blob/fc36c76c050c3775c5e899faf7403cf0262d2744/core/internal/service/multi_ip_domain/config_manager.go#L178-L214","documentation":"After copying docker-compose.yml to the host core-data dir via chroot cp, updateDockerCompose reads it back through the container-side mapped path with gfile.GetContents. If the content is empty (file missing or zero bytes), the volume mapping between the container path and the host path is assumed broken, and the compose update aborts (triggering rollback).","triggerScenarios":"gfile.GetContents(tempDockerComposePath) returns \"\": the chroot cp wrote to the host path but ../core/data is not volume-mounted to host core-data, the copy raced/silently failed, or the source docker-compose.yml itself is empty.","commonSituations":"Deployment where the host bind mount path differs from what public.HostWorkDir expects; Docker volume remapped or renamed; docker-compose.yml truncated to 0 bytes on the host; propagation delay on network filesystems between cp and read.","solutions":["Verify the bind mount maps host core-data to the container path returned by public.AbsPath('../core/data') (docker inspect the service).","Check that docker-compose.yml on the host is non-empty and readable (wc -c /opt/billionmail/docker-compose.yml).","Re-run the copy manually: chroot /host_root cp <compose> <core-data>/temp_docker-compose.yml and inspect the result.","Add fsync/size verification after cp, and report the host path in the error to ease debugging."],"exampleFix":"// before\ncontent := gfile.GetContents(tempDockerComposePath)\nif content == \"\" {\n\treturn fmt.Errorf(\"failed to read temporary docker-compose.yml file: file is empty or does not exist\")\n}\n// after\ncontent := gfile.GetContents(tempDockerComposePath)\nif content == \"\" {\n\treturn fmt.Errorf(\"failed to read temporary docker-compose.yml at %s (host path %s): empty or missing; check volume mapping\", tempDockerComposePath, hostTempDockerComposePath)\n}","handlingStrategy":"validation","validationCode":"// preflight: verify host↔container mapping\nhostCompose := filepath.Join(public.HostWorkDir, \"docker-compose.yml\")\nfi, err := os.Stat(hostCompose)\nif err != nil || fi.Size() == 0 {\n\treturn fmt.Errorf(\"docker-compose.yml missing or empty on host\")\n}\nif _, err := os.Stat(filepath.Join(public.HostWorkDir, \"core-data\")); err != nil {\n\treturn fmt.Errorf(\"core-data bind mount missing\")\n}","typeGuard":null,"tryCatchPattern":"if err := mgr.ApplyConfigsWithRollback(ctx, configs); err != nil {\n\tif strings.Contains(err.Error(), \"file is empty or does not exist\") {\n\t\t// inspect docker volume mounts; temp copy never appeared via the mapping\n\t}\n}","preventionTips":["Confirm the bind mount source matches the container path computed by AbsPath.","Verify docker-compose.yml is non-empty before applying.","Use consistent mount propagation (rshared) if on network storage.","Log both container and host temp paths on failure."],"tags":["docker-compose","volume-mount","filesystem","go"],"backgroundTag":"volume-mapping-broken","analyzedSha":"fc36c76c050c3775c5e899faf7403cf0262d2744","analyzedAt":"2026-09-05T21:28:54.019Z","contentChangedAt":"2026-09-05T21:28:54.019Z","schemaVersion":2},"datasetVersion":"2026-09-12T22:17:10.623Z"}