airlock/docker.rs
1//! Docker daemon connection helpers.
2
3#[cfg(unix)]
4use std::{env, path::PathBuf};
5
6use bollard::Docker;
7#[cfg(unix)]
8use bollard::API_DEFAULT_VERSION;
9#[cfg(unix)]
10use home::home_dir;
11#[cfg(unix)]
12use saluki_error::ErrorContext as _;
13use saluki_error::GenericError;
14#[cfg(unix)]
15use tracing::debug;
16
17/// Default connection timeout, in seconds, matching bollard.
18#[cfg(unix)]
19const DEFAULT_TIMEOUT: u64 = 120;
20
21/// The standard Docker socket path on Linux.
22#[cfg(unix)]
23const STANDARD_SOCKET: &str = "/var/run/docker.sock";
24
25/// Connects to the Docker daemon.
26///
27/// On Windows, this uses Bollard's platform default connection logic.
28///
29/// On Unix hosts, this searches non-standard socket locations before falling back to Bollard's default connection logic.
30/// Resolution order:
31///
32/// 1. If `DOCKER_HOST` is set **or** `/var/run/docker.sock` exists on disk, defer to Bollard's default connection
33/// logic.
34/// 2. Otherwise, try each candidate socket path in order and use the first that exists.
35/// 3. If no candidate is found, fall back to `Docker::connect_with_defaults()` so the error message comes from Bollard.
36///
37/// # Unix candidate paths
38///
39/// These are checked in order when the standard socket is absent:
40///
41/// | Path | Environment |
42/// |------|-------------|
43/// | `$HOME/.docker/run/docker.sock` | Docker Desktop |
44/// | `$HOME/.orbstack/run/docker.sock` | OrbStack |
45/// | `$HOME/.rd/docker.sock` | Rancher Desktop |
46/// | `$HOME/.lima/default/sock/docker.sock` | Lima (default instance) |
47/// | `$HOME/.lima/docker/sock/docker.sock` | Lima (docker instance) |
48/// | `$HOME/.colima/default/docker.sock` | Colima |
49/// | `$HOME/.colima/docker/docker.sock` | Colima (docker profile) |
50/// | `$HOME/.local/share/containers/podman/machine/podman.sock` | Podman Desktop (macOS) |
51///
52/// # Errors
53///
54/// Returns an error if no reachable Docker daemon can be found. When connecting through a discovered non-standard Unix
55/// socket path, the error includes the path that was attempted.
56pub fn connect() -> Result<Docker, GenericError> {
57 connect_inner()
58}
59
60#[cfg(windows)]
61fn connect_inner() -> Result<Docker, GenericError> {
62 Ok(Docker::connect_with_defaults()?)
63}
64
65#[cfg(unix)]
66fn connect_inner() -> Result<Docker, GenericError> {
67 // Fast path: DOCKER_HOST is explicitly configured, or the standard socket exists.
68 if env::var("DOCKER_HOST").is_ok() || PathBuf::from(STANDARD_SOCKET).exists() {
69 return Ok(Docker::connect_with_defaults()?);
70 }
71
72 // Build candidate list from well-known non-standard locations.
73 if let Some(home) = home_dir() {
74 let candidates = [
75 home.join(".docker/run/docker.sock"),
76 home.join(".orbstack/run/docker.sock"),
77 home.join(".rd/docker.sock"),
78 home.join(".lima/default/sock/docker.sock"),
79 home.join(".lima/docker/sock/docker.sock"),
80 home.join(".colima/default/docker.sock"),
81 home.join(".colima/docker/docker.sock"),
82 home.join(".local/share/containers/podman/machine/podman.sock"),
83 ];
84
85 for path in &candidates {
86 if path.exists() {
87 let path = path.display();
88 debug!("using non-standard Docker socket: {path}");
89 return Docker::connect_with_unix(&path.to_string(), DEFAULT_TIMEOUT, API_DEFAULT_VERSION)
90 .with_error_context(|| format!("Failed to connect to Docker using socket discovered at {path}"));
91 }
92 }
93 }
94
95 // Nothing found: let bollard try (and fail with its own error message).
96 Ok(Docker::connect_with_defaults()?)
97}