<?xml version='1.0' encoding='UTF-8'?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
  <id>https://www.rpatterson.net/</id>
  <title>Ross Patterson's Blog - Posts tagged docker-compose</title>
  <updated>2025-07-14T00:00:49.261228+00:00</updated>
  <link href="https://www.rpatterson.net/"/>
  <link href="https://www.rpatterson.net/blog/tag/docker-compose/atom.xml" rel="self"/>
  <generator uri="https://ablog.readthedocs.io/" version="0.11.12">ABlog</generator>
  <entry>
    <id>https://www.rpatterson.net/blog/dind-reproduce-deploys/</id>
    <title>Reproducing Deployments with Docker-in-Docker</title>
    <updated>2021-04-09T00:00:00+00:00</updated>
    <author>
      <name>Ross Patterson</name>
    </author>
    <content type="html">&lt;section id="reproducing-deployments-with-docker-in-docker"&gt;

&lt;blockquote&gt;
&lt;div&gt;&lt;p&gt;Debugging deployment issues locally by falling down the &lt;a class="reference external" href="https://hub.docker.com/_/docker"&gt;Docker-in-Docker&lt;/a&gt; (DinD)
rabbit hole.&lt;/p&gt;
&lt;/div&gt;&lt;/blockquote&gt;
&lt;p&gt;TL;DR:&lt;/p&gt;
&lt;blockquote&gt;
&lt;div&gt;&lt;p&gt;You can run a &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;#&lt;/span&gt; &lt;span class="pre"&gt;dockerd&lt;/span&gt;&lt;/code&gt; daemon inside a &lt;a class="reference external" href="https://docs.docker.com/"&gt;Docker&lt;/a&gt; container which can be used with
&lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;$&lt;/span&gt; &lt;span class="pre"&gt;docker-compose&lt;/span&gt; &lt;span class="pre"&gt;...&lt;/span&gt;&lt;/code&gt; to (almost) completely reproduce hosted deployment
environments on one’s development laptop.&lt;/p&gt;
&lt;/div&gt;&lt;/blockquote&gt;
&lt;p&gt;As noted in &lt;a class="reference external" href="../docker-gotchas/"&gt;a recent post&lt;/a&gt;, I seem to have become the &lt;a class="reference external" href="https://docs.docker.com/"&gt;Docker&lt;/a&gt; wrangler on the team
for my recent projects.  Also recently, I’m getting the distinct impression that
containerization has or is beginning to reach that special point in the life-cycle of a
technology where it is sufficiently widely adopted to be misused and has been in use
long enough for that misuse to hurt.  This is one of those ironic signs of success and
utility but knowing that doesn’t lessen the pain or cost of the individual case one may
run into.&lt;/p&gt;
&lt;p&gt;Most recently, I was dealing with problematically fragile container-based deployments
where the team was getting bitten over and over again by deployment details that weren’t
causing problems in our local container-based development environments but were causing
problems when CD pushed our changes to real hosted deployments.  Specifically, the
hosted deployments were failing on such things as filesystem paths and IP addresses.  To
make things worse, the internal CI/CD pipeline was under-resourced and very slow making
the inner loop of testing changes painfully slow and wasteful.&lt;/p&gt;
&lt;p&gt;Before you think it, this is a project where there are many things that need to be
re-worked to be in alignment with current best practices and thus less fragile: from how
containers are used, to keeping CI/CD responsive, to how deployment is orchestrated.
Deployments should &lt;em&gt;not&lt;/em&gt; be sensitive to changes in filesystem paths and IP addresses,
to be sure.  The project was a &lt;a class="reference external" href="https://docs.python.org/3/howto/pyporting.html"&gt;Python 3 upgrade&lt;/a&gt;, however, and we’d done just about
all there was appetite for in terms of cleanup work that wasn’t directly related to the
upgrade.  As software developers, it’s our job to figure out how to solve difficult
technical problems, including the difficulty of getting sub-optimal technology usage to
work.&lt;/p&gt;
&lt;p&gt;At any rate, the tax we were paying for this deployment sensitivity was so high and was
happening often enough that I decided it was past time to work on a way to try and
reproduce the real hosted deployment environment more completely, preferably locally.
Reproducing deployment hosts as thoroughly as possible without turning my development
laptop in to the deployment hosts feels like an isolation issue, so I reached for
containers and Docker.  Specifically, I wanted to start with a container image as close
to the VM image used by the deployment hosts as possible and figure out how to run the
actual application container images within those “host containers”.  IOW, can I run
Docker containers within a Docker container?  Yup, &lt;a class="reference external" href="https://www.docker.com/blog/docker-can-now-run-within-docker/"&gt;for some time now&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;It turns out that Docker now provides an &lt;a class="reference external" href="https://hub.docker.com/_/docker"&gt;official Docker-in-Docker (DinD) image&lt;/a&gt;.
After reading it’s &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;./Dockerfile&lt;/span&gt;&lt;/code&gt; and some blog posts detailing how to run DinD, I
decided it would be more efficient and maintainable to use the official &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;docker:dind&lt;/span&gt;&lt;/code&gt;
image as my base image and extend it to match the project’s deployment hosts rather than
the other way around, start with a base image close to the deployment hosts and get DinD
working in those.  A quick &lt;a class="reference download internal" download="" href="../../../_downloads/3a417606cbc7bb8ed3e711209c9136b3/Dockerfile"&gt;&lt;code class="xref download docutils literal notranslate"&gt;&lt;span class="pre"&gt;./Dockerfile&lt;/span&gt;&lt;/code&gt;&lt;/a&gt; reproduced the
deployment host content in an image, including deployment host filesystem paths and
users, e.g. &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;/home/ec2-user/&lt;/span&gt;&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight-dockerfile notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;docker:dind&lt;/span&gt;
&lt;span class="c"&gt;# Defensive shell settings, avoid silent failures&lt;/span&gt;
&lt;span class="k"&gt;SHELL&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/bin/ash&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;-xeu&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;-c&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="c"&gt;# Install required OS host packages&lt;/span&gt;
&lt;span class="k"&gt;RUN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;apk&lt;span class="w"&gt; &lt;/span&gt;--no-cache&lt;span class="w"&gt; &lt;/span&gt;add&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;py-pip&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;python3-dev&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;libffi-dev&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;openssl-dev&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;gcc&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;libc-dev&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;rust&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;cargo&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;make&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;pip3&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;docker-compose&amp;#39;&lt;/span&gt;

&lt;span class="c"&gt;# Duplicate the AWS EC2 user&lt;/span&gt;
&lt;span class="k"&gt;RUN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;adduser&lt;span class="w"&gt; &lt;/span&gt;--disabled-password&lt;span class="w"&gt; &lt;/span&gt;--gecos&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;EC2 User,,,&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;ec2-user
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;A &lt;a class="reference download internal" download="" href="../../../_downloads/39f6a6af29cc8b4aa915a8f169b656cf/docker-compose.yml"&gt;&lt;code class="xref download docutils literal notranslate"&gt;&lt;span class="pre"&gt;./docker-compose.yml&lt;/span&gt;&lt;/code&gt;&lt;/a&gt; file reproduces the running
deployment hosts including network topology such as subnet and host IP addresses.  I
also abuse the &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;./docker-compose.yml&lt;/span&gt;&lt;/code&gt; file to build &lt;a class="reference download internal" download="" href="../../../_downloads/054317702b0a3c1298bd02befac454ef/Dockerfile"&gt;&lt;code class="xref download docutils literal notranslate"&gt;&lt;span class="pre"&gt;the&lt;/span&gt; &lt;span class="pre"&gt;application&lt;/span&gt; &lt;span class="pre"&gt;images&lt;/span&gt;&lt;/code&gt;&lt;/a&gt;:&lt;/p&gt;
&lt;div class="highlight-yaml notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nt"&gt;version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;3.8&amp;quot;&lt;/span&gt;

&lt;span class="nt"&gt;networks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;ipam&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;driver&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;default&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;config&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;# Match the hosted deployment network&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;subnet&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;10.81.82.0/24&amp;quot;&lt;/span&gt;

&lt;span class="nt"&gt;services&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;

&lt;span class="c1"&gt;# Build custom container image, to be loaded into the nested host Docker daemons.&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;foo-app&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;build&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;./foo-app/&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;bar-company/foo-app:dev&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;# Build only, don&amp;#39;t actually run as a service&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;entrypoint&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p p-Indicator"&gt;[&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;true&amp;quot;&lt;/span&gt;&lt;span class="p p-Indicator"&gt;]&lt;/span&gt;

&lt;span class="c1"&gt;# Emulate deployment hosts running Docker daemons&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;foo-host&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;build&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;./&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;bar-company/foo-host:dev&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;# Just build the image, don&amp;#39;t actually run as a service&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;entrypoint&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p p-Indicator"&gt;[&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;true&amp;quot;&lt;/span&gt;&lt;span class="p p-Indicator"&gt;]&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;foo-host-corge&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;bar-company/foo-host:dev&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;depends_on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;foo-host&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;privileged&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;true&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;networks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;# Match the hosted deployment IP&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;ipv4_address&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;10.81.82.100&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;volumes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="c1"&gt;# Docker-in-Docker requires data on a real filesystem&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;./var/lib/foo-host-corge/docker/:/var/lib/docker/&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="c1"&gt;# Reproduce where project data is stored in hosted deployments&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;./var/lib/foo-data/:/home/ec2-user/foo-data/&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="c1"&gt;# Make source editable&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;./foo-app/:/srv/foo-app/&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="c1"&gt;# Reproduce deployment containers using a `$ docker-compose ...` configuration&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="c1"&gt;# specific to the deployment host&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;./foo-host-corge/:/srv/foo-host-corge/&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;working_dir&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;/srv/foo-host-corge/&amp;quot;&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;foo-host-grault&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;bar-company/foo-host:dev&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;depends_on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;foo-host&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;privileged&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;true&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;networks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;# Match the hosted deployment IP&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;ipv4_address&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;10.81.82.101&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;volumes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="c1"&gt;# Docker-in-Docker requires data on a real filesystem&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;./var/lib/foo-host-grault/docker/:/var/lib/docker/&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="c1"&gt;# Reproduce where project data is stored in hosted deployments&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;./var/lib/foo-data/:/home/ec2-user/foo-data/&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="c1"&gt;# Make source editable&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;./foo-app/:/srv/foo-app/&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="c1"&gt;# Reproduce deployment containers using a `$ docker-compose ...` configuration&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="c1"&gt;# specific to the deployment host&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;./foo-host-grault/:/srv/foo-host-grault/&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;working_dir&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;/srv/foo-host-grault/&amp;quot;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Nested &lt;a class="reference download internal" download="" href="../../../_downloads/abbd0e228e5961c04e64b6912ef76c57/docker-compose.yml"&gt;&lt;code class="xref download docutils literal notranslate"&gt;&lt;span class="pre"&gt;./foo-host-corge/docker-compose.yml&lt;/span&gt;&lt;/code&gt;&lt;/a&gt; and
&lt;a class="reference download internal" download="" href="../../../_downloads/7c8cd03aca75b1de1f3233766103c84b/docker-compose.yml"&gt;&lt;code class="xref download docutils literal notranslate"&gt;&lt;span class="pre"&gt;./foo-host-grault/docker-compose.yml&lt;/span&gt;&lt;/code&gt;&lt;/a&gt;
files reproduce how the application containers are run on the real deployment hosts, but
run instead within the nested DinD containers that reproduce the deployment hosts:&lt;/p&gt;
&lt;div class="highlight-yaml notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nt"&gt;version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;3.8&amp;quot;&lt;/span&gt;

&lt;span class="nt"&gt;services&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;postgres&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;postgres&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;ports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;5432:5432&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;env_file&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;./.env&amp;quot;&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;foo-app&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;bar-company/foo-app:dev&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;ports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;80:80&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;extra_hosts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;redis:10.81.82.101&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;volumes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="c1"&gt;# Make source editable&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;/srv/foo-app/:/srv/foo-app/&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="c1"&gt;# Reproduce where project data is stored in hosted deployments&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;/home/ec2-user/foo-data/:/srv/foo-data/&amp;quot;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="highlight-yaml notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nt"&gt;version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;3.8&amp;quot;&lt;/span&gt;

&lt;span class="nt"&gt;services&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;redis&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;ports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;6379:6379&amp;quot;&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;foo-app&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;bar-company/foo-app:dev&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;ports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;80:80&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;extra_hosts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;postgres:10.81.82.100&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;volumes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="c1"&gt;# Make source editable&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;/srv/foo-app/:/srv/foo-app/&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="c1"&gt;# Reproduce where project data is stored in hosted deployments&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;/home/ec2-user/foo-data/:/srv/foo-data/&amp;quot;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Use some &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;$&lt;/span&gt; &lt;span class="pre"&gt;docker&lt;/span&gt; &lt;span class="pre"&gt;...&lt;/span&gt;&lt;/code&gt; commands and some shell to push the application images into
the nested &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;#&lt;/span&gt; &lt;span class="pre"&gt;dockerd&lt;/span&gt;&lt;/code&gt; daemons and some &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;$&lt;/span&gt; &lt;span class="pre"&gt;docker-compose&lt;/span&gt; &lt;span class="pre"&gt;...&lt;/span&gt;&lt;/code&gt; commands to run
containers using those images, along with the rest of the deployment configuration.  I
use a &lt;a class="reference download internal" download="" href="../../../_downloads/64071479609507d0a270e2eba4423b83/Makefile"&gt;&lt;code class="xref download docutils literal notranslate"&gt;&lt;span class="pre"&gt;./Makefile&lt;/span&gt;&lt;/code&gt;&lt;/a&gt; to stitch this all together but use what you
like:&lt;/p&gt;
&lt;div class="highlight-makefile notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c"&gt;# Reproduce hosted deployments in nested Docker (DinD)&lt;/span&gt;

&lt;span class="c"&gt;### Defensive settings for make:&lt;/span&gt;
&lt;span class="c"&gt;#     https://tech.davis-hansson.com/p/make/&lt;/span&gt;
&lt;span class="nv"&gt;SHELL&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;bash
&lt;span class="nf"&gt;.ONESHELL&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
&lt;span class="nv"&gt;.SHELLFLAGS&lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;-xeu&lt;span class="w"&gt; &lt;/span&gt;-o&lt;span class="w"&gt; &lt;/span&gt;pipefail&lt;span class="w"&gt; &lt;/span&gt;-O&lt;span class="w"&gt; &lt;/span&gt;inherit_errexit&lt;span class="w"&gt; &lt;/span&gt;-c
&lt;span class="nf"&gt;.SILENT&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
&lt;span class="nf"&gt;.DELETE_ON_ERROR&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
&lt;span class="nv"&gt;MAKEFLAGS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;--warn-undefined-variables
&lt;span class="nv"&gt;MAKEFLAGS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;--no-builtin-rules


&lt;span class="c"&gt;# Top-level targets&lt;/span&gt;

&lt;span class="nf"&gt;.PHONY&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;all&lt;/span&gt;
&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;var&lt;/span&gt;/&lt;span class="n"&gt;log&lt;/span&gt;/&lt;span class="n"&gt;docker&lt;/span&gt;-&lt;span class="n"&gt;compose&lt;/span&gt;-&lt;span class="n"&gt;build&lt;/span&gt;.&lt;span class="n"&gt;log&lt;/span&gt; &lt;span class="n"&gt;foo&lt;/span&gt;-&lt;span class="n"&gt;host&lt;/span&gt;-&lt;span class="n"&gt;corge&lt;/span&gt;/.&lt;span class="n"&gt;env&lt;/span&gt;

&lt;span class="nf"&gt;.PHONY&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;
&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;all&lt;/span&gt;
&lt;span class="w"&gt;	&lt;/span&gt;docker-compose&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;exec&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-T&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;foo-host-corge&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;docker-compose&lt;span class="w"&gt; &lt;/span&gt;up&lt;span class="w"&gt; &lt;/span&gt;-d
&lt;span class="w"&gt;	&lt;/span&gt;docker-compose&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;exec&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-T&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;foo-host-grault&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;docker-compose&lt;span class="w"&gt; &lt;/span&gt;up&lt;span class="w"&gt; &lt;/span&gt;-d
&lt;span class="w"&gt;	&lt;/span&gt;sleep&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;
&lt;span class="w"&gt;	&lt;/span&gt;docker-compose&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;exec&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-T&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;foo-host-corge&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;docker-compose&lt;span class="w"&gt; &lt;/span&gt;ps
&lt;span class="w"&gt;	&lt;/span&gt;docker-compose&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;exec&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-T&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;foo-host-grault&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;docker-compose&lt;span class="w"&gt; &lt;/span&gt;ps

&lt;span class="nf"&gt;.PHONY&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;test&lt;/span&gt;
&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;
&lt;span class="c"&gt;# Demonstrate that deployment host network topology and hostnames are reproduced&lt;/span&gt;
&lt;span class="w"&gt;	&lt;/span&gt;docker-compose&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;exec&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;foo-host-corge&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;	    &lt;/span&gt;docker-compose&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;exec&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;foo-app&lt;span class="w"&gt; &lt;/span&gt;nc&lt;span class="w"&gt; &lt;/span&gt;-vz&lt;span class="w"&gt; &lt;/span&gt;redis&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;6379&lt;/span&gt;
&lt;span class="w"&gt;	&lt;/span&gt;docker-compose&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;exec&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;foo-host-grault&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;	    &lt;/span&gt;docker-compose&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;exec&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;foo-app&lt;span class="w"&gt; &lt;/span&gt;nc&lt;span class="w"&gt; &lt;/span&gt;-vz&lt;span class="w"&gt; &lt;/span&gt;postgres&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;5432&lt;/span&gt;
&lt;span class="c"&gt;# Demonstrate that deployment host filesystem paths and data are reproduced&lt;/span&gt;
&lt;span class="w"&gt;	&lt;/span&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;rm&lt;span class="w"&gt; &lt;/span&gt;-rfv&lt;span class="w"&gt; &lt;/span&gt;./var/lib/foo-data/*
&lt;span class="w"&gt;	&lt;/span&gt;ls&lt;span class="w"&gt; &lt;/span&gt;-al&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;./var/lib/foo-data/&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;	&lt;/span&gt;docker-compose&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;exec&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;foo-host-corge&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;	    &lt;/span&gt;ls&lt;span class="w"&gt; &lt;/span&gt;-al&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/home/ec2-user/foo-data/&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;	&lt;/span&gt;docker-compose&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;exec&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;foo-host-grault&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;	    &lt;/span&gt;ls&lt;span class="w"&gt; &lt;/span&gt;-al&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/home/ec2-user/foo-data/&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;	&lt;/span&gt;docker-compose&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;exec&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;foo-host-corge&lt;span class="w"&gt; &lt;/span&gt;docker-compose&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;exec&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;foo-app&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;	    &lt;/span&gt;ls&lt;span class="w"&gt; &lt;/span&gt;-al&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/srv/foo-data/&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;	&lt;/span&gt;docker-compose&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;exec&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;foo-host-grault&lt;span class="w"&gt; &lt;/span&gt;docker-compose&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;exec&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;foo-app&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;	    &lt;/span&gt;ls&lt;span class="w"&gt; &lt;/span&gt;-al&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/srv/foo-data/&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;	&lt;/span&gt;docker-compose&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;exec&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;foo-host-grault&lt;span class="w"&gt; &lt;/span&gt;docker-compose&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;exec&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;foo-app&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;	    &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;!&lt;span class="w"&gt; &lt;/span&gt;-e&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/srv/foo-data/bar.txt&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;	&lt;/span&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;touch&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;./var/lib/foo-data/bar.txt&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;	&lt;/span&gt;docker-compose&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;exec&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;foo-host-corge&lt;span class="w"&gt; &lt;/span&gt;docker-compose&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;exec&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;foo-app&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;	    &lt;/span&gt;ls&lt;span class="w"&gt; &lt;/span&gt;-al&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/srv/foo-data/&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;	&lt;/span&gt;docker-compose&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;exec&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;foo-host-grault&lt;span class="w"&gt; &lt;/span&gt;docker-compose&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;exec&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;foo-app&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;	    &lt;/span&gt;ls&lt;span class="w"&gt; &lt;/span&gt;-al&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/srv/foo-data/&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;	&lt;/span&gt;docker-compose&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;exec&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;foo-host-grault&lt;span class="w"&gt; &lt;/span&gt;docker-compose&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;exec&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;foo-app&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;	    &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-e&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/srv/foo-data/bar.txt&amp;quot;&lt;/span&gt;

&lt;span class="nf"&gt;.PHONY&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;clean&lt;/span&gt;
&lt;span class="nf"&gt;clean&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;	&lt;/span&gt;docker-compose&lt;span class="w"&gt; &lt;/span&gt;down&lt;span class="w"&gt; &lt;/span&gt;--rmi&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;local&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-v
&lt;span class="w"&gt;	&lt;/span&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;rm&lt;span class="w"&gt; &lt;/span&gt;-rf&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;./var/lib/&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;	&lt;/span&gt;mkdir&lt;span class="w"&gt; &lt;/span&gt;-pv&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;./var/log/backups/&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;!&lt;span class="w"&gt; &lt;/span&gt;-e&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;./var/log/docker-compose-build.log&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;||&lt;/span&gt;
&lt;span class="w"&gt;	    &lt;/span&gt;mv&lt;span class="w"&gt; &lt;/span&gt;--backup&lt;span class="o"&gt;=&lt;/span&gt;numbered&lt;span class="w"&gt; &lt;/span&gt;-v&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;./var/log/docker-compose-build.log&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;	        &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;./var/log/backups/&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;!&lt;span class="w"&gt; &lt;/span&gt;-e&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;./foo-host-corge/.env&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;||&lt;/span&gt;
&lt;span class="w"&gt;	    &lt;/span&gt;mv&lt;span class="w"&gt; &lt;/span&gt;--backup&lt;span class="o"&gt;=&lt;/span&gt;numbered&lt;span class="w"&gt; &lt;/span&gt;-v&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;./foo-host-corge/.env&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;	        &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;./var/log/backups/&amp;quot;&lt;/span&gt;

&lt;span class="c"&gt;# Real targets&lt;/span&gt;

&lt;span class="nf"&gt;var/log/docker-compose-build.log&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;foo&lt;/span&gt;-&lt;span class="n"&gt;app&lt;/span&gt;/&lt;span class="n"&gt;Dockerfile&lt;/span&gt; &lt;span class="n"&gt;Dockerfile&lt;/span&gt; &lt;span class="n"&gt;docker&lt;/span&gt;-&lt;span class="n"&gt;compose&lt;/span&gt;.&lt;span class="n"&gt;yml&lt;/span&gt;
&lt;span class="w"&gt;	&lt;/span&gt;mkdir&lt;span class="w"&gt; &lt;/span&gt;-pv&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;./&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;dir&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;@&lt;span class="k"&gt;))&lt;/span&gt;&lt;span class="s2"&gt;/&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;	&lt;/span&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;docker-compose&lt;span class="w"&gt; &lt;/span&gt;build&lt;span class="w"&gt; &lt;/span&gt;--pull&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;tee&lt;span class="w"&gt; &lt;/span&gt;-a&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;./&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;@&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;
&lt;span class="c"&gt;# Wait for the nested deployment host `# dorckerd` daemons to become available&lt;/span&gt;
&lt;span class="w"&gt;	&lt;/span&gt;docker-compose&lt;span class="w"&gt; &lt;/span&gt;up&lt;span class="w"&gt; &lt;/span&gt;-d
&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;host&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;foo-host-corge&lt;span class="w"&gt; &lt;/span&gt;foo-host-grault
&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;
&lt;span class="w"&gt;	    &lt;/span&gt;timeout&lt;span class="w"&gt; &lt;/span&gt;--foreground&lt;span class="w"&gt; &lt;/span&gt;-v&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;20&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;SHELL&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;.SHELLFLAGS&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;\&lt;/span&gt;
&lt;span class="s2"&gt;		while ! docker-compose exec -T &amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$$&lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;host&lt;span class="o"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot; docker ps; do sleep 0.1; done&amp;quot;&lt;/span&gt;
&lt;span class="c"&gt;# Load the built image into the deployment host Docker daemons&lt;/span&gt;
&lt;span class="w"&gt;	    &lt;/span&gt;docker&lt;span class="w"&gt; &lt;/span&gt;save&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;bar-company/foo-app:dev&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;
&lt;span class="w"&gt;		&lt;/span&gt;docker-compose&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;exec&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-T&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$$&lt;/span&gt;&lt;span class="s2"&gt;{host}&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;docker&lt;span class="w"&gt; &lt;/span&gt;load
&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;done&lt;/span&gt;

&lt;span class="nf"&gt;foo-host-corge/.env&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;POSTGRES_PASSWORD=&lt;/span&gt;&lt;span class="nv"&gt;$$&lt;/span&gt;&lt;span class="s2"&gt;(apg -M NCL -n 1)&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&lt;span class="s2"&gt;&amp;quot;./&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;@&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Once it’ up and running we can see that the network topology, host names, and
containerized services and applications are running as they would in the real hosted
deployment:&lt;/p&gt;
&lt;div class="highlight-shell-session notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;$ &lt;/span&gt;make&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;
&lt;span class="go"&gt;+ docker-compose exec -T foo-host-corge docker-compose up -d&lt;/span&gt;
&lt;span class="go"&gt;foo-host-corge_foo-app_1 is up-to-date&lt;/span&gt;
&lt;span class="go"&gt;foo-host-corge_postgres_1 is up-to-date&lt;/span&gt;
&lt;span class="go"&gt;+ docker-compose exec -T foo-host-grault docker-compose up -d&lt;/span&gt;
&lt;span class="go"&gt;foo-host-grault_redis_1 is up-to-date&lt;/span&gt;
&lt;span class="go"&gt;foo-host-grault_foo-app_1 is up-to-date&lt;/span&gt;
&lt;span class="go"&gt;+ sleep 1&lt;/span&gt;
&lt;span class="go"&gt;+ docker-compose exec -T foo-host-corge docker-compose ps&lt;/span&gt;
&lt;span class="go"&gt;          Name                         Command               State           Ports&lt;/span&gt;
&lt;span class="go"&gt;-------------------------------------------------------------------------------------------&lt;/span&gt;
&lt;span class="go"&gt;foo-host-corge_foo-app_1    python -m http.server --di ...   Up      0.0.0.0:80-&amp;gt;80/tcp&lt;/span&gt;
&lt;span class="go"&gt;foo-host-corge_postgres_1   docker-entrypoint.sh postgres    Up      0.0.0.0:5432-&amp;gt;5432/tcp&lt;/span&gt;
&lt;span class="go"&gt;+ docker-compose exec -T foo-host-grault docker-compose ps&lt;/span&gt;
&lt;span class="go"&gt;          Name                         Command               State           Ports&lt;/span&gt;
&lt;span class="go"&gt;-------------------------------------------------------------------------------------------&lt;/span&gt;
&lt;span class="go"&gt;foo-host-grault_foo-app_1   python -m http.server --di ...   Up      0.0.0.0:80-&amp;gt;80/tcp&lt;/span&gt;
&lt;span class="go"&gt;foo-host-grault_redis_1     docker-entrypoint.sh redis ...   Up      0.0.0.0:6379-&amp;gt;6379/tcp&lt;/span&gt;
&lt;span class="go"&gt;+ docker-compose exec foo-host-corge docker-compose exec foo-app nc -vz redis 6379&lt;/span&gt;
&lt;span class="go"&gt;Ncat: Version 7.70 ( https://nmap.org/ncat )&lt;/span&gt;
&lt;span class="go"&gt;Ncat: Connected to 10.81.82.101:6379.&lt;/span&gt;
&lt;span class="go"&gt;Ncat: 0 bytes sent, 0 bytes received in 0.01 seconds.&lt;/span&gt;
&lt;span class="go"&gt;+ docker-compose exec foo-host-grault docker-compose exec foo-app nc -vz postgres 5432&lt;/span&gt;
&lt;span class="go"&gt;Ncat: Version 7.70 ( https://nmap.org/ncat )&lt;/span&gt;
&lt;span class="go"&gt;Ncat: Connected to 10.81.82.100:5432.&lt;/span&gt;
&lt;span class="go"&gt;Ncat: 0 bytes sent, 0 bytes received in 0.01 seconds.&lt;/span&gt;
&lt;span class="go"&gt;+ sudo rm -rfv &amp;#39;./var/lib/foo-data/*&amp;#39;&lt;/span&gt;
&lt;span class="go"&gt;+ ls -al ./var/lib/foo-data/&lt;/span&gt;
&lt;span class="go"&gt;total 8&lt;/span&gt;
&lt;span class="go"&gt;drwxr-xr-x 2 root root 4096 Apr  5 08:49 .&lt;/span&gt;
&lt;span class="go"&gt;drwxr-xr-x 5 root root 4096 Apr  5 08:45 ..&lt;/span&gt;
&lt;span class="go"&gt;+ docker-compose exec foo-host-corge ls -al /home/ec2-user/foo-data/&lt;/span&gt;
&lt;span class="go"&gt;total 8&lt;/span&gt;
&lt;span class="go"&gt;drwxr-xr-x    2 root     root          4096 Apr  5 15:49 .&lt;/span&gt;
&lt;span class="go"&gt;drwxr-sr-x    1 ec2-user ec2-user      4096 Apr  5 15:45 ..&lt;/span&gt;
&lt;span class="go"&gt;+ docker-compose exec foo-host-grault ls -al /home/ec2-user/foo-data/&lt;/span&gt;
&lt;span class="go"&gt;total 8&lt;/span&gt;
&lt;span class="go"&gt;drwxr-xr-x    2 root     root          4096 Apr  5 15:49 .&lt;/span&gt;
&lt;span class="go"&gt;drwxr-sr-x    1 ec2-user ec2-user      4096 Apr  5 15:45 ..&lt;/span&gt;
&lt;span class="go"&gt;+ docker-compose exec foo-host-corge docker-compose exec foo-app ls -al /srv/foo-data/&lt;/span&gt;
&lt;span class="go"&gt;total 8&lt;/span&gt;
&lt;span class="go"&gt;drwxr-xr-x 2 root root 4096 Apr  5 15:49 .&lt;/span&gt;
&lt;span class="go"&gt;drwxr-xr-x 1 root root 4096 Apr  5 15:45 ..&lt;/span&gt;
&lt;span class="go"&gt;+ docker-compose exec foo-host-grault docker-compose exec foo-app ls -al /srv/foo-data/&lt;/span&gt;
&lt;span class="go"&gt;total 8&lt;/span&gt;
&lt;span class="go"&gt;drwxr-xr-x 2 root root 4096 Apr  5 15:49 .&lt;/span&gt;
&lt;span class="go"&gt;drwxr-xr-x 1 root root 4096 Apr  5 15:45 ..&lt;/span&gt;
&lt;span class="go"&gt;+ docker-compose exec foo-host-grault docker-compose exec foo-app test &amp;#39;!&amp;#39; -e /srv/foo-data/bar.txt&lt;/span&gt;
&lt;span class="go"&gt;+ sudo touch ./var/lib/foo-data/bar.txt&lt;/span&gt;
&lt;span class="go"&gt;+ docker-compose exec foo-host-corge docker-compose exec foo-app ls -al /srv/foo-data/&lt;/span&gt;
&lt;span class="go"&gt;total 8&lt;/span&gt;
&lt;span class="go"&gt;drwxr-xr-x 2 root root 4096 Apr  5 15:50 .&lt;/span&gt;
&lt;span class="go"&gt;drwxr-xr-x 1 root root 4096 Apr  5 15:45 ..&lt;/span&gt;
&lt;span class="go"&gt;-rw-r--r-- 1 root root    0 Apr  5 15:50 bar.txt&lt;/span&gt;
&lt;span class="go"&gt;+ docker-compose exec foo-host-grault docker-compose exec foo-app ls -al /srv/foo-data/&lt;/span&gt;
&lt;span class="go"&gt;total 8&lt;/span&gt;
&lt;span class="go"&gt;drwxr-xr-x 2 root root 4096 Apr  5 15:50 .&lt;/span&gt;
&lt;span class="go"&gt;drwxr-xr-x 1 root root 4096 Apr  5 15:45 ..&lt;/span&gt;
&lt;span class="go"&gt;-rw-r--r-- 1 root root    0 Apr  5 15:50 bar.txt&lt;/span&gt;
&lt;span class="go"&gt;+ docker-compose exec foo-host-grault docker-compose exec foo-app test -e /srv/foo-data/bar.txt&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;There you have it.  &lt;a class="reference external" href="https://gitlab.com/rpatterson/dind-reproduce-deploys"&gt;The demo code is publicly available&lt;/a&gt;.  I think we can find more
uses for DinD.  In particular, I have always found local development clean build issues
to be a persistent plague: “It worked for me when I committed it!”.  Next up I’m working
on using DinD as a route to a generic local clean build test.&lt;/p&gt;
&lt;/section&gt;
</content>
    <link href="https://www.rpatterson.net/blog/dind-reproduce-deploys/"/>
    <summary>Debugging deployment issues locally by falling down the Docker-in-Docker (DinD)
rabbit hole.</summary>
    <category term="DinD" label="DinD"/>
    <category term="Docker" label="Docker"/>
    <category term="docker-compose" label="docker-compose"/>
    <published>2021-04-09T00:00:00+00:00</published>
  </entry>
  <entry>
    <id>https://www.rpatterson.net/blog/docker-gotchas/</id>
    <title>Docker Gotchas I’ve Encountered</title>
    <updated>2021-04-08T00:00:00+00:00</updated>
    <author>
      <name>Ross Patterson</name>
    </author>
    <content type="html">&lt;section id="docker-gotchas-i-ve-encountered"&gt;

&lt;blockquote&gt;
&lt;div&gt;&lt;p&gt;Time to share the snags/gotchas I’ve run into developing and deploying with Docker
containers.&lt;/p&gt;
&lt;/div&gt;&lt;/blockquote&gt;
&lt;p&gt;On my recent projects, I seem to have become the &lt;a class="reference external" href="https://docs.docker.com/"&gt;Docker&lt;/a&gt; wrangler on the team.  Over
that time, I’ve hit a number of snags or gotchas and thought, &lt;a class="reference external" href="https://www.google.com/search?q=%22far+side%22+%22maybe+we+should+write+that+spot+down%22&amp;amp;sxsrf=ALeKk03GSjfKEOtaGmJTX2YwOMbOL5NiNw:1617781214389&amp;amp;source=lnms&amp;amp;tbm=isch&amp;amp;sa=X&amp;amp;ved=2ahUKEwiEge7U0OvvAhUKP30KHVOPAnsQ_AUoAXoECAIQAw&amp;amp;biw=1536&amp;amp;bih=785&amp;amp;dpr=1.25"&gt;“Maybe I should write
that spot down?”&lt;/a&gt;.  It’s as good a time as any now that &lt;a class="reference external" href="../migrating-from-plone-to-ablog/"&gt;I’ve reduced the impedance of
my blogging platform&lt;/a&gt;.&lt;/p&gt;
&lt;nav class="contents local" id="contents"&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="#disk-space" id="id1"&gt;Disk Space&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="#publicly-exposed-ports" id="id2"&gt;Publicly Exposed Ports&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="#yaml-needs-some-zen" id="id3"&gt;YAML Needs Some Zen&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="#build-context-and-image-stowaways" id="id4"&gt;Build Context and Image Stowaways&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="#ttfn" id="id5"&gt;TTFN&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/nav&gt;
&lt;section id="disk-space"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#id1" role="doc-backlink"&gt;Disk Space&lt;/a&gt;&lt;/h2&gt;
&lt;blockquote&gt;
&lt;div&gt;&lt;p&gt;TL;DR: &lt;strong&gt;CAUTION!&lt;/strong&gt; &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;$&lt;/span&gt; &lt;span class="pre"&gt;docker&lt;/span&gt; &lt;span class="pre"&gt;system&lt;/span&gt; &lt;span class="pre"&gt;prune&lt;/span&gt; &lt;span class="pre"&gt;-a&lt;/span&gt; &lt;span class="pre"&gt;--volumes&lt;/span&gt;&lt;/code&gt;&lt;/p&gt;
&lt;/div&gt;&lt;/blockquote&gt;
&lt;p&gt;It’s up to you to clean up unused images, containers, volumes, networks, etc..  This one
will likely seem obvious to anyone who understands now how Docker works, but it
certainly bit me a few times when I was climbing the learning curve.&lt;/p&gt;
&lt;p&gt;I’m not a big fan of this fact, but it’s important to understand that the &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;$&lt;/span&gt; &lt;span class="pre"&gt;docker&lt;/span&gt;
&lt;span class="pre"&gt;...&lt;/span&gt;&lt;/code&gt; CLI is built only to perform the underlying operations, the building blocks,
required for containerized applications.  The important thing to pay attention to there
is what it is &lt;em&gt;not&lt;/em&gt;.  It is not a system for &lt;em&gt;managing&lt;/em&gt; images, containers, volumes,
etc., at least not across deployments, over time, or between different applications.  It
is not a declarative system for describing what images should be used to run which
containers connected to which volumes and networks on a given host.  Even &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;$&lt;/span&gt;
&lt;span class="pre"&gt;docker-compose&lt;/span&gt; &lt;span class="pre"&gt;...&lt;/span&gt;&lt;/code&gt; &lt;span class="target" id="is-really-only-a-different-syntax"&gt;is really only a different syntax&lt;/span&gt; to write a related collection
of &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;$&lt;/span&gt; &lt;span class="pre"&gt;docker&lt;/span&gt; &lt;span class="pre"&gt;...&lt;/span&gt;&lt;/code&gt; CLI commands in a way that is much more readable, maintainable, and
&lt;em&gt;feels&lt;/em&gt; declarative.&lt;/p&gt;
&lt;p&gt;Stopping a container does not remove it.  Removing a container does not remove the
image, volumes or networks.  Removing the image may not remove it’s base image or
Layers.  And so on.  To use other terminology, neither the &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;$&lt;/span&gt; &lt;span class="pre"&gt;docker&lt;/span&gt; &lt;span class="pre"&gt;...&lt;/span&gt;&lt;/code&gt; nor the &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;$&lt;/span&gt;
&lt;span class="pre"&gt;docker-compose&lt;/span&gt; &lt;span class="pre"&gt;...&lt;/span&gt;&lt;/code&gt; CLIs are &lt;a class="reference external" href="https://docs.docker.com/get-started/orchestration/"&gt;orchestration systems&lt;/a&gt;.  As such, a developer must clean
up the inevitable large quantities of Docker cruft themselves.  If, heavens forfend,
you’re deploying containers &lt;em&gt;without&lt;/em&gt; an orchestration system, then you’ll also have to
do the same there.  A recent project was my first AWS ECS exposure, so maybe it was
being used wrong, but I was surprised to learn that this cruft also accrued for that
usage of ECS.&lt;/p&gt;
&lt;p&gt;The easiest way to take care of this is to use the &lt;a class="reference external" href="https://docs.docker.com/engine/reference/commandline/system_prune/"&gt;docker system prune command&lt;/a&gt; (or
the &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;prune&lt;/span&gt;&lt;/code&gt; sub-command under the other &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;$&lt;/span&gt; &lt;span class="pre"&gt;docker&lt;/span&gt; &lt;span class="pre"&gt;...&lt;/span&gt;&lt;/code&gt; commands) but it’s important
to understand what it does lest you destroy data or even code in a certain sense.  When
&lt;a class="reference external" href="https://docs.docker.com/"&gt;Docker&lt;/a&gt; says “prune” they mean “relative to all &lt;em&gt;currently&lt;/em&gt; running containers”.  Say
you have a debug container hanging around into which you’ve installed a rich set of
OS/dist packages, configured things just the way you like, and even written a few
utility or introspection scripts you’ve come to rely on.  This debug container is
stopped most of the time and only run when you need it.  Firstly, &lt;strong&gt;never do this&lt;/strong&gt;!
Always treat containers as ephemeral and able to be destroyed at any time, but lets
continue with this example.  If this debug container isn’t running when you run &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;$&lt;/span&gt;
&lt;span class="pre"&gt;docker&lt;/span&gt; &lt;span class="pre"&gt;system&lt;/span&gt; &lt;span class="pre"&gt;prune&lt;/span&gt;&lt;/code&gt;, then the container, its filesystem, its image, everything will be
destroyed irrevocably.&lt;/p&gt;
&lt;p&gt;As such, only run &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;$&lt;/span&gt; &lt;span class="pre"&gt;docker&lt;/span&gt; &lt;span class="pre"&gt;system&lt;/span&gt; &lt;span class="pre"&gt;prune&lt;/span&gt;&lt;/code&gt; when you’re sure every container is
currently running whose, filesystem, image, volumes, etc. you need to preserve.  See
also the options/flags to that command for more thorough cleanup.  IMO, if it’s not
always safe to run &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;$&lt;/span&gt; &lt;span class="pre"&gt;docker&lt;/span&gt; &lt;span class="pre"&gt;system&lt;/span&gt; &lt;span class="pre"&gt;prune&lt;/span&gt;&lt;/code&gt; then you’re doing something wrong, and that
stands for both local development and deployments.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="publicly-exposed-ports"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#id2" role="doc-backlink"&gt;Publicly Exposed Ports&lt;/a&gt;&lt;/h2&gt;
&lt;blockquote&gt;
&lt;div&gt;&lt;p&gt;TL;DR: Always specify the host IP for port mappings, e.g. &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;127.0.0.1:80:80&lt;/span&gt;&lt;/code&gt;.&lt;/p&gt;
&lt;/div&gt;&lt;/blockquote&gt;
&lt;p&gt;Unlike &lt;a class="reference external" href="https://man.openbsd.org/ssh_config#LocalForward"&gt;SSH port forwarding&lt;/a&gt;, which defaults to only binding to the &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;localhost&lt;/span&gt;&lt;/code&gt; IP,
&lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;127.0.0.1&lt;/span&gt;&lt;/code&gt;, the &lt;a class="reference external" href="https://docs.docker.com/engine/reference/commandline/run/#publish-or-expose-port--p---expose"&gt;docker run -p …&lt;/a&gt; option binds all IPs by default.  This is also
true for &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;$&lt;/span&gt; &lt;span class="pre"&gt;docker-compose&lt;/span&gt; &lt;span class="pre"&gt;...&lt;/span&gt;&lt;/code&gt; since it’s really just &lt;a class="reference internal" href="#is-really-only-a-different-syntax"&gt;an alternate syntax&lt;/a&gt; for the
&lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;$&lt;/span&gt; &lt;span class="pre"&gt;docker&lt;/span&gt; &lt;span class="pre"&gt;...&lt;/span&gt;&lt;/code&gt; CLI.  So to prevent exposing the top secret application you’re
developing on your laptop to everyone at the cafe who can copy and paste a &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;$&lt;/span&gt; &lt;span class="pre"&gt;nmap&lt;/span&gt;
&lt;span class="pre"&gt;...&lt;/span&gt;&lt;/code&gt; command, just default to always specifying &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;127.0.0.1&lt;/span&gt;&lt;/code&gt; as the bind address.
It’s a cheap way to force yourself to think about it and to make the intention of all
port mappings clear to anyone else who reads them while simultaneously making an audit
of intentionally exposed ports as simple as &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;$&lt;/span&gt; &lt;span class="pre"&gt;git&lt;/span&gt; &lt;span class="pre"&gt;grep&lt;/span&gt; &lt;span class="pre"&gt;-r&lt;/span&gt;
&lt;span class="pre"&gt;'0\.0\.0\.0:[0-9]+:[0-9]+'&lt;/span&gt;&lt;/code&gt;.  As for deployments, I’d personally much rather have a
release break a deployment than have a release expose a service to the internet
unintentionally and silently.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="yaml-needs-some-zen"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#id3" role="doc-backlink"&gt;YAML Needs Some Zen&lt;/a&gt;&lt;/h2&gt;
&lt;blockquote&gt;
&lt;div&gt;&lt;p&gt;TL:DR; Quote every &lt;a class="reference external" href="https://yaml.org/"&gt;YAML&lt;/a&gt; value except numbers, booleans, and &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;null&lt;/span&gt;&lt;/code&gt;.&lt;/p&gt;
&lt;/div&gt;&lt;/blockquote&gt;
&lt;div class="highlight-shell-session notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;$ &lt;/span&gt;python&lt;span class="w"&gt; &lt;/span&gt;-c&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;import this&amp;#39;&lt;/span&gt;
&lt;span class="go"&gt;The Zen of Python, by Tim Peters&lt;/span&gt;
&lt;span class="go"&gt;...&lt;/span&gt;
&lt;span class="go"&gt;Explicit is better than implicit.&lt;/span&gt;
&lt;span class="go"&gt;...&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;I love &lt;a class="reference external" href="https://yaml.org/"&gt;YAML&lt;/a&gt;, so a quick rant.  Personally, I find the tendency among developers to
find a flaw in a technology and then develop a long lasting hatred because it cost them
some time once.  Our job is to solve difficult technical problems.  Our job is also to
collectively build fruitful ecosystems of tools, frameworks, and other technology.  I
personally think that embracing what’s good about a technology is more productive than
dismissing a whole technology because of a set of flaws that are a subset of its
features.  Using something &lt;em&gt;and&lt;/em&gt; complaining is much more fruitful than complaining and
not using.  Is what you’re saying productive criticism or just complaining?  I know &lt;a class="reference external" href="http://threevirtues.com/"&gt;the
programmer’s virtues&lt;/a&gt; are delightfully subversive, but I don’t think whining is among
them.&lt;/p&gt;
&lt;p&gt;In fact, I’d love to see &lt;a class="reference external" href="https://hitchdev.com/strictyaml/why/implicit-typing-removed/"&gt;the YAML parser libraries&lt;/a&gt; add options, if not defaults in
new major versions, to disable the following problematic type conversions.  That could
put pressure on the standard to move such surprising behavior to an explicit opt-in
whereby it could still be powerful for those that need it but no longer surprising for
the rest of us.  Or maybe just the standard first, I don’t really know how these things
work.  ;-)  Enough ranting.&lt;/p&gt;
&lt;p&gt;I lied, next rant but opposite tone.  &lt;a class="reference external" href="https://yaml.org/"&gt;YAML&lt;/a&gt; does include some fairly distressing
magical behavior.  Try to digest this:&lt;/p&gt;
&lt;div class="highlight-default notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;yaml&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;safe_load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;port: 22:22&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="go"&gt;{&amp;#39;port&amp;#39;: 1342}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;I lost more time than I care to admit trying to figure out why a
&lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;./docker-compose.yml&lt;/span&gt;&lt;/code&gt; SFTP port mapping wasn’t working when everything started up
with no error.  I eventually did discover that port &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;1342:1342&lt;/span&gt;&lt;/code&gt; was being mapped but
it certainly didn’t help me &lt;em&gt;understand&lt;/em&gt;.  My other port mappings were working without
any surprises:&lt;/p&gt;
&lt;div class="highlight-default notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;yaml&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;safe_load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;port: 80:80&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="go"&gt;{&amp;#39;port&amp;#39;: &amp;#39;80:80&amp;#39;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;What the actual &lt;em&gt;fork&lt;/em&gt;!  This is the very definition of &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Principle_of_least_astonishment"&gt;surprising behavior&lt;/a&gt;.  The
root cause here is that &lt;a class="reference external" href="https://yaml.org/"&gt;YAML&lt;/a&gt; has support for several obscure value types, and &lt;a class="reference external" href="https://yaml.org/type/float.html"&gt;one of
them is base 60 integers&lt;/a&gt;.  I would really love to hear someone make the case that the
use cases for sexagesimal are important and powerful enough for such a majority of
developer users that is justifies this surprise for the rest of us.  Who knew &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Sexagesimal#Babylonian_mathematics"&gt;the
Babylonians&lt;/a&gt; are such a significant constituency!?&lt;/p&gt;
&lt;p&gt;There are, however, &lt;a class="reference external" href="https://sidneyliebrand.medium.com/the-greatnesses-and-gotchas-of-yaml-5e3377ef0c55"&gt;other such value types that can result in similar unaccountably
surprising behavior&lt;/a&gt;.  Just avoid these issues, always quote every &lt;a class="reference external" href="https://yaml.org/"&gt;YAML&lt;/a&gt; value except
numbers, booleans, and &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;null&lt;/span&gt;&lt;/code&gt; unless and until you need any of that black magic:&lt;/p&gt;
&lt;div class="highlight-default notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;yaml&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;safe_load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;port: &amp;quot;22:22&amp;quot;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="go"&gt;{&amp;#39;port&amp;#39;: &amp;#39;22:22&amp;#39;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/section&gt;
&lt;section id="build-context-and-image-stowaways"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#id4" role="doc-backlink"&gt;Build Context and Image Stowaways&lt;/a&gt;&lt;/h2&gt;
&lt;blockquote&gt;
&lt;div&gt;&lt;p&gt;TL;DR: &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;$&lt;/span&gt; &lt;span class="pre"&gt;ln&lt;/span&gt; &lt;span class="pre"&gt;-sv&lt;/span&gt; &lt;span class="pre"&gt;&amp;quot;./.gitignore&amp;quot;&lt;/span&gt; &lt;span class="pre"&gt;&amp;quot;./.dockerignore&amp;quot;&lt;/span&gt;&lt;/code&gt;&lt;/p&gt;
&lt;/div&gt;&lt;/blockquote&gt;
&lt;p&gt;Docker uses what it calls &lt;a class="reference external" href="https://docs.docker.com/develop/develop-images/dockerfile_best-practices/#understand-build-context"&gt;the build context&lt;/a&gt;, the directory containing the
&lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;./Dockerfile&lt;/span&gt;&lt;/code&gt; by default, to determine what is available to &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;COPY&lt;/span&gt;&lt;/code&gt; into an image
while building.  That makes sense to me.  What doesn’t make sense to me is that it
&lt;em&gt;copies&lt;/em&gt; the whole build context somewhere before building an image.  If past is
prologue I’d end up agreeing if I understood the full reasons for that, but until then I
remain dubious.  Moving on.  Regardless of how the build context is handled or managed,
the notion of &lt;a class="reference external" href="https://docs.docker.com/develop/develop-images/dockerfile_best-practices/#understand-build-context"&gt;the build context&lt;/a&gt; does sensibly define what will make it into the image.&lt;/p&gt;
&lt;p&gt;When &lt;a class="reference external" href="https://docs.docker.com/develop/develop-images/dockerfile_best-practices/#understand-build-context"&gt;the build context&lt;/a&gt; includes unnecessary files and directories, at best it just
slows down your image builds and maybe also increases your built image size to no
benefit.  At worst, it leaks content that wasn’t intended into a deployed image, or
worse a publicly released image.  I’ll be honest, though, I mostly get concerned about
the former, unnecessarily long times build hurt a lot when it’s in the inner loop of
developing an image.  The method &lt;a class="reference external" href="https://docs.docker.com/"&gt;Docker&lt;/a&gt; provides to control which files under the
build context directory should &lt;em&gt;not&lt;/em&gt; be included in the build context is &lt;a class="reference external" href="https://docs.docker.com/develop/develop-images/dockerfile_best-practices/#exclude-with-dockerignore"&gt;the
./.dockerignore&lt;/a&gt; file.&lt;/p&gt;
&lt;p&gt;So then just remember to add paths to &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;./.dockerignore&lt;/span&gt;&lt;/code&gt; every time you do something
that might add something to &lt;a class="reference external" href="https://docs.docker.com/develop/develop-images/dockerfile_best-practices/#understand-build-context"&gt;the build context&lt;/a&gt; that you wouldn’t want included in an
image.  Simple, right?  Don’t we all know that developers are really good at keeping a
long list of rote considerations in mind?  Well I’m not.  I also have to say I’m not the
only one, given how many other developers on teams I’ve worked on have made changes that
should be accompanied by additions to &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;./.dockerignore&lt;/span&gt;&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;You know what draws my attention to new files in my build context, and very reliably?
Well a new file represents a change and in such cases the new file is a consequence of
some other change in the source code.  We use tools to manage changes that don’t depend
on developers remembering such considerations, don’t we?  Alright, enough patronizing
sarcasm.  VCS, good ol’ &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;$&lt;/span&gt; &lt;span class="pre"&gt;git&lt;/span&gt; &lt;span class="pre"&gt;status&lt;/span&gt;&lt;/code&gt;, is how I notice when some change I made
resulted in new files in the build context.&lt;/p&gt;
&lt;p&gt;&lt;a class="reference external" href="http://risk-show.com/podcast/this-might-be-controversial/"&gt;This might be controversial&lt;/a&gt;, but in spite of the many posts I’ve found while
searching which say they should be managed separately, I’ve been strongly advocating for
making &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;./.dockerignore&lt;/span&gt;&lt;/code&gt; a symlink that points to &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;./.gitignore&lt;/span&gt;&lt;/code&gt; for ~2 years now.
I haven’t yet regretted it once.  In that same time, I’ve also worked on projects where
&lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;./.dockerignore&lt;/span&gt;&lt;/code&gt; is managed separately and inappropriate files leaking into the build
context has been a recurring issue on all of them, and not just from my commits.&lt;/p&gt;
&lt;p&gt;There are frequent cases where a build artifact should not be committed to VCS but
&lt;em&gt;should&lt;/em&gt; be included in built images.  Here we can exploit one of the &lt;a class="reference external" href="https://zzz.buzz/2018/05/23/differences-of-rules-between-gitignore-and-dockerignore/"&gt;differences
between ./.gitignore and ./.dockerignore&lt;/a&gt;, namely that &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;$&lt;/span&gt; &lt;span class="pre"&gt;git&lt;/span&gt; &lt;span class="pre"&gt;...&lt;/span&gt;&lt;/code&gt; processes
&lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;./.gitignore&lt;/span&gt;&lt;/code&gt; files in each descendant directory under the checkout but &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;$&lt;/span&gt; &lt;span class="pre"&gt;docker&lt;/span&gt;
&lt;span class="pre"&gt;build&lt;/span&gt; &lt;span class="pre"&gt;...&lt;/span&gt;&lt;/code&gt; does not.  So make sure your build artifacts go somewhere in sub-directories
of the checkout (a good idea in any case) and place your &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;./.gitignore&lt;/span&gt;&lt;/code&gt; rules for
those artifacts at the appropriate level down within that sub-directory.&lt;/p&gt;
&lt;p&gt;Also note the other &lt;a class="reference external" href="https://zzz.buzz/2018/05/23/differences-of-rules-between-gitignore-and-dockerignore/"&gt;differences between ./.gitignore and ./.dockerignore&lt;/a&gt; and keep
your rules to those that are interpreted the same by both &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;$&lt;/span&gt; &lt;span class="pre"&gt;git&lt;/span&gt; &lt;span class="pre"&gt;...&lt;/span&gt;&lt;/code&gt; and &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;$&lt;/span&gt; &lt;span class="pre"&gt;docker&lt;/span&gt;
&lt;span class="pre"&gt;build&lt;/span&gt; &lt;span class="pre"&gt;...&lt;/span&gt;&lt;/code&gt;.  In particular, replace your &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;./.gitignore&lt;/span&gt;&lt;/code&gt; rules meant to apply to files
that match in any sub-directory, e.g. &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;foo.txt&lt;/span&gt;&lt;/code&gt;, with more explicit rules that work in
&lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;./.dockerignore&lt;/span&gt;&lt;/code&gt; as well, e.g.:&lt;/p&gt;
&lt;div class="highlight-text notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;/foo.txt
**/foo.txt
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;While more verbose, I prefer this anyways as it’s more explicit and communicates to me
what’s intended instantly and without ambiguity.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="ttfn"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#id5" role="doc-backlink"&gt;TTFN&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Well those are the big gotchas I’ve encountered.  I’m sure I won’t encounter any more.
I’m sure there won’t be any forthcoming “Docker Gotcha: …” posts.  ;-)&lt;/p&gt;
&lt;/section&gt;
&lt;/section&gt;
</content>
    <link href="https://www.rpatterson.net/blog/docker-gotchas/"/>
    <summary>Time to share the snags/gotchas I’ve run into developing and deploying with Docker
containers.</summary>
    <category term="Docker" label="Docker"/>
    <category term="YAML" label="YAML"/>
    <category term="containerization" label="containerization"/>
    <category term="docker-compose" label="docker-compose"/>
    <category term="gotchas" label="gotchas"/>
    <published>2021-04-08T00:00:00+00:00</published>
  </entry>
</feed>
