Red Hat 的去 Docker 化容器实践
容器
![](/data/attachment/album/201805/10/122423nn342z26ornon21o.jpg)


最近几年，开源项目 Docker (已更名为[Moby](https://mobyproject.org/)) 在容器普及化方面建树颇多。然而，它的功能特性不断集中到一个单一、庞大的系统，该系统由具有 root 权限运行的守护进程 `dockerd` 管控，这引发了人们的焦虑。对这些焦虑的阐述，具有代表性的是 Red Hat 公司的容器团队负责人 Dan Walsh 在 [KubeCon + CloudNativecon](http://events.linuxfoundation.org/events/kubecon-and-cloudnativecon-north-america) 会议中的[演讲](https://kccncna17.sched.com/event/CU8j/cri-o-hosted-by-daniel-walsh-red-hat)。Walsh 讲述了他的容器团队目前的工作方向，即使用一系列更小、可协同工作的组件替代 Docker。他的战斗口号是“拒绝臃肿的守护进程”，理由是与公认的 Unix 哲学相违背。


### Docker 模块化实践


就像我们在[早期文献](https://lwn.net/Articles/741897/)中看到的那样，容器的基础操作不复杂：你首先拉取一个容器镜像，利用该镜像创建一个容器，最后启动这个容器。除此之外，你要懂得如何构建镜像并推送至镜像仓库。大多数人在上述这些步骤中使用 Docker，但其实 Docker 并不是唯一的选择，目前的可替换选择是 `rkt`。rkt 引发了一系列标准的创建，包括运行时标准 CRI，镜像标准 OCI 及网络标准 CNI 等。遵守这些标准的后端，如 [CRI-O](http://cri-o.io/) 和 Docker，可以与 [Kubernetes](https://kubernetes.io/) 为代表的管理软件协同工作。


这些标准促使 Red Hat 公司开发了一系列实现了部分标准的“核心应用”供 Kubernetes 使用，例如 CRI-O 运行时。但 Kubernetes 提供的功能不足以满足 Red Hat 公司的 [OpenShift](https://www.openshift.com/) 项目所需。开发者可能需要构建容器并推送至镜像仓库，实现这些操作需要额外的一整套方案。


事实上，目前市面上已有多种构建容器的工具。来自 Sysdig 公司的 Michael Ducy 在[分会场](https://kccncna17.sched.com/event/CU6B/building-better-containers-a-survey-of-container-build-tools-i-michael-ducy-chef)中回顾了 Docker 本身之外的 8 种镜像构建工具，而这也很可能不是全部。Ducy 将理想的构建工具定义如下：可以用可重现的方式创建最小化镜像。最小化镜像并不包含操作系统，只包含应用本身及其依赖。Ducy 认为 [Distroless](https://github.com/GoogleCloudPlatform/distroless), [Smith](https://github.com/oracle/smith) 及 [Source-to-Image](https://github.com/openshift/source-to-image) 都是很好的工具，可用于构建最小化镜像。Ducy 将最小化镜像称为“微容器”。


<ruby> 可重现镜像 <rt>  reproducible container </rt></ruby>是指构建多次结果保持不变的镜像。为达到这个目标，Ducy 表示应该使用“宣告式”而不是“命令式”的方式。考虑到 Ducy 来自 Chef 配置管理工具领域，你应该能理解他的意思。Ducy 给出了符合标准的几个不错的实现，包括 [Ansible 容器](https://www.ansible.com/ansible-container)、 [Habitat](https://www.habitat.sh/)、 [nixos-容器](https://nixos.org/nixos/manual/#ch-containers)和 [Simth](https://github.com/oracle/smith) 等，但你需要了解这些项目对应的编程语言。Ducy 额外指出 Habitat 构建的容器自带管理功能，如果你已经使用了 systemd、 Docker 或 Kubernetes 等外部管理工具，Habitat 的管理功能可能是冗余的。除此之外，我们还要提到从 Docker 和 [Buildah](https://github.com/projectatomic/buildah) 项目诞生的新项目 [BuildKit](https://github.com/moby/buildkit)， 它是 Red Hat 公司 [Atomic 工程](https://www.projectatomic.io/)的一个组件。


### 使用 Buildah 构建容器


![\[Buildah logo\]](/data/attachment/album/201805/10/122437qhminlii9nhbwbbw.png "Buildah logo")


Buildah 名称显然来自于 Walsh 风趣的 [波士顿口音](https://en.wikipedia.org/wiki/Boston_accent)； 该工具的品牌宣传中充满了波士顿风格，例如 logo 使用了波士顿梗犬（如图所示）。该项目的实现思路与 Ducy 不同：为了构建容器，与其被迫使用宣告式配置管理的方案，不如构建一些简单工具，结合你最喜欢的配置管理工具使用。这样你可以如愿的使用命令行，例如使用 `cp` 命令代替 Docker 的自定义指令 `COPY` 。除此之外，你可以使用如下工具为容器提供内容：1) 配置管理工具，例如Ansible 或 Puppet；2) 操作系统相关或编程语言相关的安装工具，例如 APT 和 pip； 3) 其它系统。下面展示了基于通用 shell 命令的容器构建场景，其中只需要使用 `make` 命令即可为容器安装可执行文件。



```
# 拉取基础镜像, 类似 Dockerfile 中的 FROM 命令
buildah from redhat

# 挂载基础镜像, 在其基础上工作
crt=$(buildah mount)
ap foo $crt
make install DESTDIR=$crt

# 下一步，生成快照
buildah commit

```

有趣的是，基于这个思路，你可以复用主机环境中的构建工具，无需在镜像中安装这些依赖，故可以构建非常微小的镜像。通常情况下，构建容器镜像时需要在容器中安装目标应用的构建依赖。例如，从源码构建需要容器中有编译器工具链，这是因为构建并不在主机环境进行。大量的容器也包含了 `ps` 和 `bash` 这样的 Unix 命令，对微容器而言其实是多余的。开发者经常忘记或无法从构建好的容器中移除一些依赖，增加了不必要的开销和攻击面。


Buildah 的模块化方案能够以非 root 方式进行部分构建；但`mount` 命令仍然需要 `CAP_SYS_ADMIN`，有一个 [工单](https://github.com/projectatomic/buildah/issues/171) 试图解决该问题。但 Buildah 与 Docker [都有](https://github.com/projectatomic/buildah/issues/158)同样的[限制](https://github.com/moby/moby/issues/27886#issuecomment-281278525)，即无法在容器内构建容器。对于 Docker，你需要使用“特权”模式运行容器，一些特殊的环境很难满足这个条件，例如 [GitLab 持续集成](https://about.gitlab.com/features/gitlab-ci-cd/)；即使满足该条件，配置也特别[繁琐](https://jpetazzo.github.io/2015/09/03/do-not-use-docker-in-docker-for-ci/)。


手动提交的步骤可以对创建容器快照的时间节点进行细粒度控制。Dockerfile 每一行都会创建一个新的快照；相比而言，Buildah 的提交检查点都是事先选择好的，这可以减少不必要的快照并节省磁盘空间。这也有利于隔离私钥或密码等敏感信息，避免其出现在公共镜像中。


Docker 构建的镜像是非标准的、仅供其自身使用；相比而言，Buildah 提供[多种输出格式](https://github.com/projectatomic/buildah/blob/master/docs/buildah-push.md)，其中包括符合 OCI 标准的镜像。为向后兼容，Buildah 提供了一个“使用 Dockerfile 构建”的命令，即 [`buildah bud`](https://github.com/projectatomic/buildah/blob/master/docs/buildah-bud.md)， 它可以解析标准的 Dockerfile。Buildah 提供 `enter` 命令直接查看镜像内部信息，`run` 命令启动一个容器。实现这些功能仅使用了 `runc` 在内的标准工具，无需在后台运行一个“臃肿的守护进程”。


Ducy 对 Buildah 表示质疑，认为采用非宣告性不利于可重现性。如果允许使用 shell 命令，可能产生很多预想不到的情况；例如，一个 shell 脚本下载了任意的可执行程序，但后续无法追溯文件的来源。shell 命令的执行受环境变量影响，执行结果可能大相径庭。与基于 shell 的工具相比，Puppet 或 Chef 这样的配置管理系统在理论上更加可靠，因为它们的设计初衷就是收敛于最终配置；事实上，可以通过配置管理系统调用 shell 命令。但 Walsh 对此提出反驳，认为已有的配置管理工具可以在 Buildah 的基础上工作，用户可以选择是否使用配置管理；这样更加符合“机制与策略分离”的经典 Unix 哲学。


目前 Buildah 处于测试阶段，Red Hat 公司正努力将其集成到 OpenShift。我写这篇文章时已经测试过 Buildah，它缺少一些文档，但基本可以稳定运行。尽管在错误处理方面仍有待提高，但它确实是一款值得你关注的容器工具。


### 替换其它 Docker 命令行


Walsh 在其演讲中还简单介绍了 Red hat 公司 正在开发的另一个暂时叫做 [libpod](https://jpetazzo.github.io/2015/09/03/do-not-use-docker-in-docker-for-ci/) 的项目。项目名称来源于 Kubernetes 中的 “pod”， 在 Kubernetes 中 “pod” 用于分组主机内的容器，分享名字空间等。


Libpod 提供 `kpod` 命令，用于直接检查和操作容器存储。Walsh 分析了该命令发挥作用的场景，例如 `dockerd` 停止响应或 Kubernetes 集群崩溃。基本上，`kpod` 独立地再次实现了 `docker` 命令行工具。`kpod ps` 返回运行中的容器列表，`kpod images` 返回镜像列表。事实上，[命令转换速查手册](https://github.com/projectatomic/libpod/blob/master/transfer.md#development-transfer) 中给出了每一条 Docker 命令对应的 `kpod` 命令。


这种模块化实现的一个好处是，当你使用 `kpod run` 运行容器时，容器直接作为当前 shell 而不是 `dockerd` 的子进程启动。理论上，可以直接使用 systemd 启动容器，这样可以消除 `dockerd` 引入的冗余。这让[由套接字激活的容器](http://0pointer.de/blog/projects/socket-activated-containers.html)成为可能，但暂时基于 Docker 实现该特性[并不容易](https://legacy-developer.atlassian.com/blog/2015/03/docker-systemd-socket-activation/)，[即使借助 Kubernetes](https://github.com/kubernetes/kubernetes/issues/484) 也是如此。但我在测试过程中发现，使用 `kpod` 启动的容器有一些基础功能性缺失，具体而言是网络功能（!），相关实现在[活跃开发](https://github.com/projectatomic/libpod/issues/129)过程中。


我们最后提到的命令是 `push`。虽然上述命令已经足以满足本地使用容器的需求，但没有提到远程仓库，借助远程仓库开发者可以活跃地进行应用打包协作。仓库也是持续部署框架的核心组件。[skopeo](https://github.com/projectatomic/skopeo) 项目用于填补这个空白，它是另一个 Atomic 成员项目，按其 `README` 文件描述，“包含容器镜像及镜像库的多种操作”。该项目的设计初衷是，在不用类似 `docker pull` 那样实际去下载可能体积庞大的镜像的前提下，检查容器镜像的内容。Docker [拒绝加入](https://github.com/moby/moby/pull/14258) 检查功能，建议通过一个额外的工具实现该功能，这促成了 Skopeo 项目。除了 `pull`、`push`，Skopeo 现在还可以完成很多其它操作，例如在，不产生本地副本的情况下将镜像在不同的仓库中复制和转换。由于部分功能比较基础，可供其它项目使用，目前很大一部分 Skopeo 代码位于一个叫做 [containers/image](https://github.com/containers/image) 的基础库。[Pivotal](https://pivotal.io/)、 Google 的 [container-diff](https://github.com/GoogleCloudPlatform/container-diff) 、`kpod push` 及 `buildah push` 都使用了该库。


`kpod` 与 Kubernetes 并没有紧密的联系，故未来可能会更换名称（事实上，在本文刊发过程中，已经更名为 [`podman`](https://github.com/projectatomic/libpod/blob/master/docs/podman.1.md)），毕竟 Red Hat 法务部门还没有明确其名称。该团队希望实现更多 pod 级别的命令，这样可以对多个容器进行操作，有点类似于 [`docker compose`](https://docs.docker.com/compose/overview/#compose-documentation) 实现的功能。但在这方面，[Kompose](http://kompose.io/) 是更好的工具，可以通过 [复合 YAML 文件](https://docs.docker.com/compose/compose-file/) 在 Kubernetes 集群中运行容器。按计划，我们不会实现类似于 [`swarm`] 的 Docker 命令，这部分功能最好由 Kubernetes 本身完成。


目前看来，已经持续数年的 Docker 模块化努力终将硕果累累。但目前 `kpod` 处于快速迭代过程中，不太适合用于生产环境，不过那些工具的与众不同的设计理念让人很感兴趣，而且其中大部分的工具已经可以用于开发环境。目前只能通过编译源码的方式安装 libpod，但最终会提供各个发行版的二进制包。



> 
> 本文[最初发表](https://lwn.net/Articles/741841/)于 [Linux Weekly News](http://lwn.net/)。
> 
> 
> 




---


via: <https://anarc.at/blog/2017-12-20-docker-without-docker/>


作者：[Anarcat](https://anarc.at) 译者：[pinewall](https://github.com/pinewall) 校对：[wxy](https://github.com/wxy)


本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译，[Linux中国](https://linux.cn/) 荣誉推出


OK
# Containers without Docker at Red Hat

This is one part of my coverage of KubeCon Austin 2017. Other articles include:


[An overview of KubeCon + CloudNativeCon]- Docker without Docker at Red Hat (this article)
[Demystifying Container Runtimes][Monitoring with Prometheus 2.0][Changes in Prometheus 2.0][The cost of hosting in the cloud]

The Docker (now [Moby](https://mobyproject.org/)) project has done a lot
to popularize containers in recent years. Along the way, though, it has
generated concerns about its concentration of functionality into a
single, monolithic system under the control of a single daemon running
with root privileges: `dockerd`

. Those concerns were reflected in a
[talk](https://kccncna17.sched.com/event/CU8j/cri-o-hosted-by-daniel-walsh-red-hat)
by Dan Walsh, head of the container team at Red Hat, at [KubeCon +
CloudNativeCon](http://events.linuxfoundation.org/events/kubecon-and-cloudnativecon-north-america).
Walsh spoke about the work the container team is doing to replace Docker
with a set of smaller, interoperable components. His rallying cry is "no
big fat daemons" as he finds them to be contrary to the venerated Unix
philosophy.

# The quest to modularize Docker

As we saw in an [earlier article](../2017-12-20-demystifying-container-runtimes/), the
basic set of container operations is not that complicated: you need to
pull a container image, create a container from the image, and start it.
On top of that, you need to be able to build images and push them to a
registry. Most people still use Docker for all of those steps but, as it
turns out, Docker isn't the only name in town anymore: an early
alternative was `rkt`

, which led to the creation of various standards
like CRI (runtime), OCI (image), and CNI (networking) that allow
backends like [CRI-O](http://cri-o.io/) or Docker to interoperate with,
for example, [Kubernetes](https://kubernetes.io/).

These standards led Red Hat to create a set of "core utils" like the
CRI-O runtime that implements the parts of the standards that Kubernetes
needs. But Red Hat's [OpenShift](https://www.openshift.com/) project
needs more than what Kubernetes provides. Developers will want to be
able to build containers and push them to the registry. Those operations
need a whole different bag of tricks.

It turns out that there are multiple tools to build containers right
now. Apart from Docker itself, a
[session](https://kccncna17.sched.com/event/CU6B/building-better-containers-a-survey-of-container-build-tools-i-michael-ducy-chef)
from Michael Ducy of Sysdig reviewed eight image builders, and that's
probably not all of them. Ducy identified the ideal build tool as one
that would create a minimal image in a reproducible way. A minimal image
is one where there is no operating system, only the application and its
essential dependencies. Ducy identified
[Distroless](https://github.com/GoogleCloudPlatform/distroless),
[Smith](https://github.com/oracle/smith), and
[Source-to-Image](https://github.com/openshift/source-to-image) as good
tools to build minimal images, which he called "micro-containers".

A reproducible container is one that you can build multiple times and
always get the same result. For that, Ducy said you have to use a
"declarative" approach (as opposed to "imperative"), which is
understandable given that he comes from the Chef
configuration-management world. He gave the examples of [Ansible
Container](https://www.ansible.com/ansible-container),
[Habitat](https://www.habitat.sh/),
[nixos-container](https://nixos.org/nixos/manual/#ch-containers), and
Smith (yes, again) as being good approaches, provided you were familiar
with their domain-specific languages. He added that Habitat ships its
own supervisor in its containers, which may be superfluous if you
already have an external one, like systemd, Docker, or Kubernetes. To
complete the list, we should mention the new
[BuildKit](https://github.com/moby/buildkit) from Docker and
[Buildah](https://github.com/projectatomic/buildah), which is part of
Red Hat's [Project Atomic](https://www.projectatomic.io/).

# Building containers with Buildah

Buildah's name apparently comes from Walsh's colorful

[Boston
accent](https://en.wikipedia.org/wiki/Boston_accent); the Boston theme
permeates the branding of the tool: the logo, for example, is a Boston
terrier dog (seen at right). This project takes a different approach
from Ducy's decree: instead of enforcing a declarative
configuration-management approach to containers, why not build simple
tools that can be used by your favorite configuration-management tool?
If you want to use regular command-line commands like `cp`

(instead of
Docker's custom `COPY`

directive, for example), you can. But you can
also use Ansible or Puppet, OS-specific or language-specific installers
like APT or pip, or whatever other system to provision the content of
your containers. This is what building a container looks like with
regular shell commands and simply using `make`

to install a binary
inside the container:

```
# pull a base image, equivalent to a Dockerfile's FROM command
buildah from redhat
# mount the base image to work on it
crt=$(buildah mount)
cp foo $crt
make install DESTDIR=$crt
# then make a snapshot
buildah commit
```


An interesting thing with this approach is that, since you reuse normal
build tools from the host environment, you can build really minimal
images because you don't need to install all the dependencies in the
image. Usually, when building a container image, the target application
build dependencies need to be installed within the container. For
example, building from source usually requires a compiler toolchain in
the container, because it is not meant to access the host environment. A
lot of containers will also ship basic Unix tools like `ps`

or `bash`

which are not actually necessary in a micro-container. Developers often
forget to (or simply can't) remove some dependencies from the built
containers; that common practice creates unnecessary overhead and attack
surface.

The modular approach of Buildah means you can run at least parts of the
build as non-root: the `mount`

command still needs the `CAP_SYS_ADMIN`

capability, but there is an
[issue](https://github.com/projectatomic/buildah/issues/171) open to
resolve this. However, Buildah
[shares](https://github.com/projectatomic/buildah/issues/158) the same
[limitation](https://github.com/moby/moby/issues/27886#issuecomment-281278525)
as Docker in that it can't build containers inside containers. For
Docker, you need to run the container in "privileged" mode, which is not
possible in certain environments (like [GitLab Continuous
Integration](https://about.gitlab.com/features/gitlab-ci-cd/), for
example) and, even when it is possible, the configuration is
[messy](https://jpetazzo.github.io/2015/09/03/do-not-use-docker-in-docker-for-ci/)
at best.

The manual commit step allows fine-grained control over when to create container snapshots. While in a Dockerfile every line creates a new snapshot, with Buildah commit checkpoints are explicitly chosen, which reduces unnecessary snapshots and saves disk space. This is useful to isolate sensitive material like private keys or passwords which sometimes mistakenly end up in public images as well.

While Docker builds non-standard, Docker-specific images, Buildah
produces standard OCI images among [other output
formats](https://github.com/projectatomic/buildah/blob/master/docs/buildah-push.md).
For backward compatibility, it has a command called
`build-using-dockerfile`

or
[ buildah bud](https://github.com/projectatomic/buildah/blob/master/docs/buildah-bud.md)
that parses normal Dockerfiles. Buildah has a

`enter`

command to inspect
images from the inside directly and a `run`

command to start containers
on the fly. It does all the work without any "fat daemon" running in the
background and uses standard tools like `runc`

.Ducy's criticism of Buildah was that it was not declarative, which made
it less reproducible. When allowing shell commands anything can happen:
for example, a shell script might download arbitrary binaries, without
any way of subsequently retracing where those come from. Shell command
effects may vary according to the environment. In contrast to
shell-based tools, configuration-management systems like Puppet or Chef
are designed to "converge" over a final configuration that is more
reliable, at least in theory: in practice you can call shell commands
from configuration-management systems. Walsh, however, argued that
existing configuration management *can* be used on top of Buildah, but
it doesn't *force* users down that path. This fits well with the classic
"separation" principle of the Unix philosophy ("mechanism not policy").

At this point, Buildah is in beta and Red Hat is working on integrating it into OpenShift. I have tested Buildah while writing this article and, short of some documentation issues, it generally works reliably. It could use some polishing in error handling, but it is definitely a great asset to add to your container toolbox.

# Replacing the rest of the Docker command-line

Walsh continued his presentation by giving an overview of another
project that Red Hat is working on, tentatively called
[libpod](https://github.com/projectatomic/libpod). The name derives from
a "pod" in Kubernetes, which is a way to group containers inside a host,
to share namespaces, for example.

Libpod includes the `kpod`

command to inspect and manipulate container
storage directly. Walsh explained this can be useful if, for example,
`dockerd`

hangs or if a Kubernetes cluster crashes. `kpod`

is basically
an independent re-implementation of the `docker`

command-line tool.
There is a command to list running containers (`kpod ps`

) or images
(`kpod images`

). In fact, there is a [translation cheat
sheet](https://github.com/projectatomic/libpod/blob/master/transfer.md#development-transfer)
documenting all Docker commands with a `kpod`

equivalent.

One of the nice things with the modular approach is that when you run a
container with `kpod run`

, the container is directly started as a
subprocess of the current shell, instead of a subprocess of `dockerd`

.
In theory, this allows running containers directly from systemd,
removing the duplicate work `dockerd`

is doing. It enables things like
[socket-activated
containers](http://0pointer.de/blog/projects/socket-activated-containers.html),
which is something that is [not
straightforward](https://legacy-developer.atlassian.com/blog/2015/03/docker-systemd-socket-activation/)
to do with Docker, or [even with
Kubernetes](https://github.com/kubernetes/kubernetes/issues/484) right
now. In my experiments, however, I have found that containers started
with `kpod`

lack some fundamental functionality, namely networking (!),
although there is an [issue in
progress](https://github.com/projectatomic/libpod/issues/129) to
complete that implementation.

A final command we haven't covered is `push`

. While the above commands
provide a good process for working with local containers, they don't
cover remote registries, which allow developers to actively collaborate
on application packaging. Registries are also an essential part of a
continuous-deployment framework. This is where the
[skopeo](https://github.com/projectatomic/skopeo) project comes in.
Skopeo is another Atomic project that "performs various operations on
container images and image repositories", according to the `README`

file. It was originally designed to inspect the contents of container
registries without actually downloading the sometimes voluminous images
as `docker pull`

does. Docker [refused
patches](https://github.com/moby/moby/pull/14258) to support inspection,
suggesting the creation of a separate tool, which led to Skopeo. After
`pull`

, `push`

was the logical next step and Skopeo can now do a bunch
of other things like copying and converting images between registries
without having to store a copy locally. Because this functionality was
useful to other projects as well, a lot of the Skopeo code now lives in
a reusable library called
[containers/image](https://github.com/containers/image). That library is
in turn used by [Pivotal](https://pivotal.io/), Google's
[container-diff](https://github.com/GoogleCloudPlatform/container-diff),
`kpod push`

, and `buildah push`

.

`kpod`

is not directly tied to Kubernetes, so the name might change in
the future — especially since Red Hat legal has not cleared the name
yet. (In fact, just as this article was going to "press", the name was
changed to
[ podman](https://github.com/projectatomic/libpod/blob/master/docs/podman.1.md).)
The team wants to implement more "pod-level" commands which would allow
operations on multiple containers, a bit like what

[might do. But at that level, a better tool might be](https://docs.docker.com/compose/overview/#compose-documentation)

`docker compose`

[Kompose](http://kompose.io/)which can execute

[Compose YAML files](https://docs.docker.com/compose/compose-file/)into a Kubernetes cluster. Some Docker commands (like

[) will never be implemented, on purpose, as they are best left for Kubernetes itself to handle.](https://docs.docker.com/engine/swarm/)

`swarm`

It seems that the effort to modularize Docker that started a few years
ago is finally bearing fruit. While, at this point, `kpod`

is under
heavy development and probably should not be used in production, the
design of those different tools is certainly interesting; a lot of it is
ready for development environments. Right now, the only way to install
libpod is to compile it from source, but we should expect packages
coming out for your favorite distribution eventually.


This article[first appeared]in the[Linux Weekly News].

[Edited .](https://gitlab.com/anarcat/anarc.at/-/commits/main/blog/2017-12-20-docker-without-docker.mdwn)