dev_container: Detect actual buildx availability instead of assuming Docker has it (#53910)
krisswee
,
krisswee
, and
KyleBarton
created 2 weeks ago
`supports_compose_buildkit()` returned `!self.is_podman()`, assuming
every Docker install has buildx. On setups like Colima where the buildx
CLI plugin isn't installed, this causes builds to fail with "classic
builder doesn't support additional contexts" since `DOCKER_BUILDKIT=1`
alone isn't enough without the plugin.
Now probes for `docker buildx version` at construction time and caches
the result. When buildx isn't found, the existing scratch-image fallback
path (same one Podman uses) kicks in instead.
Closes #53890
Release Notes:
- Fixed dev container builds failing on Docker installations without the
buildx plugin.
---------
Co-authored-by: krisswee <krisswee@users.noreply.github.com>
Co-authored-by: KyleBarton <kjb@initialcapacity.io>
Change summary
crates/dev_container/src/devcontainer_manifest.rs | 12 ++++---
crates/dev_container/src/docker.rs | 25 ++++++++++++++--
2 files changed, 29 insertions(+), 8 deletions(-)
Detailed changes
@@ -2108,9 +2108,9 @@ pub(crate) async fn read_devcontainer_configuration(
environment: HashMap<String, String>,
) -> Result<DevContainer, DevContainerError> {
let docker = if context.use_podman {
- Docker::new("podman")
+ Docker::new("podman").await
} else {
- Docker::new("docker")
+ Docker::new("docker").await
};
let mut dev_container = DevContainerManifest::new(
context,
@@ -2132,9 +2132,9 @@ pub(crate) async fn spawn_dev_container(
local_project_path: &Path,
) -> Result<DevContainerUp, DevContainerError> {
let docker = if context.use_podman {
- Docker::new("podman")
+ Docker::new("podman").await
} else {
- Docker::new("docker")
+ Docker::new("docker").await
};
let mut devcontainer_manifest = DevContainerManifest::new(
context,
@@ -4784,12 +4784,14 @@ FROM docker.io/hexpm/elixir:1.21-erlang-28.4.1-debian-trixie-20260316-slim AS de
pub(crate) struct FakeDocker {
exec_commands_recorded: Mutex<Vec<RecordedExecCommand>>,
podman: bool,
+ has_buildx: bool,
}
impl FakeDocker {
pub(crate) fn new() -> Self {
Self {
podman: false,
+ has_buildx: true,
exec_commands_recorded: Mutex::new(Vec::new()),
}
}
@@ -5029,7 +5031,7 @@ FROM docker.io/hexpm/elixir:1.21-erlang-28.4.1-debian-trixie-20260316-slim AS de
}))
}
fn supports_compose_buildkit(&self) -> bool {
- !self.podman
+ !self.podman && self.has_buildx
}
fn docker_cli(&self) -> String {
if self.podman {
@@ -175,6 +175,7 @@ pub(crate) struct DockerComposeConfig {
pub(crate) struct Docker {
docker_cli: String,
+ has_buildx: bool,
}
impl DockerInspect {
@@ -184,9 +185,24 @@ impl DockerInspect {
}
impl Docker {
- pub(crate) fn new(docker_cli: &str) -> Self {
+ pub(crate) async fn new(docker_cli: &str) -> Self {
+ let has_buildx = if docker_cli == "podman" {
+ false
+ } else {
+ let output = Command::new(docker_cli)
+ .args(["buildx", "version"])
+ .output()
+ .await;
+ output.map(|o| o.status.success()).unwrap_or(false)
+ };
+ if !has_buildx && docker_cli != "podman" {
+ log::info!(
+ "docker buildx not found; dev container builds will use the scratch-image fallback"
+ );
+ }
Self {
docker_cli: docker_cli.to_string(),
+ has_buildx,
}
}
@@ -372,7 +388,7 @@ impl DockerClient for Docker {
}
fn supports_compose_buildkit(&self) -> bool {
- !self.is_podman()
+ self.has_buildx
}
}
@@ -595,7 +611,10 @@ mod test {
#[test]
fn should_create_docker_inspect_command() {
- let docker = Docker::new("docker");
+ let docker = Docker {
+ docker_cli: "docker".to_string(),
+ has_buildx: false,
+ };
let given_id = "given_docker_id";
let command = docker.create_docker_inspect(given_id);