Skip to content

Follow-up: improve Docker daemon image-build errors#66

Open
aniketmaurya wants to merge 1 commit intomainfrom
codex/github-mention-bug]-docker-is-required-to-build-images-ipp9yt
Open

Follow-up: improve Docker daemon image-build errors#66
aniketmaurya wants to merge 1 commit intomainfrom
codex/github-mention-bug]-docker-is-required-to-build-images-ipp9yt

Conversation

@aniketmaurya
Copy link
Collaborator

@aniketmaurya aniketmaurya commented Mar 18, 2026

fixes #63

Motivation

  • Users hit a generic Docker is required to build images error even when Docker is installed but the daemon is not reachable, which provides poor guidance for common failures.
  • Make image-build failures actionable by surfacing whether Docker is missing, the daemon is unreachable, or the caller lacks socket permissions.

Description

  • Update ImageBuilder.check_docker to use docker info so the check verifies the daemon is reachable as well as the client binary being present (src/smolvm/build.py).
  • Add ImageBuilder.docker_requirement_error() to produce richer, actionable ImageError messages for three cases: Docker not installed, Docker daemon unreachable (include original Docker error text), and permission/socket-access issues (src/smolvm/build.py).
  • Replace direct ImageError raises in the image build entry points to call the new diagnostic helper so all image-build paths (build_alpine_ssh, build_alpine_ssh_key, build_debian_ssh_key, and similar) surface the improved message (src/smolvm/build.py).
  • Add unit tests covering the new diagnostics: missing Docker and daemon-unreachable error paths (tests/test_build.py).

Testing

  • Ran ruff check src/smolvm/build.py tests/test_build.py and it passed.
  • Ran PYTHONPATH=src pytest tests/test_build.py and all tests passed (7 passed).
  • Running pytest tests/test_build.py without PYTHONPATH=src fails in this environment due to the package not being installed into the interpreter path (import error), which is unrelated to the code changes.

Codex Task

Summary by CodeRabbit

  • Bug Fixes

    • Enhanced Docker unavailability error messages to provide clearer, context-aware guidance including installation instructions, daemon startup tips, and permission troubleshooting for smoother user experience.
  • Tests

    • Added comprehensive test coverage for Docker diagnostics to validate error messaging in scenarios where Docker is missing or the daemon is unreachable.

@coderabbitai
Copy link

coderabbitai bot commented Mar 18, 2026

📝 Walkthrough

Walkthrough

The pull request enhances Docker availability error handling by introducing a new docker_requirement_error() method on ImageBuilder that generates context-aware, diagnostic error messages for Docker unavailability scenarios, and updates multiple build methods to consistently use this improved error handling instead of hardcoded error strings.

Changes

Cohort / File(s) Summary
Docker Error Diagnostics
src/smolvm/build.py
Introduces docker_requirement_error() method that constructs detailed ImageError objects with platform-specific hints for missing Docker binaries, unreachable daemon, or permission issues. Updates check_docker() to use docker info instead of docker version. Replaces inline error strings in build_alpine_ssh, build_alpine_ssh_key, build_debian_ssh_key, and build_openclaw_rootfs with calls to the new error builder.
Docker Diagnostics Tests
tests/test_build.py
Adds test class with two new tests: test_docker_requirement_error_when_docker_missing verifies missing Docker detection and installation hints, and test_docker_requirement_error_when_daemon_unreachable verifies daemon connectivity failure messaging including daemon startup guidance.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 A docker diagnostic tale,
Where errors once were terse and pale,
Now hints bloom forth with wisdom bright,
To guide lost builders through the night,
Context-aware, the rabbit's delight! 🐳✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: improving Docker daemon error messages in image-build operations, which is the primary focus of the changeset.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Description check ✅ Passed The pull request description comprehensively covers all required template sections: Summary describes the motivation and changes, Related Issues references #63, and Testing documents the test commands run locally with results.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/github-mention-bug]-docker-is-required-to-build-images-ipp9yt
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/smolvm/build.py (1)

103-108: ⚠️ Potential issue | 🟠 Major

Harden Docker probes against PATH hijacking and hangs.

Both probes execute docker via PATH and without timeout. That leaves a command-injection surface (Ruff S607) and a potential indefinite hang on daemon/host misconfiguration.

🔧 Suggested hardening patch
     def check_docker(self) -> bool:
         """Check if Docker is available and the daemon is reachable."""
+        docker_bin = shutil.which("docker")
+        if docker_bin is None:
+            return False
         try:
             subprocess.run(
-                ["docker", "info"],
+                [docker_bin, "info"],
                 check=True,
                 stdout=subprocess.DEVNULL,
                 stderr=subprocess.DEVNULL,
+                timeout=10,
             )
             return True
         except (subprocess.CalledProcessError, FileNotFoundError):
             return False
 
     def docker_requirement_error(self) -> ImageError:
         """Create a helpful Docker availability error."""
@@
-        if shutil.which("docker") is None:
+        docker_bin = shutil.which("docker")
+        if docker_bin is None:
             return ImageError(f"Docker is required to build images. {install_hint}")
@@
             subprocess.run(
-                ["docker", "info"],
+                [docker_bin, "info"],
                 check=True,
                 capture_output=True,
                 text=True,
+                timeout=10,
             )

Also applies to: 129-134

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/smolvm/build.py` around lines 103 - 108, Replace the PATH-invoked,
potentially-hanging docker probes with an explicit resolved docker binary and a
timeout: use shutil.which("docker") to get an absolute path (raise a clear error
if None), then call subprocess.run([docker_path, "info"], check=True,
timeout=10, stdout=..., stderr=...) instead of subprocess.run(["docker",
"info"], ...); apply the same change to the second probe (the other
subprocess.run([... "docker", "info" ...]) occurrence) so both use the resolved
docker_path and a timeout to mitigate PATH hijacking (Ruff S607) and indefinite
hangs.
🧹 Nitpick comments (1)
tests/test_build.py (1)

50-70: Add a permission/socket-denied diagnostic test to lock the third branch.

The helper distinguishes three Docker failure classes; this suite currently asserts two. Add a dedicated permission-denied socket case to prevent regression in branch ordering/classification.

🧪 Suggested test addition
 class TestDockerDiagnostics:
@@
     `@patch`("smolvm.build.subprocess.run")
     def test_docker_requirement_error_when_daemon_unreachable(
         self, mock_subprocess_run: MagicMock, tmp_path: Path
     ) -> None:
@@
         assert "Start Docker Desktop or the Docker service" in str(error)
         assert "Cannot connect to the Docker daemon" in str(error)
+
+    `@patch`("smolvm.build.subprocess.run")
+    def test_docker_requirement_error_when_socket_permission_denied(
+        self, mock_subprocess_run: MagicMock, tmp_path: Path
+    ) -> None:
+        builder = ImageBuilder(cache_dir=tmp_path / "images")
+        mock_subprocess_run.side_effect = subprocess.CalledProcessError(
+            1,
+            ["docker", "info"],
+            stderr=(
+                "error during connect: permission denied while trying to connect "
+                "to the Docker daemon socket at unix:///var/run/docker.sock"
+            ),
+        )
+
+        with patch("smolvm.build.shutil.which", return_value="/usr/bin/docker"):
+            error = builder.docker_requirement_error()
+
+        assert "cannot access the Docker daemon socket" in str(error)
+        assert "docker.sock" in str(error)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/test_build.py` around lines 50 - 70, The test suite lacks a case for
the third Docker failure class (permission/socket-denied); add a new test
similar to test_docker_requirement_error_when_daemon_unreachable that patches
subprocess.run to raise subprocess.CalledProcessError for
ImageBuilder.docker_requirement_error() with stderr indicating a
permission/socket-denied message (e.g. "permission denied" or "connect:
permission denied"), ensure shutil.which is patched to return a docker path,
call builder.docker_requirement_error(), and assert the returned error string
contains the permission/socket-denied diagnostic (e.g. "permission denied" and a
guidance about socket/file permissions) so the permission branch in
docker_requirement_error is covered and locked in.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/smolvm/build.py`:
- Around line 140-151: The permission/socket error branch is being bypassed
because the broader `"error during connect"` check runs first; update the
conditional order in build.py so the permission-specific check (the `if
"permission denied" in details_lower and "docker.sock" in details_lower:` branch
that returns ImageError using permission_hint) appears before the `"error during
connect"` / daemon-unreachable branch (the block that returns ImageError using
start_hint), ensuring socket permission errors are matched to the
permission_hint; keep the same predicates and return values (using details,
details_lower, permission_hint, start_hint, and ImageError) but swap their
positions so the permission check is evaluated first.

---

Outside diff comments:
In `@src/smolvm/build.py`:
- Around line 103-108: Replace the PATH-invoked, potentially-hanging docker
probes with an explicit resolved docker binary and a timeout: use
shutil.which("docker") to get an absolute path (raise a clear error if None),
then call subprocess.run([docker_path, "info"], check=True, timeout=10,
stdout=..., stderr=...) instead of subprocess.run(["docker", "info"], ...);
apply the same change to the second probe (the other subprocess.run([...
"docker", "info" ...]) occurrence) so both use the resolved docker_path and a
timeout to mitigate PATH hijacking (Ruff S607) and indefinite hangs.

---

Nitpick comments:
In `@tests/test_build.py`:
- Around line 50-70: The test suite lacks a case for the third Docker failure
class (permission/socket-denied); add a new test similar to
test_docker_requirement_error_when_daemon_unreachable that patches
subprocess.run to raise subprocess.CalledProcessError for
ImageBuilder.docker_requirement_error() with stderr indicating a
permission/socket-denied message (e.g. "permission denied" or "connect:
permission denied"), ensure shutil.which is patched to return a docker path,
call builder.docker_requirement_error(), and assert the returned error string
contains the permission/socket-denied diagnostic (e.g. "permission denied" and a
guidance about socket/file permissions) so the permission branch in
docker_requirement_error is covered and locked in.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 63df860d-4421-47eb-b9d6-7370235134d1

📥 Commits

Reviewing files that changed from the base of the PR and between 30e0ca9 and 318d597.

📒 Files selected for processing (2)
  • src/smolvm/build.py
  • tests/test_build.py

Comment on lines +140 to +151
if (
"cannot connect to the docker daemon" in details_lower
or "is the docker daemon running" in details_lower
or "error during connect" in details_lower
):
return ImageError(
f"{start_hint} Original Docker error: {details or 'unknown error.'}"
)
if "permission denied" in details_lower and "docker.sock" in details_lower:
return ImageError(
f"{permission_hint} Original Docker error: {details or 'unknown error.'}"
)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Permission/socket errors can be misclassified as daemon-unreachable.

The "error during connect" check runs before the permission check. Common socket permission failures can include that phrase and hit the wrong branch, defeating the “distinct permission diagnostics” goal.

🔁 Suggested branch-order fix
-            if (
-                "cannot connect to the docker daemon" in details_lower
-                or "is the docker daemon running" in details_lower
-                or "error during connect" in details_lower
-            ):
-                return ImageError(
-                    f"{start_hint} Original Docker error: {details or 'unknown error.'}"
-                )
             if "permission denied" in details_lower and "docker.sock" in details_lower:
                 return ImageError(
                     f"{permission_hint} Original Docker error: {details or 'unknown error.'}"
                 )
+            if (
+                "cannot connect to the docker daemon" in details_lower
+                or "is the docker daemon running" in details_lower
+                or "error during connect" in details_lower
+            ):
+                return ImageError(
+                    f"{start_hint} Original Docker error: {details or 'unknown error.'}"
+                )
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (
"cannot connect to the docker daemon" in details_lower
or "is the docker daemon running" in details_lower
or "error during connect" in details_lower
):
return ImageError(
f"{start_hint} Original Docker error: {details or 'unknown error.'}"
)
if "permission denied" in details_lower and "docker.sock" in details_lower:
return ImageError(
f"{permission_hint} Original Docker error: {details or 'unknown error.'}"
)
if "permission denied" in details_lower and "docker.sock" in details_lower:
return ImageError(
f"{permission_hint} Original Docker error: {details or 'unknown error.'}"
)
if (
"cannot connect to the docker daemon" in details_lower
or "is the docker daemon running" in details_lower
or "error during connect" in details_lower
):
return ImageError(
f"{start_hint} Original Docker error: {details or 'unknown error.'}"
)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/smolvm/build.py` around lines 140 - 151, The permission/socket error
branch is being bypassed because the broader `"error during connect"` check runs
first; update the conditional order in build.py so the permission-specific check
(the `if "permission denied" in details_lower and "docker.sock" in
details_lower:` branch that returns ImageError using permission_hint) appears
before the `"error during connect"` / daemon-unreachable branch (the block that
returns ImageError using start_hint), ensuring socket permission errors are
matched to the permission_hint; keep the same predicates and return values
(using details, details_lower, permission_hint, start_hint, and ImageError) but
swap their positions so the permission check is evaluated first.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Docker is required to build images error

1 participant