Example of using BuildImageFromDockerfileAsync
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.
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();
}
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 ---
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();
}
}
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);
}
}
Yes, why not to set a context as usual. Docker engine can copy the files without our interaction.
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
Dockerfileis in there, and if it's not namedDockerfilethen make sure to update the ImageBuildParameters with where it's at. - Watch for the wrong slashes on windows
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);
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);
Would be good if this was supported out of the box.
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?
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.