FluentDocker

FluentDocker

简化Docker操作的流畅.NET API库

FluentDocker是一个为.NET开发者提供的Docker操作库,通过流畅的API简化了Docker和Docker Compose的使用。该库支持跨平台操作,包括容器管理、端口映射和环境变量设置等功能。FluentDocker采用三层架构,包括Docker二进制交互、服务层和流畅API,便于开发者构建和管理Docker服务。此外,它还提供了等待特定端口或进程的功能,以及文件系统操作,有效提高了Docker相关开发的效率。

FluentDockerDocker容器化.NETAPIGithub开源项目

Quality Gate Status Build status

PackageNuGet
FluentDockerNuGet
Microsoft TestNuGet
XUnit TestNuGet

FluentDocker

This library enables docker and docker-compose interactions usinga Fluent API. It is supported on Linux, Windows and Mac. It also has support for the legazy docker-machine interactions.

Sample Fluent API usage

using ( var container = new Builder().UseContainer() .UseImage("kiasaki/alpine-postgres") .ExposePort(5432) .WithEnvironment("POSTGRES_PASSWORD=mysecretpassword") .WaitForPort("5432/tcp", 30000 /*30s*/) .Build() .Start()) { var config = container.GetConfiguration(true); Assert.AreEqual(ServiceRunningState.Running, config.State.ToServiceState()); }

This fires up a postgres and waits for it to be ready. To use compose, just do it like this:

:bulb: NOTE: Use the AssumeComposeVersion(ComposeVersion.V2) to use the V2 behaviour, default is still V1 (to be changed to default to V2 later this year)

var file = Path.Combine(Directory.GetCurrentDirectory(), (TemplateString) "Resources/ComposeTests/WordPress/docker-compose.yml"); // @formatter:off using (var svc = new Builder() .UseContainer() .UseCompose() .FromFile(file) .RemoveOrphans() .WaitForHttp("wordpress", "http://localhost:8000/wp-admin/install.php") .Build().Start()) // @formatter:on { // We now have a running WordPress with a MySql database var installPage = await "http://localhost:8000/wp-admin/install.php".Wget(); Assert.IsTrue(installPage.IndexOf("https://wordpress.org/", StringComparison.Ordinal) != -1); Assert.AreEqual(1, svc.Hosts.Count); // The host used by compose Assert.AreEqual(2, svc.Containers.Count); // We can access each individual container Assert.AreEqual(2, svc.Images.Count); // And the images used. }

:bulb Note for Linux Users: Docker requires sudo by default and the library by default expects that executing user do not need to do sudo in order to talk to the docker daemon. More description can be found in the Talking to Docker Daemon chapter.

The fluent API builds up one or more services. Each service may be composite or singular. Therefore it is possible to e.g. fire up several docker-compose based services and manage each of them as a single service or dig in and use all underlying services on each docker-compose service. It is also possible to use services directly e.g.

var file = Path.Combine(Directory.GetCurrentDirectory(), (TemplateString) "Resources/ComposeTests/WordPress/docker-compose.yml"); using (var svc = new DockerComposeCompositeService(DockerHost, new DockerComposeConfig { ComposeFilePath = new List<string> { file }, ForceRecreate = true, RemoveOrphans = true, StopOnDispose = true })) { svc.Start(); // We now have a running WordPress with a MySql database var installPage = await $"http://localhost:8000/wp-admin/install.php".Wget(); Assert.IsTrue(installPage.IndexOf("https://wordpress.org/", StringComparison.Ordinal) != -1); }

The above example creates a docker-compose service from a single compose file. When the service is disposed all underlying services is automatically stopped.

The library is supported by .NET full 4.51 framework and higher, .NET standard 1.6, 2.0. It is divided into three thin layers, each layer is accessible:

  1. Docker Binaries interactions - Static commands and docker environment
  2. Services - thin service layer to manage machines, containers etc.
  3. Fluent API - API to build/discover services to be used

The Majority of the service methods are extension methods and not hardwired into the service itself, making them lightweight and customizable. Since everything is accessible it is e.g. easy to add extensions method for a service that uses the layer 1 commands to provide functionality.

Contribution

I do welcome contribution, though there is no contribution guideline as of yet, make sure to adhere to .editorconfig when doing the Pull Requests. Otherwise the build will fail. I'll update with a real guideline sooner or later this year.

Basic Usage of Commands (Layer 1)

All commands needs a DockerUri to work with. It is the Uri to the docker daemon, either locally or remote. It can be discoverable or hardcoded. Discovery of local DockerUri can be done by

var hosts = new Hosts().Discover(); var _docker = hosts.FirstOrDefault(x => x.IsNative) ?? hosts.FirstOrDefault(x => x.Name == "default");

The example snipped will check for native, or docker beta "native" hosts, if not choose the docker-machine "default" as host. If you're using docker-machine and no machine exists or is not started it is easy to create / start a docker-machine by e.g. "test-machine".Create(1024,20000000,1). This will create a docker machine named "test-machine" with 1GB of RAM, 20GB Disk, and use one CPU.

It is now possible to use the Uri to communicate using the commands. For example to get the version of client and server docker binaries:

var result = _docker.Host.Version(_docker.Certificates); Debug.WriteLine(result.Data); // Will Print the Client and Server Version and API Versions respectively.

All commands return a CommandResponse<T> such that it is possible to check success factor by response.Success. If any data associated with the command it is returned in the response.Data property.

Then it is simple as below to start and stop include delete a container using the commands. Below starts a container and do a PS on it and then deletes it.

var id = _docker.Host.Run("nginx:latest", null, _docker.Certificates).Data; var ps = _docker.Host.Ps(null, _docker.Certificates).Data; _docker.Host.RemoveContainer(id, true, true, null, _docker.Certificates);

When running on windows, one can choose to run linux or windows container. Use the LinuxDaemon or WindowsDaemon to control which daemon to talk to.

_docker.LinuxDaemon(); // ensures that it will talk to linux daemon, if windows daemon it will switch

Some commands returns a stream of data when e.g. events or logs is wanted using a continuous stream. Streams can be used in background tasks and support CancellationToken. Below example tails a log.

using (var logs = _docker.Host.Logs(id, _docker.Certificates)) { while (!logs.IsFinished) { var line = logs.TryRead(5000); // Do a read with timeout if (null == line) { break; } Debug.WriteLine(line); } }

Utility methods exists for commands. They come in different flaviours such as networking etc. For example when reading a log to the end:

using (var logs = _docker.Host.Logs(id, _docker.Certificates)) { foreach (var line in logs.ReadToEnd()) { Debug.WriteLine(line); } }

Using Fluent API

The highest layer of this library is the fluent API where you can define and control machines, images, and containers. For example to setup a load balancer with two nodejs servers reading from a redis server can look like this (node image is custom built if not found in the repository).

var fullPath = (TemplateString) @"${TEMP}/fluentdockertest/${RND}"; var nginx = Path.Combine(fullPath, "nginx.conf"); Directory.CreateDirectory(fullPath); typeof(NsResolver).ResourceExtract(fullPath, "index.js"); using (var services = new Builder() // Define custom node image to be used .DefineImage("mariotoffia/nodetest").ReuseIfAlreadyExists() .From("ubuntu") .Maintainer("Mario Toffia <mario.toffia@xyz.com>") .Run("apt-get update &&", "apt-get -y install curl &&", "curl -sL https://deb.nodesource.com/setup | sudo bash - &&", "apt-get -y install python build-essential nodejs") .Run("npm install -g nodemon") .Add("emb:Ductus.FluentDockerTest/Ductus.FluentDockerTest.MultiContainerTestFiles/package.txt", "/tmp/package.json") .Run("cd /tmp && npm install") .Run("mkdir -p /src && cp -a /tmp/node_modules /src/") .UseWorkDir("/src") .Add("index.js", "/src") .ExposePorts(8080) .Command("nodemon", "/src/index.js").Builder() // Redis Db Backend .UseContainer().WithName("redis").UseImage("redis").Builder() // Node server 1 & 2 .UseContainer().WithName("node1").UseImage("mariotoffia/nodetest").Link("redis").Builder() .UseContainer().WithName("node2").UseImage("mariotoffia/nodetest").Link("redis").Builder() // Nginx as load balancer .UseContainer().WithName("nginx").UseImage("nginx").Link("node1", "node2") .CopyOnStart(nginx, "/etc/nginx/nginx.conf") .ExposePort(80).Builder() .Build().Start()) { Assert.AreEqual(4, services.Containers.Count); var ep = services.Containers.First(x => x.Name == "nginx").ToHostExposedEndpoint("80/tcp"); Assert.IsNotNull(ep); var round1 = $"http://{ep.Address}:{ep.Port}".Wget(); Assert.AreEqual("This page has been viewed 1 times!", round1); var round2 = $"http://{ep.Address}:{ep.Port}".Wget(); Assert.AreEqual("This page has been viewed 2 times!", round2); }

The above example defines a Dockerfile, builds it, for the node image. It then uses vanilla redis and nginx. If you just want to use an existing Dockerfile it can be done like this.

using (var services = new Builder() .DefineImage("mariotoffia/nodetest").ReuseIfAlreadyExists() .FromFile("/tmp/Dockerfile") .Build().Start()) { // Container either build to reused if found in registry and started here. }

The fluent API supports from defining a docker-machine to a set of docker instances. It has built-in support for e.g. waiting for a specific port or a process within the container before Build() completes and thus can be safely be used within a using statement. If specific management on wait timeouts etc. you can always build and start the container and use extension methods to do the waiting on the container itself.

To create a container just omit the start. For example:

using ( var container = new Builder().UseContainer() .UseImage("kiasaki/alpine-postgres") .WithEnvironment("POSTGRES_PASSWORD=mysecretpassword") .Build()) { Assert.AreEqual(ServiceRunningState.Stopped, container.State); }

This example creates a container with postgres, configure one environment variable. Within the using statement it is possible to start the IContainerService. Thus each built container is wrapped in a IContainerService. It is also possible to use the IHostService.GetContainers(...) to obtain the created, running, and exited containers. From the IHostService it is also possible to get all the images in the local repository to create containers from.

Whe you want to run a single container do use the fluent or container service start method. For example:

using ( var container = new Builder().UseContainer() .UseImage("kiasaki/alpine-postgres") .WithEnvironment("POSTGRES_PASSWORD=mysecretpassword") .Build() .Start()) { var config = container.GetConfiguration(); Assert.AreEqual(ServiceRunningState.Running, container.State); Assert.IsTrue(config.Config.Env.Any(x => x == "POSTGRES_PASSWORD=mysecretpassword")); }

By default the container is stopped and deleted when the Dispose method is run, in order to keep the container in archve, use the KeepContainer() on the fluent API. When Dispose() is invoked it will be stopped but not deleted. It is also possible to keep it running after dispose as well.

Working with ports

It is possible to expose ports both explicit or randomly. Either way it is possible to resolve the IP (in case of machine) and the port (in case of random port) to use in code. For example:

using ( var container = new Builder().UseContainer() .UseImage("kiasaki/alpine-postgres") .ExposePort(40001, 5432) .WithEnvironment("POSTGRES_PASSWORD=mysecretpassword") .Build() .Start()) { var endpoint = container.ToHostExposedEndpoint("5432/tcp"); Assert.AreEqual(40001, endpoint.Port); }

Here we map the container port 5432 to host port 40001 explicitly. Note the use of container.ToHostExposedEndpoint(...). This is to always resolve to a working ip and port to communicate with the docker container. It is also possible to map a random port, i.e. let Docker choose a available port. For example:

using ( var container = new Builder().UseContainer() .UseImage("kiasaki/alpine-postgres") .ExposePort(5432) .WithEnvironment("POSTGRES_PASSWORD=mysecretpassword") .Build() .Start()) { var endpoint = container.ToHostExposedEndpoint("5432/tcp"); Assert.AreNotEqual(0, endpoint.Port); }

The only difference here is that only one argument is used when ExposePort(...) was used to configure the container. The same usage applies otherwise and thus is transparent for the code.

In order to know when a certain service is up and running before starting to e.g. connect to it. It is possible to wait for a specific port to be open. For example:

using ( var container = new Builder().UseContainer() .UseImage("kiasaki/alpine-postgres") .ExposePort(5432) .WithEnvironment("POSTGRES_PASSWORD=mysecretpassword") .WaitForPort("5432/tcp", 30000 /*30s*/) .Build() .Start()) { var config = container.GetConfiguration(true); Assert.AreEqual(ServiceRunningState.Running, config.State.ToServiceState()); }

In the above example we wait for the container port 5432 to be opened within 30 seconds. If it fails, it will throw an exception and thus the container will be disposed and removed (since we dont have any keep container etc. configuration).

using ( var container = new Builder().UseContainer() .UseImage("kiasaki/alpine-postgres") .ExposePort(5432) .WithEnvironment("POSTGRES_PASSWORD=mysecretpassword") .WaitForPort("5432/tcp", 30000 /*30s*/, "127.0.0.1") .Build() .Start()) { var config = container.GetConfiguration(true); Assert.AreEqual(ServiceRunningState.Running, config.State.ToServiceState()); }

Sometimes it is not possible to directly reach the container, by local ip and port, instead e.g. the container has an exposed port on the loopback interface (127.0.0.1) and that is the only way of reaching the container from the program. The above example forces the address to be 127.0.0.1 but still resolves the host port. By default, FluentDocker uses the network inspect on the container to determine the network configuration.

Sometime it is not sufficient to just wait for a port. Sometimes a container process is much more vital to wait for. Therefore a wait for process method exist in the fluent API as well as an extension method on the container object. For example:

using ( var container = new Builder().UseContainer() .UseImage("kiasaki/alpine-postgres") .ExposePort(5432) .WithEnvironment("POSTGRES_PASSWORD=mysecretpassword") .WaitForProcess("postgres", 30000 /*30s*/) .Build() .Start()) { var config = container.GetConfiguration(true); Assert.AreEqual(ServiceRunningState.Running, config.State.ToServiceState()); }

In the above example Build() will return control when the process "postgres" have been started within the container.

Filesystem & Files

In order to make use

编辑推荐精选

Pixmax

Pixmax

一站式AI短剧创作平台

Pixmax专注打造下一代“ AI 视觉创作引擎”,整合行业顶尖 AI 大模型、工工业级精准控制及企业级协同管理功能,是全方位的 AI 内容创作平台。

豆包

豆包

字节跳动旗下 AI 智能助手

字节跳动旗下 AI 智能助手

GPT Plus|Pro充值

GPT Plus|Pro充值

GPT充值

支持 ChatGPT Plus / Pro 充值服务,支付便捷,自动发货,售后可查。

GPT Image 2中文站

GPT Image 2中文站

AI 图片生成平台

GPT Image 2 是面向用户的 AI 图片生成平台,支持文生图、图生图及多模型创意工作流。

Vecbase

Vecbase

你的AI Agent团队

Vecbase 是专为 AI 团队打造的智能工作空间,将数据管理、模型协作与知识沉淀整合于一处。算法、产品与业务在同一平台无缝协同,让从数据到 AI 应用的落地更快一步。

音述AI

音述AI

全球首个AI音乐社区

音述AI是全球首个AI音乐社区,致力让每个人都能用音乐表达自我。音述AI提供零门槛AI创作工具,独创GETI法则帮助用户精准定义音乐风格,AI润色功能支持自动优化作品质感。音述AI支持交流讨论、二次创作与价值变现。针对中文用户的语言习惯与文化背景进行专门优化,支持国风融合、C-pop等本土音乐标签,让技术更好地承载人文表达。

QoderWork

QoderWork

阿里Qoder团队推出的桌面端AI智能体

QoderWork 是阿里推出的本地优先桌面 AI 智能体,适配 macOS14+/Windows10+,以自然语言交互实现文件管理、数据分析、AI 视觉生成、浏览器自动化等办公任务,自主拆解执行复杂工作流,数据本地运行零上传,技能市场可无限扩展,是高效的 Agentic 生产力办公助手。

lynote.ai

lynote.ai

一站式搞定所有学习需求

不再被海量信息淹没,开始真正理解知识。Lynote 可摘要 YouTube 视频、PDF、文章等内容。即时创建笔记,检测 AI 内容并下载资料,将您的学习效率提升 10 倍。

AniShort

AniShort

为AI短剧协作而生

专为AI短剧协作而生的AniShort正式发布,深度重构AI短剧全流程生产模式,整合创意策划、制作执行、实时协作、在线审片、资产复用等全链路功能,独创无限画布、双轨并行工业化工作流与Ani智能体助手,集成多款主流AI大模型,破解素材零散、版本混乱、沟通低效等行业痛点,助力3人团队效率提升800%,打造标准化、可追溯的AI短剧量产体系,是AI短剧团队协同创作、提升制作效率的核心工具。

seedancetwo2.0

seedancetwo2.0

能听懂你表达的视频模型

Seedance two是基于seedance2.0的中国大模型,支持图像、视频、音频、文本四种模态输入,表达方式更丰富,生成也更可控。

下拉加载更多