Docker Image not being build with Bollard

⚓ rust    📅 2025-05-17    👤 surdeus    👁️ 4      

surdeus

Warning

This post was published 44 days ago. The information described in this article may have changed.

I'm using the [Bollard]Bollard crate in Rust to build a Docker image from a custom Dockerfile and then create a container from that image. when I try to create the image, I get the following error:
Error during image build: Docker responded with status code 500: Cannot locate specified Dockerfile: Dockerfile
I've checked that:

  • The image name and tag are consistent (python_executor:latest) in both the build and container creation steps.
  • The Dockerfile is added to the tar archive with the correct name.
  • The [BuildImageOptions] uses the correct dockerfile field .

Despite this, the image is not being created

use bollard::Docker;
use bollard::container::{Config, CreateContainerOptions, StartContainerOptions};
use bollard::exec::{CreateExecOptions, StartExecResults};
use bollard::image::BuildImageOptions;
use bollard::models::{HostConfig, PortBinding};
use futures_util::stream::StreamExt;
use std::error::Error;
use std::fs::File;
use std::path::Path;
use tar::Builder;
use tokio::io::AsyncReadExt;

pub async fn handle_request(language: &str, code: &str) -> Result<String, Box<dyn Error>> {
    let docker = Docker::connect_with_local_defaults()?;

    // Select the appropriate Dockerfile
    let dockerfile_path = match language {
        "python" => "./docker/Dockerfile.python",
        "javascript" => "./docker/Dockerfile.javascript",
        "java" => "./docker/Dockerfile.java",
        _ => return Err(format!("Unsupported language: {}", language).into()),
    };

    // Build and run the container
    let container_name = build_and_run_container(&docker, dockerfile_path, language).await?;

    // Execute the code inside the container
    let result = execute_code_in_container(&docker, &container_name, code).await?;

    Ok(result)
}

pub async fn build_and_run_container(
    docker: &Docker,
    dockerfile_path: &str,
    language: &str,
) -> Result<String, Box<dyn Error>> {
    let image_name = format!("{}_executor:latest", language);
    // Create tar archive for build context
    let tar_path = "./docker/context.tar";
    let dockerfile_name = create_tar_archive(dockerfile_path, tar_path)?; // This should be a sync function that writes a tarball
    println!("Using dockerfile_name: '{}'", dockerfile_name);
    // Use a sync File, not tokio::fs::File, because bollard expects a blocking Read stream
    let mut file = tokio::fs::File::open(tar_path).await?;

    let mut contents = Vec::new();
    file.read(&mut contents).await?;
    // Build image options
    let build_options = BuildImageOptions {
        dockerfile: dockerfile_name,
        t: image_name.clone(),
        rm: true,
        ..Default::default()
    };

    // Start the image build stream
    let mut build_stream = docker.build_image(build_options, None, Some(contents.into()));

    // Print docker build output logs
    while let Some(build_output) = build_stream.next().await {
        match build_output {
            Ok(output) => {
                if let Some(stream) = output.stream {
                    print!("{}", stream);
                }
            }
            Err(e) => {
                eprintln!("Error during image build: {}", e);
                return Err(Box::new(e));
            }
        }
    }

    println!("Docker image '{}' built successfully!", image_name);

    // Create container config
    let container_name = format!("{}_executor_container", language);
    let config = Config {
        image: Some(image_name),
        host_config: Some(HostConfig {
            port_bindings: Some(
                [(
                    "5000/tcp".to_string(),
                    Some(vec![PortBinding {
                        host_ip: Some("0.0.0.0".to_string()),
                        host_port: Some("5000".to_string()),
                    }]),
                )]
                .iter()
                .cloned()
                .collect(),
            ),
            ..Default::default()
        }),
        ..Default::default()
    };
    // Create container
    docker
        .create_container(
            Some(CreateContainerOptions {
                name: &container_name,
                platform: None,
            }),
            config,
        )
        .await?;
    println!("Container '{}' created successfully.", container_name);

    // Start container
    docker
        .start_container(&container_name, None::<StartContainerOptions<String>>)
        .await?;
    println!("Container '{}' started successfully!", container_name);

    Ok(container_name)
}

async fn execute_code_in_container(
    docker: &Docker,
    container_name: &str,
    code: &str,
) -> Result<String, Box<dyn Error>> {
    let shell_command = format!("echo '{}' > script.py && python script.py", code);
    let exec_options = CreateExecOptions {
        cmd: Some(vec!["sh", "-c", &shell_command]),
        attach_stdout: Some(true),
        attach_stderr: Some(true),
        ..Default::default()
    };

    let exec = docker.create_exec(container_name, exec_options).await?;
    let output = docker.start_exec(&exec.id, None).await?;

    match output {
        StartExecResults::Attached { mut output, .. } => {
            let mut result = String::new();
            while let Some(Ok(log)) = output.next().await {
                match log {
                    bollard::container::LogOutput::StdOut { message } => {
                        result.push_str(&String::from_utf8_lossy(&message));
                    }
                    bollard::container::LogOutput::StdErr { message } => {
                        result.push_str(&String::from_utf8_lossy(&message));
                    }
                    _ => {}
                }
            }
            Ok(result)
        }
        _ => Err("Failed to execute code in container".into()),
    }
}

fn create_tar_archive(dockerfile_path: &str, tar_path: &str) -> Result<String, Box<dyn Error>> {
    let tar_file = File::create(tar_path)?;
    let mut tar_builder = Builder::new(tar_file);

    let _dockerfile_name = Path::new(dockerfile_path)
        .file_name()
        .ok_or("Invalid Dockerfile path")?
        .to_string_lossy()
        .to_string();
    tar_builder.append_path_with_name(dockerfile_path, "Dockerfile")?;
    tar_builder.finish()?;
    println!("Tar archive created at {}", tar_path);

    Ok("Dockerfile".to_string())
}

// service.rs
 let code = r#"print("Hello, World!")"#;
 let result = docker_manager::handle_request(language, code).await?;
    Ok(result)

Output received.

Server listening on [::1]:50051
Received request: ExecuteRequest { language: "python", code: "", stdin: "" }
Handling request for language: python
Tar archive created at ./docker/context.tar
Using dockerfile_name: 'Dockerfile'
Error during image build: Docker responded with status code 500: Cannot locate specified Dockerfile: Dockerfile
Error: Docker responded with status code 500: Cannot locate specified Dockerfile: Dockerfile

2 posts - 1 participant

Read full topic

🏷️ rust_feed