{"id":15040597,"url":"https://github.com/mariotoffia/fluentdocker","last_synced_at":"2025-05-14T04:07:51.750Z","repository":{"id":37550381,"uuid":"54591411","full_name":"mariotoffia/FluentDocker","owner":"mariotoffia","description":"Use docker, docker-compose local and remote in tests and your .NET core/full framework apps via a FluentAPI","archived":false,"fork":false,"pushed_at":"2024-10-07T07:37:07.000Z","size":2824,"stargazers_count":1354,"open_issues_count":58,"forks_count":98,"subscribers_count":17,"default_branch":"master","last_synced_at":"2025-05-11T00:42:12.468Z","etag":null,"topics":["builder","compose","docker","docker-container","docker-machine","docker-stack","dotnetcore","fluent","linux","macos","net","test","windows","wsl2"],"latest_commit_sha":null,"homepage":"","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mariotoffia.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null},"funding":{"github":["mariotoffia"]}},"created_at":"2016-03-23T20:36:52.000Z","updated_at":"2025-05-03T04:24:10.000Z","dependencies_parsed_at":"2024-01-28T20:06:23.566Z","dependency_job_id":"803135ce-25e8-4288-9ae7-d19ec3a65b6e","html_url":"https://github.com/mariotoffia/FluentDocker","commit_stats":{"total_commits":539,"total_committers":34,"mean_commits":"15.852941176470589","dds":0.3246753246753247,"last_synced_commit":"83ec85f16368a0a8ac2031f4a9f88cd4fd35812e"},"previous_names":[],"tags_count":55,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mariotoffia%2FFluentDocker","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mariotoffia%2FFluentDocker/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mariotoffia%2FFluentDocker/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mariotoffia%2FFluentDocker/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mariotoffia","download_url":"https://codeload.github.com/mariotoffia/FluentDocker/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254069625,"owners_count":22009558,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["builder","compose","docker","docker-container","docker-machine","docker-stack","dotnetcore","fluent","linux","macos","net","test","windows","wsl2"],"created_at":"2024-09-24T20:44:47.472Z","updated_at":"2025-05-14T04:07:51.668Z","avatar_url":"https://github.com/mariotoffia.png","language":"C#","readme":"[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=mariotoffia_FluentDocker\u0026metric=alert_status)](https://sonarcloud.io/dashboard?id=mariotoffia_FluentDocker)\n[![Build status](https://ci.appveyor.com/api/projects/status/kqqrkcv8wp3e9my6/branch/master?svg=true)](https://ci.appveyor.com/project/mariotoffia/fluentdocker) \n\n| Package        | NuGet          |\n| ---------------|:--------------:|\n| FluentDocker   |[![NuGet](https://img.shields.io/nuget/v/Ductus.FluentDocker.svg)](https://www.nuget.org/packages/Ductus.FluentDocker) |\n| Microsoft Test | [![NuGet](https://img.shields.io/nuget/v/Ductus.FluentDocker.MsTest.svg)](https://www.nuget.org/packages/Ductus.FluentDocker.MsTest) |\n| XUnit Test     | [![NuGet](https://img.shields.io/nuget/v/Ductus.FluentDocker.XUnit.svg)](https://www.nuget.org/packages/Ductus.FluentDocker.XUnit) |\n\n# FluentDocker\n\nThis 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.\n\n**Sample Fluent API usage**\n```cs\n      using (\n        var container =\n          new Builder().UseContainer()\n            .UseImage(\"kiasaki/alpine-postgres\")\n            .ExposePort(5432)\n            .WithEnvironment(\"POSTGRES_PASSWORD=mysecretpassword\")\n            .WaitForPort(\"5432/tcp\", 30000 /*30s*/)\n            .Build()\n            .Start())\n      {\n        var config = container.GetConfiguration(true);\n        Assert.AreEqual(ServiceRunningState.Running, config.State.ToServiceState());\n      }\n```\nThis fires up a postgres and waits for it to be ready. To use compose, just do it like this:\n\n: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)**\n\n```cs\n      var file = Path.Combine(Directory.GetCurrentDirectory(),\n        (TemplateString) \"Resources/ComposeTests/WordPress/docker-compose.yml\");\n\n      // @formatter:off\n      using (var svc = new Builder()\n                        .UseContainer()\n                        .UseCompose()\n                        .FromFile(file)\n                        .RemoveOrphans()\n                        .WaitForHttp(\"wordpress\", \"http://localhost:8000/wp-admin/install.php\") \n                        .Build().Start())\n        // @formatter:on\n      {\n        // We now have a running WordPress with a MySql database        \n        var installPage = await \"http://localhost:8000/wp-admin/install.php\".Wget();\n\n        Assert.IsTrue(installPage.IndexOf(\"https://wordpress.org/\", StringComparison.Ordinal) != -1);\n        Assert.AreEqual(1, svc.Hosts.Count); // The host used by compose\n        Assert.AreEqual(2, svc.Containers.Count); // We can access each individual container\n        Assert.AreEqual(2, svc.Images.Count); // And the images used.\n      }\n```\n\n:bulb **Note for Linux Users:** Docker requires _sudo_ by default and the library by default expects that executing user do not\nneed to do _sudo_ in order to talk to the docker daemon. More description can be found in the _Talking to Docker Daemon_ chapter.\n\nThe fluent _API_ builds up one or more services. Each service may be composite or singular. Therefore it is possible\nto e.g. fire up several _docker-compose_ based services and manage each of them as a single service or dig in and use\nall underlying services on each _docker-compose_ service. It is also possible to use services directly e.g.\n```cs\n      var file = Path.Combine(Directory.GetCurrentDirectory(),\n        (TemplateString) \"Resources/ComposeTests/WordPress/docker-compose.yml\");\n\n      using (var svc = new DockerComposeCompositeService(DockerHost, new DockerComposeConfig\n      {\n        ComposeFilePath = new List\u003cstring\u003e { file }, ForceRecreate = true, RemoveOrphans = true,\n        StopOnDispose = true\n      }))\n      {\n        svc.Start();\n        \n        // We now have a running WordPress with a MySql database\n        var installPage = await $\"http://localhost:8000/wp-admin/install.php\".Wget();\n        \n        Assert.IsTrue(installPage.IndexOf(\"https://wordpress.org/\", StringComparison.Ordinal) != -1);\n      }\n```\nThe above example creates a _docker-compose_ service from a single compose file. When the service is disposed all\nunderlying services is automatically stopped.\n\nThe library is supported by .NET full 4.51 framework and higher, .NET standard 1.6, 2.0. It is divided into \nthree thin layers, each layer is accessible:\n\n1. Docker Binaries interactions - Static commands and docker environment\n2. Services - thin service layer to manage machines, containers etc.\n3. Fluent API - API to build/discover services to be used\n\nThe 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. \n\n## Contribution\nI do welcome contribution, though there is no contribution guideline as of yet, make sure to adhere to _.editorconfig_ when doing the Pull Requests.\nOtherwise the build will fail. I'll update with a **real** guideline sooner or later this year.\n\n## Basic Usage of Commands (Layer 1)\nAll 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 \n```cs\n     var hosts = new Hosts().Discover();\n     var _docker = hosts.FirstOrDefault(x =\u003e x.IsNative) ?? hosts.FirstOrDefault(x =\u003e x.Name == \"default\");\n```\nThe 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.\n\nIt is now possible to use the Uri to communicate using the commands. For example to get the version of client and server docker binaries:\n```cs\n     var result = _docker.Host.Version(_docker.Certificates);\n     Debug.WriteLine(result.Data); // Will Print the Client and Server Version and API Versions respectively.\n```\nAll commands return a CommandResponse\u003cT\u003e 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.\n\nThen 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.\n```cs\n     var id = _docker.Host.Run(\"nginx:latest\", null, _docker.Certificates).Data;\n     var ps = _docker.Host.Ps(null, _docker.Certificates).Data;\n     \n     _docker.Host.RemoveContainer(id, true, true, null, _docker.Certificates);\n```\nWhen running on windows, one can choose to run linux or windows container. Use the ```LinuxDaemon``` or ```WindowsDaemon``` to control which daemon to talk to.\n```cs\n     _docker.LinuxDaemon(); // ensures that it will talk to linux daemon, if windows daemon it will switch\n```\n\nSome 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.\n```cs\n     using (var logs = _docker.Host.Logs(id, _docker.Certificates))\n     {\n          while (!logs.IsFinished)\n          {\n               var line = logs.TryRead(5000); // Do a read with timeout\n               if (null == line)\n               {\n                    break;\n               }\n\n               Debug.WriteLine(line);\n          }\n     }\n```\n\nUtility methods exists for commands. They come in different flaviours such as networking etc. For example when reading a log to the end:\n```cs\n     using (var logs = _docker.Host.Logs(id, _docker.Certificates))\n     {\n          foreach (var line in logs.ReadToEnd())\n          {\n            Debug.WriteLine(line);\n          }\n     }\n```\n\n## Using Fluent API\nThe 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).\n\n```cs\n     var fullPath = (TemplateString) @\"${TEMP}/fluentdockertest/${RND}\";\n      var nginx = Path.Combine(fullPath, \"nginx.conf\");\n\n      Directory.CreateDirectory(fullPath);\n      typeof(NsResolver).ResourceExtract(fullPath, \"index.js\");\n\n        using (var services = new Builder()\n\n          // Define custom node image to be used\n          .DefineImage(\"mariotoffia/nodetest\").ReuseIfAlreadyExists()\n          .From(\"ubuntu\")\n          .Maintainer(\"Mario Toffia \u003cmario.toffia@xyz.com\u003e\")\n          .Run(\"apt-get update \u0026\u0026\",\n            \"apt-get -y install curl \u0026\u0026\",\n            \"curl -sL https://deb.nodesource.com/setup | sudo bash - \u0026\u0026\",\n            \"apt-get -y install python build-essential nodejs\")\n          .Run(\"npm install -g nodemon\")\n          .Add(\"emb:Ductus.FluentDockerTest/Ductus.FluentDockerTest.MultiContainerTestFiles/package.txt\",\n            \"/tmp/package.json\")\n          .Run(\"cd /tmp \u0026\u0026 npm install\")\n          .Run(\"mkdir -p /src \u0026\u0026 cp -a /tmp/node_modules /src/\")\n          .UseWorkDir(\"/src\")\n          .Add(\"index.js\", \"/src\")\n          .ExposePorts(8080)\n          .Command(\"nodemon\", \"/src/index.js\").Builder()\n\n          // Redis Db Backend\n          .UseContainer().WithName(\"redis\").UseImage(\"redis\").Builder()\n\n          // Node server 1 \u0026 2\n          .UseContainer().WithName(\"node1\").UseImage(\"mariotoffia/nodetest\").Link(\"redis\").Builder()\n          .UseContainer().WithName(\"node2\").UseImage(\"mariotoffia/nodetest\").Link(\"redis\").Builder()\n\n          // Nginx as load balancer\n          .UseContainer().WithName(\"nginx\").UseImage(\"nginx\").Link(\"node1\", \"node2\")\n          .CopyOnStart(nginx, \"/etc/nginx/nginx.conf\")\n          .ExposePort(80).Builder()\n          .Build().Start())\n        {\n          Assert.AreEqual(4, services.Containers.Count);\n\n          var ep = services.Containers.First(x =\u003e x.Name == \"nginx\").ToHostExposedEndpoint(\"80/tcp\");\n          Assert.IsNotNull(ep);\n\n          var round1 = $\"http://{ep.Address}:{ep.Port}\".Wget();\n          Assert.AreEqual(\"This page has been viewed 1 times!\", round1);\n\n          var round2 = $\"http://{ep.Address}:{ep.Port}\".Wget();\n          Assert.AreEqual(\"This page has been viewed 2 times!\", round2);\n        }\n```\n\nThe above example defines a _Dockerfile_, builds it, for the node image. It then uses vanilla redis and nginx. \nIf you just want to use an existing _Dockerfile_ it can be done like this.\n\n```cs\n        using (var services = new Builder()\n\n          .DefineImage(\"mariotoffia/nodetest\").ReuseIfAlreadyExists()\n          .FromFile(\"/tmp/Dockerfile\")\n          .Build().Start())\n        {\n         // Container either build to reused if found in registry and started here.\n        }\n```\n  \nThe fluent API supports from defining a docker-machine to a set of docker instances. It has built-in support for e.g. \nwaiting for a specific port or a process within the container before ```Build()``` completes and thus can be safely \nbe used within a using statement. If specific management on wait timeouts etc. you can always build and start the \ncontainer and use extension methods to do the waiting on the container itself.\n\nTo create a container just omit the start. For example:\n```cs\nusing (\n        var container =\n          new Builder().UseContainer()\n            .UseImage(\"kiasaki/alpine-postgres\")\n            .WithEnvironment(\"POSTGRES_PASSWORD=mysecretpassword\")\n            .Build())\n      {\n        Assert.AreEqual(ServiceRunningState.Stopped, container.State);\n      }\n```\nThis 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.\n\nWhe you want to run a single container do use the fluent or container service start method. For example:\n```cs\n      using (\n        var container =\n          new Builder().UseContainer()\n            .UseImage(\"kiasaki/alpine-postgres\")\n            .WithEnvironment(\"POSTGRES_PASSWORD=mysecretpassword\")\n            .Build()\n            .Start())\n      {\n        var config = container.GetConfiguration();\n\n        Assert.AreEqual(ServiceRunningState.Running, container.State);\n        Assert.IsTrue(config.Config.Env.Any(x =\u003e x == \"POSTGRES_PASSWORD=mysecretpassword\"));\n      }\n```\n\nBy 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.\n\n### Working with ports\nIt 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:\n\n```cs\n      using (\n        var container =\n          new Builder().UseContainer()\n            .UseImage(\"kiasaki/alpine-postgres\")\n            .ExposePort(40001, 5432)\n            .WithEnvironment(\"POSTGRES_PASSWORD=mysecretpassword\")\n            .Build()\n            .Start())\n      {\n        var endpoint = container.ToHostExposedEndpoint(\"5432/tcp\");\n        Assert.AreEqual(40001, endpoint.Port);\n      }\n```\n\nHere 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:\n\n```cs\n      using (\n        var container =\n          new Builder().UseContainer()\n            .UseImage(\"kiasaki/alpine-postgres\")\n            .ExposePort(5432)\n            .WithEnvironment(\"POSTGRES_PASSWORD=mysecretpassword\")\n            .Build()\n            .Start())\n      {\n        var endpoint = container.ToHostExposedEndpoint(\"5432/tcp\");\n        Assert.AreNotEqual(0, endpoint.Port);\n      }\n```\nThe 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.\n\nIn 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:\n```cs\n      using (\n        var container =\n          new Builder().UseContainer()\n            .UseImage(\"kiasaki/alpine-postgres\")\n            .ExposePort(5432)\n            .WithEnvironment(\"POSTGRES_PASSWORD=mysecretpassword\")\n            .WaitForPort(\"5432/tcp\", 30000 /*30s*/)\n            .Build()\n            .Start())\n      {\n        var config = container.GetConfiguration(true);\n        Assert.AreEqual(ServiceRunningState.Running, config.State.ToServiceState());\n      }\n```\nIn 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).\n\n\n```cs\n      using (\n        var container =\n          new Builder().UseContainer()\n            .UseImage(\"kiasaki/alpine-postgres\")\n            .ExposePort(5432)\n            .WithEnvironment(\"POSTGRES_PASSWORD=mysecretpassword\")\n            .WaitForPort(\"5432/tcp\", 30000 /*30s*/, \"127.0.0.1\")\n            .Build()\n            .Start())\n      {\n        var config = container.GetConfiguration(true);\n        Assert.AreEqual(ServiceRunningState.Running, config.State.ToServiceState());\n      }\n```\nSometimes 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\naddress 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.\n\nSometime 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: \n```cs\n      using (\n        var container =\n          new Builder().UseContainer()\n            .UseImage(\"kiasaki/alpine-postgres\")\n            .ExposePort(5432)\n            .WithEnvironment(\"POSTGRES_PASSWORD=mysecretpassword\")\n            .WaitForProcess(\"postgres\", 30000 /*30s*/)\n            .Build()\n            .Start())\n      {\n        var config = container.GetConfiguration(true);\n        Assert.AreEqual(ServiceRunningState.Running, config.State.ToServiceState());\n      }\n```\nIn the above example ```Build()``` will return control when the process \"postgres\" have been started within the container.\n\n### Filesystem \u0026 Files\nIn order to make use of containers, sometimes it is necessary to mount volumes in the container onto the host or just copy from or to the container. Depending on if you're running machine or docker natively volume mapping have the constraint that it must be reachable from the virtual machine.\n\nA normal use case is to have e.g. a webserver serving content on a docker container and the user edits files on the host file system. In such scenario it is necessary to mount a docker container volume onto the host. For example: \n\n```cs\n     const string html = \"\u003chtml\u003e\u003chead\u003eHello World\u003c/head\u003e\u003cbody\u003e\u003ch1\u003eHello world\u003c/h1\u003e\u003c/body\u003e\u003c/html\u003e\";\n      var hostPath = (TemplateString) @\"${TEMP}/fluentdockertest/${RND}\";\n      Directory.CreateDirectory(hostPath);\n\n      using (\n        var container =\n          new Builder().UseContainer()\n            .UseImage(\"nginx:latest\")\n            .ExposePort(80)\n            .Mount(hostPath, \"/usr/share/nginx/html\", MountType.ReadOnly)\n            .Build()\n            .Start()\n            .WaitForPort(\"80/tcp\", 30000 /*30s*/))\n      {\n          File.WriteAllText(Path.Combine(hostPath, \"hello.html\"), html);\n\n          var response = $\"http://{container.ToHostExposedEndpoint(\"80/tcp\")}/hello.html\".Wget();\n          Assert.AreEqual(html, response);\n      }\n```\nIn the above example a nginx container is started and mounts '/usr/share/nginx/html' onto a (random, in temp directory) host path. A HTML file is copied into the host path and when a HTTP get towards the nginx docker container is done, that same file is served.\n\nSometimes it is necessary copy files to and from a container. For example copy a configuration file, configure it and copy it back. More common scenario is to copy a configuration file to the container, just before it is started. The multi container example copies a nginx configuration file just before it is started. Thus it is possible to avoid manually creating a Dockerfile and a image for such a simple task. Instead just use e.g. an official or custom image, copy configuration and run.\n```cs\n using (new Builder().UseContainer()\n          .UseImage(\"kiasaki/alpine-postgres\")\n          .ExposePort(5432)\n          .WithEnvironment(\"POSTGRES_PASSWORD=mysecretpassword\")\n          .Build()\n          .Start()\n          .CopyFrom(\"/etc/conf.d\", fullPath))\n        {\n          var files = Directory.EnumerateFiles(Path.Combine(fullPath, \"conf.d\")).ToArray();\n          Assert.IsTrue(files.Any(x =\u003e x.EndsWith(\"pg-restore\")));\n          Assert.IsTrue(files.Any(x =\u003e x.EndsWith(\"postgresql\")));\n        }\n```\nAbove example copies a directory to a host path (fullPath) from a running container. Note the use of extension method here, thus not using the fluent API (since CopyFrom is after Start()). If you want to copy files from the container just before starting use the Fluent API instead.\n```cs\n       using (new Builder().UseContainer()\n          .UseImage(\"kiasaki/alpine-postgres\")\n          .ExposePort(5432)\n          .WithEnvironment(\"POSTGRES_PASSWORD=mysecretpassword\")\n          .CopyOnStart(\"/etc/conf.d\", fullPath)\n          .Build()\n          .Start())\n        {\n        }\n```\n\nThe below example illustrates a much more common scenario where files are copied to the container. This example makes use of the extension method instead of the fluent API version. It takes a Diff snapshot before copy and then just after the copy. In the latter\nthe hello.html is present.\n```cs\n       using (\n          var container =\n            new Builder().UseContainer()\n              .UseImage(\"kiasaki/alpine-postgres\")\n              .ExposePort(5432)\n              .WithEnvironment(\"POSTGRES_PASSWORD=mysecretpassword\")\n              .Build()\n              .Start()\n              .WaitForProcess(\"postgres\", 30000 /*30s*/)\n              .Diff(out before)\n              .CopyTo(\"/bin\", fullPath))\n        {\n          var after = container.Diff();\n\n          Assert.IsFalse(before.Any(x =\u003e x.Item == \"/bin/hello.html\"));\n          Assert.IsTrue(after.Any(x =\u003e x.Item == \"/bin/hello.html\"));\n        }\n```\n\nSometime is it useful to copy files in the ```IContainerService.Dispose()``` (just before container has stopped). Therefore a fluent API exists to ensure that it will just do that.\n```cs\nusing (new Builder().UseContainer()\n          .UseImage(\"kiasaki/alpine-postgres\")\n          .ExposePort(5432)\n          .WithEnvironment(\"POSTGRES_PASSWORD=mysecretpassword\")\n          .CopyOnDispose(\"/etc/conf.d\", fullPath)\n          .Build()\n          .Start())\n        {\n        }\n```\nIn order to analyze a container, export extension method and fluent API methods exists. Most notably is the possibility to export a container when a ```IContainerService``` is disposed.\n```cs\n        using (new Builder().UseContainer()\n          .UseImage(\"kiasaki/alpine-postgres\")\n          .ExposePort(5432)\n          .WithEnvironment(\"POSTGRES_PASSWORD=mysecretpassword\")\n          .ExportOnDispose(fullPath)\n          .Build()\n          .Start())\n        {\n        }\n```\nThis will produce a container export (tar file) on the host (fullPath). If you rather have it exploded (un-tared) use the ```ExportExplodedOnDispose``` method instead. Of course you can export the container any time using a extension method on the container.\n\nA useful trick when it comes to unit-testing is to export the container state when the unit test fails for some reason, therefore it exists a Fluent API that will export when a certain Lambda condition is met. For example:\n```cs\n      var failure = false;\n        using (new Builder().UseContainer()\n          .UseImage(\"kiasaki/alpine-postgres\")\n          .ExposePort(5432)\n          .WithEnvironment(\"POSTGRES_PASSWORD=mysecretpassword\")\n          .ExportOnDispose(fullPath, svc =\u003e failure)\n          .Build()\n          .Start())\n        {\n          failure = true;\n        }\n```\nThis snippet will export the container when the using statement is disposing the container since the failure variable is set to true and is used in the ```ExportOnDispose``` expression.\n\n#### IService Hooks\nAll services can be extended with hooks. In the ```ExportOnDispose(path, lambda)``` installs a Hook when the service state is set to execute the lambda when the state is ```Removing```. It is possible to install and remove hooks on the fly. If multiple hooks are registered on same service instance, with same ```ServiceRunningState```, they will be executed in installation order. \n\nThe hooks are particulary good if you want something to be executed when a state is about to be set (or executed) on the service such as ```Starting```. The Fluent API makes use of those in some situations such as Copy files, export, etc.\n\n## Docker Networking\nFluentDocker do support all docker network commands. It can discover networks by ```_docker.NetworkLs()``` where it discovers all networks and some simple parameters defined in ```NetworkRow```. It can also inspect to gain deeper information about the network (such as which containers is in the network and Ipam configuration) by ```_docker.NetworkInspect(network:\"networkId\")```.\n\nIn order to create a new network, use the ```_docker.NetworkCreate(\"name_of_network\")```. It is also possible to supply ```NetworkCreateParams``` where everything can be customized such as creating a _overlay_ network och change the Ipam configuration. To delete a network, just use the ```_docker.NetworkRm(network:\"networkId\")```.\n\n*Note that networks are not deleted if there are any containers attached to it!*\n\nWhen a network is created it is possible to put one or more containers into it using the ```_docker.NetworkConnect(\"containerId\",\"networkId\")```. Note that containers may be in several networks at a time, thus can proxy request between isolated networks. To disconnect a container from a network, simple do a ```_docker.NetworkDisconnect(\"containerId\",\"networkId\")```.\n\nThe following sample runs a container, creates a new network, and connects the running container into the network. It then disconnect the container, delete it, and delete the network.\n```cs\n    var cmd = _docker.Run(\"postgres:9.6-alpine\", new ContainerCreateParams\n        {\n          PortMappings = new[] {\"40001:5432\"},\n          Environment = new[] {\"POSTGRES_PASSWORD=mysecretpassword\"}\n        }, _certificates);\n\n    var container = cmd.Data;\n    var network = string.Empty;\n\n    var created = _docker.NetworkCreate(\"test-network\");\n    if (created.Success)\n      network = created.Data[0];\n\n    _docker.NetworkConnect(container, network);\n\n    // Container is now running and has address in the newly created 'test-network'\n\n    _docker.NetworkDisconnect(container, id, true /*force*/);\n    _docker.RemoveContainer(container, true, true);\n\n    // Now it is possible to delete the network since it has been disconnected from the network\n    _docker.NetworkRm(network: network);\n```\n### Fluent Networking\nIt is also possible to use a fluent builder to build new or reuse existing docker networks. Those can then be referenced while building _containers_. It is possible to build more than one docker network and attach a container to more than one network at a time.\n```cs\n    using(var nw = new Builder().UseNetwork(\"test-network\")) \n    {\n      using (\n        var container =\n          new DockerBuilder()\n            .WithImage(\"kiasaki/alpine-postgres\")\n            .WithEnvironment(\"POSTGRES_PASSWORD=mysecretpassword\")\n            .ExposePorts(\"5432\")\n            .UseNetwork(nw)\n            .WaitForPort(\"5432/tcp\", 30000 /*30s*/)\n            .Build())\n      {\n        container.Create().Start();\n      }\n    }\n```\nThe above code snippet creates a new network called _test-network_ and then creates a container that is attached to the _test-network_. When the ```Dispose()``` is called on _nw_ it will remove the network.\nIt is also possible to do static _IP_ container assignments within the network by `UseIpV4` or `UseIpV6`. For example:\n```cs\nusing (var nw = Fd.UseNetwork(\"unit-test-nw\")\n                .UseSubnet(\"10.18.0.0/16\").Build())\n{\n\tusing (\n\t\tvar container =\n\t\tFd.UseContainer()\n\t\t\t.UseImage(\"postgres:9.6-alpine\")\n\t\t\t.WithEnvironment(\"POSTGRES_PASSWORD=mysecretpassword\")\n\t\t\t.ExposePort(5432)\n\t\t\t.UseNetwork(nw)\n\t\t\t.UseIpV4(\"10.18.0.22\")              \n\t\t\t.WaitForPort(\"5432/tcp\", 30000 /*30s*/)\n\t\t\t.Build()\n\t\t\t.Start())\n\t{\n\t\tvar ip = container.GetConfiguration().NetworkSettings.Networks[\"unit-test-nw\"].IPAddress;\n\t\tAssert.AreEqual(\"10.18.0.22\", ip);\n\t}\n}\n```\nThe above example creates a new network _unit-test-nw_ with ip-range _10.18.0.0/16_. It is the used in the new container. The IP for the container is set to _10.18.0.22_ and is static due to `UseIpV4` command.\n## Volume Support\nFluentDocker supports docker volume management both from commands and from a fluent API. Therefore it is possible to have total control on volumes used in container such if it shall be disposed, reused, what driver to use etc.\n\n```cs\n  var volume = _docker.VolumeCreate(\"test-volume\", \"local\", opts: {\n                                      {\"type\",\"nfs\"},\n                                      {\"o=addr\",\"192.168.1.1,rw\"},\n                                      {\"device\",\":/path/to/dir\"}\n                                    });\n\n  var cfg = _docker.VolumeInspect(_certificates, \"test-volume\");\n  _docker.VolumeRm(force: true, id: \"test-volume\");\n```\nThe above snippet creates a new volume with name _test-volume_ and is of _NFS_ type. It then inspects the just created volume and lastly force delete the volume.\n\n### Fluent Volume API\nIt is also possible to use a fluent API to create or use volumes. They can then be used when building a container. This is especially useful when creation of volumes are special or lifetime needs to be controlled.\n```cs\n      using (var vol = new Builder().UseVolume(\"test-volume\").RemoveOnDispose().Build())\n      {\n        using (\n          var container =\n            new Builder().UseContainer()\n              .UseImage(\"postgres:9.6-alpine\")\n              .WithEnvironment(\"POSTGRES_PASSWORD=mysecretpassword\")\n              .MountVolume(vol, \"/var/lib/postgresql/data\", MountType.ReadWrite)\n              .Build()\n              .Start())\n        {\n          var config = container.GetConfiguration();\n\n          Assert.AreEqual(1, config.Mounts.Length);\n          Assert.AreEqual(\"test-volume\", config.Mounts[0].Name);\n        }\n      }\n```\nThe above sample creates a new volume called _test-volume_ and it is scheduled to be delete when ```Dispose()``` is invoked on the ```IVolumeService```. The container is created and mounts the newly created volume to _/var/lib/postgresql/data_ as _read/write_ access mode.\nSince the container is within the scope of the ```using``` statement of the volume it's lifetime spans the whole container lifetime and then gets deleted.\n\n### Events\n\n_FluentDocker_ supports, connecting to the docker event mechanism to listen to the events it sends.\n\n```cs\nusing (var events = Fd.Native().Events())\n{\n  using (\n      var container =\n          new Builder().UseContainer()\n              .UseImage(\"postgres:9.6-alpine\")\n              .ExposePort(5432)\n              .WithEnvironment(\"POSTGRES_PASSWORD=mysecretpassword\")\n              .WaitForPort(\"5432/tcp\", 30000 /*30s*/)\n              .Build()\n              .Start())\n  {\n    FdEvent e;\n    while ((e= events.TryRead(3000)) != null)\n    {\n      if (e.Type == EventType.Container \u0026\u0026 e.Action == EventAction.Start)\n        break;\n    }\n  }\n}\n```\n\nEvent listener is global and may handle many `EventAction` types. \n\nFor example\n\n* Pull\n* Create\n* Start\n* Kill\n* Die\n* Connect\n* Disconnect\n* Stop\n* Destroy\n\nDepending on action, the event type may differ such as `ContainerKillEvent` for `EventAction.Kill`. All events derive from `FdEvent`. That means all shared properties is in the base event and the explicit ones are in the derived.\n\nFor example, the ´ContainerKillEvent` contains the following properties:\n\n```cs\npublic sealed class ContainerKillActor : EventActor\n{\n  /// \u003csummary\u003e\n  /// The image name and label such as \"alpine:latest\".\n  /// \u003c/summary\u003e\n  public string Image { get; set; }\n  /// \u003csummary\u003e\n  /// Name of the container.\n  /// \u003c/summary\u003e\n  public string Name { get; set; }\n  /// \u003csummary\u003e\n  /// The signal that the container has been signalled.\n  /// \u003c/summary\u003e\n  public string Signal { get; set; }\n}\n```\n\nThis event loop may be used to pick up events and drive your instantiated `IService` instances. Or if you need to react to e.g. a network is added or deleted.\n\n### Logging\nIn the full framework it uses verbose logging using the ```System.Diagnostics.Debugger.Log```. For .net core it uses the standard\n```Microsoft.Extensions.Logging.ILog``` to log. Both are using the category _Ductus.FluentDocker_ and therefore may be configured\nto participate in logging or not and configure different logging destinations.\n\nIn .net core you can provide the logging segment in the application config file.\n```\n{\n  \"Logging\": {\n    \"IncludeScopes\": false,\n    \"LogLevel\": {\n      \"Ductus.FluentDocker\": \"None\"\n      }\n   }\n}\n```\nPlease check the https://docs.microsoft.com/en-us/aspnet/core/fundamentals/logging/?view=aspnetcore-2.1 for more information.\nFor full framework please check out the _XML_ needed in the appconfig for the full framework described in https://docs.microsoft.com/en-us/dotnet/framework/wcf/diagnostics/tracing/configuring-tracing.\n\nThere's a quick way of disabling / enabling logging via (```Ductus.FluentDocker.Services```) ```Logging.Enabled()``` or ```Logging.Disabled()```. This will forcefully enable / disable logging.\n\n### Custom IPEndpoint Resolvers\n\nIt is possible to override the default mechanism of _FluentDocker_ resolves the container IP from the clients perspective in e.g. `WaitForPort`. This can be overridden on `ContainerBuilder` basis.\n\nThe below sample, overrides the _default_ behaviour. When it returns `null` the _default_ resolver kicks in.\n  \n```cs\nusing (\n  var container =\n    Fd.UseContainer()\n      .UseImage(\"postgres:9.6-alpine\")\n      .WithEnvironment(\"POSTGRES_PASSWORD=mysecretpassword\")\n      .ExposePort(5432)\n      .UseCustomResolver((\n        ports, portAndProto, dockerUri) =\u003e\n      {\n        if (null == ports || string.IsNullOrEmpty(portAndProto))\n          return null;\n\n        if (!ports.TryGetValue(portAndProto, out var endpoints))\n          return null;\n\n        if (null == endpoints || endpoints.Length == 0)\n          return null;\n\n        if (CommandExtensions.IsNative())\n          return endpoints[0];\n\n        if (CommandExtensions.IsEmulatedNative())\n          return CommandExtensions.IsDockerDnsAvailable()\n            ? new IPEndPoint(CommandExtensions.EmulatedNativeAddress(), endpoints[0].Port)\n            : new IPEndPoint(IPAddress.Loopback, endpoints[0].Port);\n\n        if (Equals(endpoints[0].Address, IPAddress.Any) \u0026\u0026 null != dockerUri)\n          return new IPEndPoint(IPAddress.Parse(dockerUri.Host), endpoints[0].Port);\n\n        return endpoints[0];\n      })\n      .WaitForPort(\"5432/tcp\", 30000 /*30s*/)\n      .Build()\n      .Start())\n{\n  var state = container.GetConfiguration(true/*force*/).State.ToServiceState();\n  Assert.AreEqual(ServiceRunningState.Running, state);\n}\n```\n\n### Talking to custom docker daemon URI without docker-machine\n\nThere's limited support to use the _FluentAPI_ to talk to a remote docker daemon without using docker-machine. This is done either by manually creating a instance of a `DockerHostService` or use `FromUri` on `HostBuilder`.\n\n```cs\n  using(var container = Fd.UseHost().\n                          FromUri(Settings.DockerUri, isWindowsHost: true).\n                          UseContainer().\n                          Build()) \n  {\n  }\n```\n\nThe above sample connects to a custom `DockerUri` from a setting and is a windows container docker daemon.\n\n\n* `FromUri` that uses a `DockerUri` to create a `IHostService`. This _uri_ is arbitrary. It also support other properties (_see below_).\n\n```cs\n   public HostBuilder FromUri(\n      DockerUri uri,\n      string name = null,\n      bool isNative = true,\n      bool stopWhenDisposed = false,\n      bool isWindowsHost = false,\n      string certificatePath = null) {/*...*/}\n```\n\nIt will use _\"sensible\"_ defaults on all parameters. Most of the case the _uri_ is sufficient. For example if not providing the _certificatePath_ it will try to get it from the environment _DOCKER_CERT_PATH_. If not found in the environment, it will default to none.\n\n* `UseHost` that takes a instantiated `IHostService` implementation.\n\n## Docker Compose Support\nThe library support _docker-compose_ to use existing compose files to render services and manage lifetime of such.\n\nThe following sample will do have compose file that fires up a _MySql_ and a _WordPress_. Therefore the single compose\nservice will have two _container_ services below it. By default, it will stop the services and clean up when \n```Dispose()``` is invoked. This can be overridden by ```KeepContainers()``` in the _fluent_ configuration.\n\n```yml\nversion: '3.3'\n\nservices:\n  db:\n    image: mysql:5.7\n    volumes:\n    - db_data:/var/lib/mysql\n    restart: always\n    environment:\n      MYSQL_ROOT_PASSWORD: somewordpress\n      MYSQL_DATABASE: wordpress\n      MYSQL_USER: wordpress\n      MYSQL_PASSWORD: wordpress\n\n  wordpress:\n    depends_on:\n    - db\n    image: wordpress:latest\n    ports:\n    - \"8000:80\"\n    restart: always\n    environment:\n      WORDPRESS_DB_HOST: db:3306\n      WORDPRESS_DB_USER: wordpress\n      WORDPRESS_DB_PASSWORD: wordpress\nvolumes:\n  db_data:\n``` \nThe above file is the _docker-compose_ file to stitch up the complete service.\n\n```cs\n      var file = Path.Combine(Directory.GetCurrentDirectory(),\n        (TemplateString) \"Resources/ComposeTests/WordPress/docker-compose.yml\");\n\n      using (var svc = new Builder()\n                        .UseContainer()\n                        .UseCompose()\n                        .FromFile(file)\n                        .RemoveOrphans()\n                        .Build().Start())\n      {\n        var installPage = await \"http://localhost:8000/wp-admin/install.php\".Wget();\n\n        Assert.IsTrue(installPage.IndexOf(\"https://wordpress.org/\", StringComparison.Ordinal) != -1);\n        Assert.AreEqual(1, svc.Hosts.Count);\n        Assert.AreEqual(2, svc.Containers.Count);\n        Assert.AreEqual(2, svc.Images.Count);\n        Assert.AreEqual(5, svc.Services.Count);\n      }\n``` \n The above snippet is fluently configuring the _docker-compose_ service and invokes the install page to verify that\n WordPress is indeed working.\n \n It is also possible to do all the operations that a single container supports such as copy on, export, wait operations. For example:\n```cs\n      var file = Path.Combine(Directory.GetCurrentDirectory(),\n        (TemplateString) \"Resources/ComposeTests/WordPress/docker-compose.yml\");\n\n      // @formatter:off\n      using (new Builder()\n                .UseContainer()\n                .UseCompose()\n                .FromFile(file)\n                .RemoveOrphans()\n                .WaitForHttp(\"wordpress\",  \"http://localhost:8000/wp-admin/install.php\", continuation: (resp, cnt) =\u003e  \n                             resp.Body.IndexOf(\"https://wordpress.org/\", StringComparison.Ordinal) != -1 ? 0 : 500)\n                .Build().Start())\n        // @formatter:on\n      {\n        // Since we have waited - this shall now always work.       \n        var installPage = await \"http://localhost:8000/wp-admin/install.php\".Wget();\n        Assert.IsTrue(installPage.IndexOf(\"https://wordpress.org/\", StringComparison.Ordinal) != -1);\n      }\n```\nThe above snippet fires up the wordpress docker compose project and checks the _URL_ http://localhost:8000/wp-admin/install.php it it returns a certain value in the body \n(in this case \"https://wordpress.org/\"). If not it returns _500_ and the ```WaitForHttp``` function will wait 500 milliseconds before invoking again. This works for any custom\nlambda as well, just use ```WaitFor``` instead. Thus it is possible to e.g. query a database before continuing inside the using scope.\n\n## Talking to Docker Daemon\nFor Linux and Mac users there are several options how to authenticate towards the socket. _FluentDocker_ supports no _sudo_, _sudo_ without any password (user added as NOPASSWD in /etc/sudoer), or\n_sudo_ with password. The default is that FluentDocker expects to be able to talk without any _sudo_. The options ar global but can be changed in runtime.\n\n```cs\n     SudoMechanism.None.SetSudo(); // This is the default\n     SudoMechanism.Password.SetSudo(\"\u003cmy-sudo-password\u003e\");\n     SudoMechanism.NoPassword.SetSudo();\n```\n\nIf you wish to turn off _sudo_ to communicate with the docker daemon, you can follow a docker [tutorial](https://docs.docker.com/install/linux/docker-ce/ubuntu/) and do the last step of adding your user to docker group.\n\n### Connecting to Remote Docker Daemons\nFluentDocker supports connection to remote docker daemons. The fluent API supports e.g. \n```cs\nnew Builder().UseHost().UseMachine().WithName(\"remote-daemon\")\n```\nwhere this requires a already pre-setup entry in the _docker-machine_ registry. It is also possible to\ndefine _SSH_ based _docker-machine_ registry entires to connect to remote daemon.\n\n```cs\n      using (\n        var container =\n          new Builder().UseHost()\n            .UseSsh(\"192.168.1.27\").WithName(\"remote-daemon\")\n            .WithSshUser(\"solo\").WithSshKeyPath(\"${E_LOCALAPPDATA}/lxss/home/martoffi/.ssh/id_rsa\")\n            .UseContainer()\n            .UseImage(\"postgres:9.6-alpine\")\n            .WithEnvironment(\"POSTGRES_PASSWORD=mysecretpassword\")\n            .Build())\n      {\n        Assert.AreEqual(ServiceRunningState.Stopped, container.State);\n      }\n```\nThis example will create a new _docker-machine_ registry entry named _remote-daemon_ that uses _SSH_ with\nip address of _192.168.1.27_ and the _SSH_ user _solo_. If a entry is already found named _remote-daemon_\nit will just reuse this entry. Then it gets a _IHostService_ with correct certificates and _URL_ for the\nremote daemon. Thus, it is possible then to create a docker container on the remote daemon, in thus case\nit is the _postgres_ image. When it disposes the container, as usual it deletes it from the remote docker.\nThe _IHostService_ do make sure to pick upp all necessary certificates in order to authenticate the connection.\n\nThe above example produces this _docker-machine_ registry entry.\n```\nC:\\Users\\martoffi\u003edocker-machine ls\nNAME           ACTIVE   DRIVER    STATE     URL                       SWARM   DOCKER        ERRORS\nremote-daemon  *        generic   Running   tcp://192.168.1.27:2376           v18.06.1-ce\n```\n\nIn order to use ```UseSsh(...)``` a _SSH_ tunnel with no password must been set up. In addition the user\nthat uses the tunnel must be allowed to access the docker daemon.\n\nFollow these tutorial how to setup the _SSH_ tunnel and make sure the user can access the docker daemon.\n1) https://www.kevinkuszyk.com/2016/11/28/connect-your-docker-client-to-a-remote-docker-host/\n2) https://askubuntu.com/questions/192050/how-to-run-sudo-command-with-no-password\n\nBasically create a new rsa key to use with the _SSH_ tunnel using ```ssh-keygen -t rsa``` and then\ncopy it to the remote host by ```ssh-copy-id {username}@{host}```.\n\nEdit the /etc/sudoers as specified in the second tutorial.\n\nWhen this is done, you now can access the remote docker daemon by the generic driver or the fluent API\nspecified above. To do the same thing manually as specified in the example it would look something like this.\n\n\n```\nC:\\Users\\martoffi\u003edocker-machine.exe create --driver generic --generic-ip-address=192.168.1.27 --generic-ssh-key=\"%localappdata%/lxss/home/martoffi/.ssh/id_rsa\" --generic-ssh-user=solo remote-daemon\nRunning pre-create checks...\nCreating machine...\n(remote-daemon) Importing SSH key...\nWaiting for machine to be running, this may take a few minutes...\nDetecting operating system of created instance...\nWaiting for SSH to be available...\nDetecting the provisioner...\nProvisioning with ubuntu(systemd)...\nInstalling Docker...\nCopying certs to the local machine directory...\nCopying certs to the remote machine...\nSetting Docker configuration on the remote daemon...\nChecking connection to Docker...\nDocker is up and running!\nTo see how to connect your Docker Client to the Docker Engine running on this virtual machine, run: docker-machine.exe env remote-daemon\n```\n\nNow the registry entry is created, it is possible set the environment for the terminal docker.\n\n```\nC:\\Users\\martoffi\u003edocker-machine.exe env remote-daemon\nSET DOCKER_TLS_VERIFY=1\nSET DOCKER_HOST=tcp://192.168.1.24:2376\nSET DOCKER_CERT_PATH=C:\\Users\\martoffi\\.docker\\machine\\machines\\remote-daemon\nSET DOCKER_MACHINE_NAME=remote-daemon\nSET COMPOSE_CONVERT_WINDOWS_PATHS=true\nREM Run this command to configure your shell:\nREM     @FOR /f \"tokens=*\" %i IN ('docker-machine.exe env remote-daemon') DO @%i\n```\n\nRun this to make docker client use the remote docker daemon.\n```\n@FOR /f \"tokens=*\" %i IN ('docker-machine.exe env remote-daemon') DO @%i\n```\nAll commands using the ```docker``` binary will now execute on the remote docker daemon.\n\n### Hyper-V\nWhen creating and querying, via machine, a hyper-v docker machine the process needs to be elevated since Hyper-V will not\nrespond to API calls in standard user mode.\n\n## Misc\n\n### Health Check\nIt is possible to specify a health check for the docker container to report the state of the container based on such activity. The following example\nis using a health check that the container has exited or not. It is possible to check the configuration (make sure to force refresh) what status\nthe health check is reporting.\n```cs\n      using (\n        var container =\n          Fd.UseContainer()\n            .UseImage(\"postgres:latest\", force: true)\n            .HealthCheck(\"exit\")\n            .WithEnvironment(\"POSTGRES_PASSWORD=mysecretpassword\")\n            .Build()\n            .Start())\n      {\n        var config = container.GetConfiguration(true);\n        AreEqual(HealthState.Starting, config.State.Health.Status);\n      }\n```\n\n### ulimit\nIt is possible via the _Fluent API_ and `ContainerCreateParams` specify ulimit to the docker container to e.g. limit the number of open files etc. For example\nusing the _Fluent API_ could look like this when restricting the number of open files to 2048 (both soft and hard).\n```cs\nusing (\n        var container =\n          Fd.UseContainer()\n            .UseImage(\"postgres:latest\", force: true)\n            .UseUlimit(Ulimit.NoFile,2048, 2048)\n            .WithEnvironment(\"POSTGRES_PASSWORD=mysecretpassword\")\n            .Build()\n            .Start())\n      {\n        // Do stuff\n      }\n```\n\n## Test Support\nThis repo contains three nuget packages, one for the fluent access, one for ms-test base classes and another for xunit base classes to be used while testing. For example in a unit-test it is possible to fire up a postgres container and wait when the the db has booted.\n\n### XUnit\n```cs\n     public class PostgresXUnitTests : IClassFixture\u003cPostgresTestBase\u003e\n     {\n          [Fact]\n          public void Test()\n          {\n               // We now have a running postgres\n               // and a valid connection string to use.\n          }\n     }\n```\n\n\n### MSTest\n```cs\n     using (\n        var container =\n          new DockerBuilder()\n            .WithImage(\"kiasaki/alpine-postgres\")\n            .WithEnvironment(\"POSTGRES_PASSWORD=mysecretpassword\")\n            .ExposePorts(\"5432\")\n            .WaitForPort(\"5432/tcp\", 30000 /*30s*/)\n            .Build())\n      {\n        container.Create().Start();\n      }\n```\nIt is also possible to re-use abstract base classes, for example postgres test base to simplify and make clean unittest towards a container.\n```cs\n     [TestClass]\n     public class PostgresMsTests : PostgresTestBase\n     {\n          [TestMethod]\n          public void Test()\n          {\n               // We now have a running postgres\n               // and a valid connection string to use.\n          }\n     }\n```  \nThe `FluentDockerTestBase` allows for simple overrides to do whatever custom docker backed test easily. Just create a test class and derive from the `FluentDockerTestBase` and override suitable methods. For example.\n```cs\n     protected override DockerBuilder Build()\n     {\n          return new DockerBuilder()\n               .WithImage(\"kiasaki/alpine-postgres\")\n               .WithEnvironment($\"POSTGRES_PASSWORD={PostgresPassword}\")\n               .ExposePorts(\"5432\")\n               .WaitForPort(\"5432/tcp\", 30000 /*30s*/);\n     }\n```     \nThis will create a builder with docker image postgres:latest and set one environment string, it will also expose the postgres db port 5432 to the host so one can connect to the db within the container. Lastly it will wait for the port 5432. This ensures that the db is running and have properly booted. If timeout, in this example set to 30 seconds, it will throw an exception and the container is stopped and removed. Note that the host port is not 5432! Use `Container.GetHostPort(\"5432/tcp\")` to get the host port. The host ip can be retrieved by `Container.Host` property and thus shall be used when communicating with the app in the container. \n\nIf a callback is needed when the container has been successfully pulled, created, and started.\n```cs\n     protected override void OnContainerInitialized()\n     {\n          ConnectionString = string.Format(PostgresConnectionString, Container.Host,\n               Container.GetHostPort(\"5432/tcp\"), PostgresUser,\n               PostgresPassword, PostgresDb);\n     }\n```     \nThis example renders a proper connection string to the postgresql db within the newly spun up container. This can be used to connect using Npgsql, EF7, NHibernate, Marten or other compatible tools. This method will not be called if pulling of the image from the docker repository or it could not create/start the container.\n\nIf a before shutdown container hook is wanted override.\n```cs\n     protected virtual void OnContainerTearDown()\n     {\n          // Do stuff before container is shut down.\n     }\n```\nNote that if un-named container, if not properly disposed, the docker container will still run and must be manually removed. This is a feature not a bug since you might want several containers running in your test. The `DockerContainer` class manages the instance id of the container and thus only interact with it and no other container.\n\nWhen creating / starting a new container it will first check the local repository if the container image is already present and will download it if not found. This may take some time and there's just a Debug Log if enabled it is possible to monitor the download process.\n\n## Miscellanious\n\n### Unhandled Exceptions\nWhen a unhandled exception occurs and the application _FailFast_ i.e. terminates quickly it\nwill *not* invoke ```finally``` clause. Thus a failing ```WaitForPort``` inside a ```using``` statement will *not* \ndispose the container service. Therefore the container is is still running. To fix this, either have a global \ntry...catch or inject one locally e.g.\n```cs\n            try\n            {\n                using (var container =\n                        new Builder().UseContainer()\n                            .UseImage(\"postgres:9.6-alpine\")\n                            .ExposePort(5432)\n                            .WithEnvironment(\"POSTGRES_PASSWORD=postgres\")\n                            .WaitForPort(\"5777/tcp\", 10000) // Fail here since 5777 is not valid port\n                            .Build())\n                {\n                    container.Start(); // FluentDockerException is thrown here since WaitForPort is executed\n                }\n            } catch { throw; }\n```\nBut it this is only when application termination is done due to the ```FluentDockerException``` thrown in the\n```WaitForPort```, otherwise it will dispose the container properly and thus the ```try...catch``` is not needed.\n\nThis could also be solved using the ```Fd.Build``` functions (_see Using Builder Extensions_ for more information).\n\n### Using Builder Extensions\nThe class ```Fd``` is a static _class_ that provides convenience methods for building and running single\nand composed containers. To build a container just use:\n```cs\n    var build = Fd.Build(c =\u003e c.UseContainer()\n                    .UseImage(\"postgres:9.6-alpine\")\n                    .ExposePort(5432)\n                    .WithEnvironment(\"POSTGRES_PASSWORD=mysecretpassword\")\n                    .WaitForPort(\"5432/tcp\", TimeSpan.FromSeconds(30)));\n\n// This is the equivalent of\n    var build = new Builder().UseContainer()\n                             .UseImage(\"postgres:9.6-alpine\")\n                             .ExposePort(5432)\n                             .WithEnvironment(\"POSTGRES_PASSWORD=mysecretpassword\")\n                             .WaitForPort(\"5432/tcp\", TimeSpan.FromSeconds(30));\n```\nThis can then be used to start the containers within a _safe_ ```using``` clause that is **guaranteed** to be\ndisposed even if uncaught exception.\n```cs\n    build.Container(svc =\u003e\n    {\n        var config = svc.GetConfiguration();\n        // Do stuff...\n    });   \n```\nAfter the ```Container``` method has been executed the container is in this case stopped and removed. This is the quivalent of\n```cs\n   // This is equivalent of\n    try\n    {\n        using(var svc = build.Build()) \n        {\n            svc.Start();\n            \n            var config = svc.GetConfiguration();\n            // Do stuff...            \n        }\n    }\n    catch\n    {\n        Log(...);\n        throw;\n    }\n```\n\n\nIt is also possible to combine builder and running e.g. via:\n```cs\n      Fd.Container(c =\u003e c.UseContainer()\n          .UseImage(\"postgres:9.6-alpine\")\n          .ExposePort(5432)\n          .WithEnvironment(\"POSTGRES_PASSWORD=mysecretpassword\")\n          .WaitForPort(\"5432/tcp\", TimeSpan.FromSeconds(30)),\n        svc =\u003e\n        {\n          var config = svc.GetConfiguration();\n          // Do stuff...\n        });\n```\nThe above example will build the container, start, stop, and finally delete the container. Even if and\n```Exception``` is thrown it will be ```Disposed```. Of course it is possible to use compsed container using \n```composite``` extension methods as with ```container```.\n","funding_links":["https://github.com/sponsors/mariotoffia"],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmariotoffia%2Ffluentdocker","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmariotoffia%2Ffluentdocker","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmariotoffia%2Ffluentdocker/lists"}