diff --git a/testcontainers/src/core/client.rs b/testcontainers/src/core/client.rs index 4efc2ce4..be1c3599 100644 --- a/testcontainers/src/core/client.rs +++ b/testcontainers/src/core/client.rs @@ -312,7 +312,7 @@ impl Client { } pub(crate) async fn docker_hostname(&self) -> Result { - let docker_host = self.config.docker_host(); + let docker_host = &self.config.docker_host(); let docker_host_url = Url::from_str(docker_host) .map_err(|e| ConfigurationError::InvalidDockerHost(e.to_string()))?; diff --git a/testcontainers/src/core/client/bollard_client.rs b/testcontainers/src/core/client/bollard_client.rs index e185c4cc..0362544d 100644 --- a/testcontainers/src/core/client/bollard_client.rs +++ b/testcontainers/src/core/client/bollard_client.rs @@ -8,7 +8,7 @@ use crate::core::env; const DEFAULT_TIMEOUT: Duration = Duration::from_secs(2 * 60); pub(super) fn init(config: &env::Config) -> Result { - let host = config.docker_host(); + let host = &config.docker_host(); let host_url = Url::from_str(host)?; match host_url.scheme() { @@ -36,7 +36,7 @@ fn connect_with_ssl(config: &env::Config) -> Result &str { + /// 4. Read the default Docker socket path, without the unix schema. E.g. `/var/run/docker.sock`. + /// 5. Read the rootless Docker socket path, checking in the following alternative locations: + /// 1. `${XDG_RUNTIME_DIR}/.docker/run/docker.sock`. + /// 2. `${HOME}/.docker/run/docker.sock`. + /// 3. `${HOME}/.docker/desktop/docker.sock`. + /// 6. The default Docker socket including schema will be returned if none of the above are set. + pub(crate) fn docker_host(&self) -> Cow<'_, str> { self.tc_host .as_deref() .or(self.host.as_deref()) - .unwrap_or(DEFAULT_DOCKER_HOST) + .map(Cow::Borrowed) + .unwrap_or_else(|| { + if cfg!(unix) { + validate_path("/var/run/docker.sock".into()) + .or_else(|| { + runtime_dir().and_then(|dir| { + validate_path(format!("{}/.docker/run/docker.sock", dir.display())) + }) + }) + .or_else(|| { + home_dir().and_then(|dir| { + validate_path(format!("{}/.docker/run/docker.sock", dir.display())) + }) + }) + .or_else(|| { + home_dir().and_then(|dir| { + validate_path(format!( + "{}/.docker/desktop/docker.sock", + dir.display() + )) + }) + }) + .map(|p| format!("unix://{p}")) + .map(Cow::Owned) + .unwrap_or(DEFAULT_DOCKER_HOST.into()) + } else { + DEFAULT_DOCKER_HOST.into() + } + }) } pub(crate) fn tls_verify(&self) -> bool { @@ -150,6 +186,15 @@ impl Config { } } +/// Validate the path exists and return it if it does. +fn validate_path(path: String) -> Option { + if Path::new(&path).exists() { + Some(path) + } else { + None + } +} + /// Read the Docker authentication configuration in the following order: /// /// 1. `DOCKER_AUTH_CONFIG` environment variable, unmarshalling the string value from its JSON representation and using it as the Docker config. diff --git a/testcontainers/src/lib.rs b/testcontainers/src/lib.rs index bdefce47..76baa54f 100644 --- a/testcontainers/src/lib.rs +++ b/testcontainers/src/lib.rs @@ -40,7 +40,12 @@ //! 1. Docker host from the `tc.host` property in the `~/.testcontainers.properties` file. //! 2. `DOCKER_HOST` environment variable. //! 3. Docker host from the "docker.host" property in the `~/.testcontainers.properties` file. -//! 4. Else, the default Docker socket will be returned. +//! 4. Read the default Docker socket path, without the unix schema. E.g. `/var/run/docker.sock`. +//! 5. Read the rootless Docker socket path, checking in the following alternative locations: +//! 1. `${XDG_RUNTIME_DIR}/.docker/run/docker.sock`. +//! 2. `${HOME}/.docker/run/docker.sock`. +//! 3. `${HOME}/.docker/desktop/docker.sock`. +//! 6. The default Docker socket including schema will be returned if none of the above are set. //! //! ### Docker authentication //!