Docker.DotNet icon indicating copy to clipboard operation
Docker.DotNet copied to clipboard

Example of using BuildImageFromDockerfileAsync

Open andtii opened this issue 7 years ago • 11 comments

Im trying to use the BuildImageFromDockerfileAsync but i get an error response stating unexpected EOF. I cant find any examples of how to use it.

andtii avatar Apr 26 '18 10:04 andtii

I was not able to make it work. It seem you need to create an archive (tar.gz) file from your context. and send it in the stream argument.

https://forums.docker.com/t/how-to-build-an-image-using-docker-api/31807

using (var stream = new FileStream(@"context.tar.gz", FileMode.Open))
{
    var task = client.Images.BuildImageFromDockerfileAsync(stream, new ImageBuildParameters() { Dockerfile = "path/to/your dockerfile/inside your tar file", Tags = new string[] { "foo:latest" } });
    task.Wait();
}

edmBernard avatar Jul 04 '18 13:07 edmBernard

I try something like that but it still crash

DockerClient client = new DockerClientConfiguration(new Uri("unix:///var/run/docker.sock")).CreateClient();

var tarPath = System.IO.Path.GetTempPath() + "context.tar.gz";
if (File.Exists(tarPath))
{
    File.Delete(tarPath);
}
ZipFile.CreateFromDirectory(".", tarPath);
Console.WriteLine(tarPath);
foreach (var service in serviceImage.Keys)
{
    // var stream = File.OpenRead(serviceDockerfile[service]);
    using (var stream = new FileStream(tarPath, FileMode.Open))
    {
        var task = client.Images.BuildImageFromDockerfileAsync(stream, new ImageBuildParameters() { Dockerfile = serviceDockerfile[service], Tags = new string[] { serviceImage[service] } });
        task.Wait();
    }
}
Unhandled Exception: System.AggregateException: One or more errors occurred. (The requested failed, see inner exception for details.) ---> System.Net.Http.HttpRequestException: The requested failed, see inner exception for details. ---> System.Net.Http.HttpRequestException: Error while copying content to a stream.---> System.IO.IOException: Unable to transfer data on the transport connection: Broken pipe. ---> System.Net.Sockets.SocketException: Broken pipe
   --- End of inner exception stack trace ---

edmBernard avatar Jul 04 '18 16:07 edmBernard

I found my mistake Docker API accept a .tar file not a .tar.gz If you create your context with tar -zcvf /tmp/context.tar . and use the following code it works for me :

DockerClient client = new DockerClientConfiguration(new Uri("unix:///var/run/docker.sock")).CreateClient();

var tarPath = System.IO.Path.GetTempPath() + "context.tar";
Console.WriteLine(tarPath);
foreach (var service in serviceImage.Keys)
{
    using (var stream = new FileStream(tarPath, FileMode.Open))
    {
        var task = client.Images.BuildImageFromDockerfileAsync(stream, new ImageBuildParameters() { Dockerfile = serviceDockerfile[service], Tags = new string[] { serviceImage[service] } });
        task.Wait();
    }
}

edmBernard avatar Jul 05 '18 07:07 edmBernard

I could never make this work on Windows... This method complexity is too high and should be refactored to avoid passing in a TAR stream. I ended up using a process to accomplish it:

        public void Build(string targetName, string pathToDockerFile)
        {
            try
            {
                var process = new Process();
                var startInfo = new ProcessStartInfo
                {
                    WindowStyle = ProcessWindowStyle.Hidden,
                    FileName = "cmd.exe",
                    Arguments = $"/C docker build -t {targetName} -f {pathToDockerFile}/Dockerfile {pathToDockerFile}"
                };
                process.StartInfo = startInfo;
                process.Start();
                process.WaitForExit();
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex);
            }
        }

omniscient avatar Oct 10 '18 17:10 omniscient

Yes, why not to set a context as usual. Docker engine can copy the files without our interaction.

Serg046 avatar Feb 21 '19 09:02 Serg046

The command worked fine for me on Windows.

One thing I did stumble upon:

When creating the tar file (using SharpZipLib) if I create the tar entries from the filenames, the tar will be created with the wrong slashes (\ instead of / for linux). I had to swap those out myself if you're doing any COPY or ADD within folders.

Psuedo-code but here's all there is to build a docker image

var tarFilePath = CreateTarFile(); // contains Dockerfile, and then some content within folders, uncompressed
var stream = File.OpenRead(tarFilePath);

var response = await client.BuildImageFromDockerfileAsync(stream, new ImageBuildParameters 
{ 
   Tags = new List<string> { "my-image" }
});

I'm guessing the tricky part is probably creating the tar file.

  • This acts as your docker context, so make sure a Dockerfile is in there, and if it's not named Dockerfile then make sure to update the ImageBuildParameters with where it's at.
  • Watch for the wrong slashes on windows

KyleGobel avatar Aug 05 '19 06:08 KyleGobel

I can confirm it's working on Docker Desktop for Windows 10. This is the code I used to create the tarball. It's using SharpZipLib as kindly pointed out by @KyleGobel.

private static Stream CreateTarballForDockerfileDirectory(string directory)
{
    var tarball = new MemoryStream();
    var files = Directory.GetFiles(directory, "*.*", SearchOption.AllDirectories);

    using var archive = new TarOutputStream(tarball)
    {
        //Prevent the TarOutputStream from closing the underlying memory stream when done
        IsStreamOwner = false
    };

    foreach (var file in files)
    {
        //Replacing slashes as KyleGobel suggested and removing leading /
        string tarName = file.Substring(directory.Length).Replace('\\', '/').TrimStart('/');
		
        //Let's create the entry header
        var entry = TarEntry.CreateTarEntry(tarName);
        using var fileStream = File.OpenRead(file);
        entry.Size = fileStream.Length;
        archive.PutNextEntry(entry);

        //Now write the bytes of data
        byte[] localBuffer = new byte[32 * 1024];
        while (true)
        {   
            int numRead = fileStream.Read(localBuffer, 0, localBuffer.Length);
            if (numRead <= 0)
                break;

            archive.Write(localBuffer, 0, numRead);
        }
		
        //Nothing more to do with this entry
        archive.CloseEntry();
    }
    archive.Close();

    //Reset the stream and return it, so it can be used by the caller
    tarball.Position = 0;
    return tarball;
}

Remeber to dispose the tarball Stream after you've sent it via BuildImageFromDockerfileAsync. Here's a usage example:

var imageBuildParameters = new ImageBuildParameters
{
    //...
};
//C# 8.0 using syntax
using var tarball = CreateTarballForDockerfileDirectory(workingDirectory);
using var dockerClient = new DockerClientConfiguration(new Uri("npipe://./pipe/docker_engine")).CreateClient();
using var responseStream = await dockerClient.Images.BuildImageFromDockerfileAsync(tarball, imageBuildParameters);

MorenoGentili avatar Oct 29 '19 08:10 MorenoGentili

The command above was almost working the same has the command line, the mode file must be set for each file in the tarball archive, like this:

entry.Size = fileStream.Length;
entry.TarHeader.Mode = Convert.ToInt32("100755", 8); //chmod 755
archive.PutNextEntry(entry);

shelly-rng avatar Jan 10 '20 14:01 shelly-rng

Would be good if this was supported out of the box.

davidguidali avatar Jul 15 '21 11:07 davidguidali

wow, I'm in 2024 and still no real solution for this. @MorenoGentili awesome answer :) did you ever extend this to support the dockerignore file?

EugeneKrapivin avatar Dec 12 '24 08:12 EugeneKrapivin

In Testcontainers for .NET, we use the following implementation to build an image. Testcontainers contains many features like this that should ideally be part of the Docker client. That's why I recently forked the project. I would like to move more features from Testcontainers to the client so that more developers can benefit from them. If someone wants to contribute and work on such a feature (or migration), I would be happy to review it and include it in the forked version.

HofmeisterAn avatar Dec 12 '24 08:12 HofmeisterAn