FluentDocker

2025-12-07 0 122

Package NuGet Downloads
FluentDocker
Microsoft Test
XUnit Test

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.

Breaking changes. It will not adhere to AssumeComposeVersion instead it will autodetect if it supports docker compose subcommand and automatically set it to V2. If not supported, it will use docker-compose and this is V1.

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:

      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://l*ocalh*o*st:8000/wp-admin/install.php\") 
                        .Build().Start())
        // @formatter:on
      {
        // We now have a running WordPress with a MySql database        
        var installPage = await \"http://l*ocalh*o*st:8000/wp-admin/install.php\".Wget();

        Assert.IsTrue(installPage.IndexOf(\"https://**wo*rdpress.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://l*ocalh*o*st:8000/wp-admin/install.php\".Wget();
        
        Assert.IsTrue(installPage.IndexOf(\"https://**wo*rdpress.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 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.*no*deso*urce.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:

下载源码

通过命令行克隆项目:

git clone https://github.com/mariotoffia/FluentDocker.git

收藏 (0) 打赏

感谢您的支持,我会继续努力的!

打开微信/支付宝扫一扫,即可进行扫码打赏哦,分享从这里开始,精彩与您同在
点赞 (0)

申明:本文由第三方发布,内容仅代表作者观点,与本网站无关。对本文以及其中全部或者部分内容的真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。本网发布或转载文章出于传递更多信息之目的,并不意味着赞同其观点或证实其描述,也不代表本网对其真实性负责。

左子网 开发教程 FluentDocker https://www.zuozi.net/31556.html

sensenet
上一篇: sensenet
web mode
下一篇: web mode
常见问题
  • 1、自动:拍下后,点击(下载)链接即可下载;2、手动:拍下后,联系卖家发放即可或者联系官方找开发者发货。
查看详情
  • 1、源码默认交易周期:手动发货商品为1-3天,并且用户付款金额将会进入平台担保直到交易完成或者3-7天即可发放,如遇纠纷无限期延长收款金额直至纠纷解决或者退款!;
查看详情
  • 1、描述:源码描述(含标题)与实际源码不一致的(例:货不对板); 2、演示:有演示站时,与实际源码小于95%一致的(但描述中有”不保证完全一样、有变化的可能性”类似显著声明的除外); 3、发货:不发货可无理由退款; 4、安装:免费提供安装服务的源码但卖家不履行的; 5、收费:价格虚标,额外收取其他费用的(但描述中有显著声明或双方交易前有商定的除外); 6、其他:如质量方面的硬性常规问题BUG等。 注:经核实符合上述任一,均支持退款,但卖家予以积极解决问题则除外。
查看详情
  • 1、左子会对双方交易的过程及交易商品的快照进行永久存档,以确保交易的真实、有效、安全! 2、左子无法对如“永久包更新”、“永久技术支持”等类似交易之后的商家承诺做担保,请买家自行鉴别; 3、在源码同时有网站演示与图片演示,且站演与图演不一致时,默认按图演作为纠纷评判依据(特别声明或有商定除外); 4、在没有”无任何正当退款依据”的前提下,商品写有”一旦售出,概不支持退款”等类似的声明,视为无效声明; 5、在未拍下前,双方在QQ上所商定的交易内容,亦可成为纠纷评判依据(商定与描述冲突时,商定为准); 6、因聊天记录可作为纠纷评判依据,故双方联系时,只与对方在左子上所留的QQ、手机号沟通,以防对方不承认自我承诺。 7、虽然交易产生纠纷的几率很小,但一定要保留如聊天记录、手机短信等这样的重要信息,以防产生纠纷时便于左子介入快速处理。
查看详情

相关文章

猜你喜欢
发表评论
暂无评论
官方客服团队

为您解决烦忧 - 24小时在线 专业服务