<?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 - Posted in 2021</title>
  <updated>2025-07-14T00:00:48.028091+00:00</updated>
  <link href="https://www.rpatterson.net/"/>
  <link href="https://www.rpatterson.net/blog/2021/atom.xml" rel="self"/>
  <generator uri="https://ablog.readthedocs.io/" version="0.11.12">ABlog</generator>
  <entry>
    <id>https://www.rpatterson.net/blog/endorsing-conventional-commits/</id>
    <title>Endorsing Conventional Commits</title>
    <updated>2021-04-12T00:00:00+00:00</updated>
    <author>
      <name>Ross Patterson</name>
    </author>
    <content type="html">&lt;section id="endorsing-conventional-commits"&gt;

&lt;blockquote&gt;
&lt;div&gt;&lt;p&gt;Consider adopting a VCS commit message convention, &lt;a class="reference external" href="https://www.conventionalcommits.org/en/v1.0.0/#summary"&gt;Conventional Commits&lt;/a&gt; has made
me a better developer.&lt;/p&gt;
&lt;/div&gt;&lt;/blockquote&gt;
&lt;p&gt;Over the years when starting a new project, I usually found myself staring at the cursor
in my editor when it prompts me to write the commit message for my first commit and
thinking “Is there a convention for this?”.  Sometimes the project would have it’s own
convention, which was always refreshing.  That has been, unfortunately, rare and each
such project usually ended up being the only one that uses that particular convention.
In the last few years, I’ve been using &lt;a class="reference external" href="https://www.conventionalcommits.org/en/v1.0.0/#summary"&gt;Conventional Commits&lt;/a&gt; here and there.  When I
went looking for a tool to &lt;a class="reference external" href="https://github.com/rpatterson/python-main-wrapper/commit/39ad9377977b5544cc674dc837b4516b29e8ced7"&gt;automate versioning and releasing based on VCS history&lt;/a&gt;,
&lt;a class="reference external" href="https://github.com/mathieudutour/github-tag-action#bumping"&gt;the tool I found&lt;/a&gt; uses &lt;a class="reference external" href="https://www.conventionalcommits.org/en/v1.0.0/#summary"&gt;Conventional Commits&lt;/a&gt; which pushed me over the edge to
endorsing and advocating for it.&lt;/p&gt;
&lt;p&gt;The primary reason I reach for a commit message convention is how much easier it makes
visually scanning and make sense of VCS logs as a human.  It makes a world of
difference.  Trying to remember that cool application logic you wrote because you need
something similar again now but all your &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;log&lt;/span&gt; &lt;span class="pre"&gt;-G...&lt;/span&gt;&lt;/code&gt; efforts have come to
naught?  Browsing the log narrowed to &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;log&lt;/span&gt; &lt;span class="pre"&gt;--grep'^feat'&lt;/span&gt;&lt;/code&gt; will most often get
you there a lot faster when compared to iterating through the whole log.  For everything
from &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;blame&lt;/span&gt; &lt;span class="pre"&gt;...&lt;/span&gt;&lt;/code&gt; to CI failure messages to picking out-of-place commits in a PR,
having this extra metadata as well as declaration of intent and scope in every commit
has a thousand little incremental benefits that really add up for the humans that have
to interpret commit messages.&lt;/p&gt;
&lt;p&gt;The second most important reason for a convention, IMO, is &lt;a class="reference external" href="https://dev.to/maniflames/how-conventional-commits-improved-my-git-skills-1jfk"&gt;it makes us better
developers&lt;/a&gt;.  It certainly has made me a better developer.  When we add more structure
to our commit messages and there is meaning attached to that structure, then writing
commits forces us to think about those aspects of development at the time we are
committing changes.  When my commit message convention includes a piece of structure
that declares how this change relates to releasing, versioning, and &lt;a class="reference external" href="https://github.com/conventional-changelog/conventional-changelog"&gt;the changelog&lt;/a&gt;,
then I am forced to think about those things before committing.  If the changes I was
intending to include in this commit include some backwards incompatible refactoring and
a new feature, the convention will force me to realize I need to refactor this commit
into two separate commits.  It may also help me to think of a way to rework the changes
to be backwards compatible.&lt;/p&gt;
&lt;p&gt;Finally, using a commit message convention allows us to use tools to automate
versioning, the changelog, and releasing.  Use &lt;a class="reference external" href="https://github.com/mathieudutour/github-tag-action#bumping"&gt;a tool in your CD pipeline&lt;/a&gt;, so that
when you merge &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;develop&lt;/span&gt;&lt;/code&gt; into &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;master&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;git&lt;/span&gt; &lt;span class="pre"&gt;push&lt;/span&gt;&lt;/code&gt;, the CD pipeline will
create a new version tag using the convention to determine whether the next version
should be a patch version for bug fixes, a minor version for backwards compatible
features, or a major version for backwards incompatible changes.  Next, the CD pipeline
kicks off the language-specific process to build release artifacts and &lt;a class="reference external" href="https://github.com/pypa/setuptools_scm/#setuptools_scm"&gt;a
language-specific add-on/library&lt;/a&gt; uses that new version tag to determine the version to
use in the release artifacts.  Finally, the CD pipeline will upload those release
artifacts to the appropriate registry/index.&lt;/p&gt;
&lt;p&gt;IOW, merge, push, and walk away.  Release discipline becomes a part of code review,
e.g.: “The commit message says this is a backwards compatible feature but this change is
not backward compatible.  Please rework this change to be backward compatible or, if not
possible, change the type in the commit message.”  No manual version bump or changelog
update steps.  No duplicated version information.  There are far fewer mind numbing
chores for the release manager leading to less burn out in that role and more frequent
releases.&lt;/p&gt;
&lt;p&gt;Since I started using &lt;a class="reference external" href="https://www.conventionalcommits.org/en/v1.0.0/#summary"&gt;Conventional Commits&lt;/a&gt;, I’ve just formatted my commit messages
thusly and when working with a team I’ve only shared my enthusiasm when a teammate
asked.  Having done so for a while now, I think I’m ready to start taking a more active
tack and proselytizing to my teammates going forward; for the use of a convention in
general, and &lt;a class="reference external" href="https://www.conventionalcommits.org/en/v1.0.0/#summary"&gt;Conventional Commits&lt;/a&gt; in particular.  I can’t imagine going back,
certainly not back to commit messages without &lt;em&gt;some&lt;/em&gt; convention.  I’m pretty thoroughly
sold on the benefits, even when &lt;em&gt;not&lt;/em&gt; using the CD tools that take advantage, and I
honestly can’t think of a downside.  So give it a shot, embrace the discipline, and see
if you feel the same.&lt;/p&gt;
&lt;p&gt;P.S.: If you agree and you’re a &lt;a class="reference external" href="https://magit.vc/"&gt;Magit&lt;/a&gt; user, please +1 &lt;a class="reference external" href="https://github.com/magit/magit/issues/4027#issuecomment-816139428"&gt;my feature request&lt;/a&gt;.&lt;/p&gt;
&lt;/section&gt;
</content>
    <link href="https://www.rpatterson.net/blog/endorsing-conventional-commits/"/>
    <summary>Consider adopting a VCS commit message convention, Conventional Commits has made
me a better developer.</summary>
    <category term="ConventionalCommits" label="Conventional Commits"/>
    <category term="Magit" label="Magit"/>
    <category term="VCS" label="VCS"/>
    <category term="git" label="git"/>
    <category term="semver" label="semver"/>
    <published>2021-04-12T00:00:00+00:00</published>
  </entry>
  <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>
  <entry>
    <id>https://www.rpatterson.net/blog/ttw-reflections-from-future/</id>
    <title>Reflections on TTW Programming from the Future</title>
    <updated>2021-04-07T00:00:00+00:00</updated>
    <author>
      <name>Ross Patterson</name>
    </author>
    <content type="html">&lt;section id="reflections-on-ttw-programming-from-the-future"&gt;

&lt;blockquote&gt;
&lt;div&gt;&lt;p&gt;Playing with &lt;a class="reference external" href="https://ifttt.com/"&gt;IFTTT&lt;/a&gt; and &lt;a class="reference external" href="https://zapier.com/"&gt;Zapier&lt;/a&gt; has me remembering the TTW programming fallout
and debate.&lt;/p&gt;
&lt;/div&gt;&lt;/blockquote&gt;
&lt;p&gt;I’ve been working on &lt;a class="reference external" href="../feeding-ablog-to-social-media/"&gt;feeding my ABlog posts to social media&lt;/a&gt;.  I’ve been experimenting
with some external services for this, primarily because implementing something
self-hosted seems like overkill for this and it makes an excuse to explore something
I’ve been wanting to.  I’m intrigued by these services that claim to let you connect
little pieces of functionality, through an intuitive UI, to create “custom”
mini-“applications”.&lt;/p&gt;
&lt;p&gt;I’m old enough to have cut my teeth on &lt;a class="reference external" href="https://zope.org/"&gt;some of the first Through-The-Web (TTW)
programming offerings&lt;/a&gt; and thus long enough to watch the problems that became of
that. The consensus among the communities I’ve been involved in is that it’s a bad idea
to invite users and tinkerers to build their own custom applications with the promise
that it’s easier and achievable because they can do it in their browser.  The issue is
&lt;em&gt;not&lt;/em&gt; that non-programmers can’t learn to program.  They certainly can and I object to
the kind of rarefied elitism that leads others to argue otherwise.  The issue is that no
one can build the kind of custom application one can build with a full programming
language without &lt;em&gt;also&lt;/em&gt; learning most, if not all, of the suite of programming best
practices.  Without those best practices, one is all but guaranteed to end up with a
poorly engineered, unmaintainable mess.  The best case scenario is that your application
fails to become useful and thus never has a chance to become a burden.  That leaves
those who manage to create an actually useful, initially successful application that
users come to depend on.  These successful tinkerers are eventually rewarded by hitting
multiple walls at once.  Thinking about, planning for, and executing on maintainability
and other best practices are simply costs that must be paid.  The apparent savings of
not having to think about such things is an illusion and those costs have been
compounding all along behind your back.&lt;/p&gt;
&lt;p&gt;While the 10th feature was as easy to implement as the 1st, the 100th feature is 10
times as difficult as the 10th and breaks features 20-23; you can infer the curve.  You
took a stab at a major version upgrade of the framework months ago, hit another wall,
and set it aside.  Now the security/LTS of your major version is about to expire meaning
your custom application will soon be vulnerable to multiple publicly available exploits.
Change management is becoming a nightmare with duplicated code all over the place in an
effort to compensate.  A while back, you learned about VCS and the current best-of-breed
in that domain but you also realized that you couldn’t use those tools to manage your
TTW code.  There are better ways to build custom applications even within the framework
you’re using, but not TTW, the effort is comparable to re-implementing the application,
and those that can do it justly charge much more.  BTW, having learned so much from
this, you’re now worth a lot more yourself and are recruited into your first junior
programming job.  Those last two are good things, but the end result is that a team of
users is now dependent on a custom application that’s going to cost them a lot to either
replace or maintain.  They became a company in the business of developing software
without ever intending to, planning on or budgeting for.&lt;/p&gt;
&lt;p&gt;That whole process breeds resentment in a way that doesn’t come from even an equally
blundering and costly but conscious effort to develop one’s own custom software.  Like
much of human happiness, it’s expectations, the surprise, that’s the issue.  That’s how
I understand the issue with TTW programming.  If a framework provides anything
approaching a full programming language, &lt;em&gt;and&lt;/em&gt; offers or promises to save
users-come-developers some portion of software development “hassles”, then everyone is
almost certainly in for some bad-will inducing surprises.&lt;/p&gt;
&lt;p&gt;So that means that any system that allows users to assemble “applications” through a UI
meant to spare them from some portion of software development discipline needs to have
clear, rigid boundaries on expectations.  It needs to narrowly define what can be done,
place firm limits on the size of what can be built (roughly, the quantity of
components).  It doesn’t hurt to provide users an eject button so that they can export
what they’ve done to the filesystem and continue developing it incrementally using a
full software development tool chain and process.  These are the lessons the &lt;a class="reference external" href="https://plone.org/"&gt;Plone&lt;/a&gt;
community tried to learn from our &lt;a class="reference external" href="https://zope.org/"&gt;Zope&lt;/a&gt; TTW roots.  The &lt;a class="reference external" href="https://plone.org/"&gt;Plone&lt;/a&gt; community built web
UI based on these conclusions for both custom content types with custom schemata and for
custom themes.&lt;/p&gt;
&lt;p&gt;Given my involvement in that long (for software) history, I’ve been wanting to check out
&lt;a class="reference external" href="https://ifttt.com/"&gt;IFTTT&lt;/a&gt;.  Such custom applet “development” and hosting platforms are solving different
problems, very simple workflow applications connecting various internet services, but I
imagine theses same principles apply.  &lt;a class="reference external" href="https://ifttt.com/"&gt;IFTTT&lt;/a&gt; seems to employ these principles quite
firmly.  I’m not aware of any eject button that could meaningfully allow one to
incrementally build software on top of &lt;a class="reference external" href="https://ifttt.com/"&gt;IFTTT&lt;/a&gt; applets, but that wouldn’t be terribly
useful anyways given how firmly they restrict the scope of what one can do.  I don’t
have any current need for anything beyond IFTTT’s simple, two-step composition model,
the &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;this&lt;/span&gt;&lt;/code&gt; component and the &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;that&lt;/span&gt;&lt;/code&gt; component in “If this then that”, but I could
well imagine wanting more than that.  There’s also the limit on 3 applets on the free
tier of their freemium pricing plan, I am already using 2.  Finally, I couldn’t find a
&lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;then&lt;/span&gt;&lt;/code&gt; component for posting to LinkedIn as a service, that’s the one that really lead
me to seek other options.&lt;/p&gt;
&lt;p&gt;I ran across &lt;a class="reference external" href="https://zapier.com/"&gt;Zapier&lt;/a&gt; a while back and was similarly intrigued, so when that showed up
in my search for something that could post to LinkedIn, I eagerly tried it out.  They
offer 5 applets, which they call “Zaps” (bit of a groaner of a term), on the free tier
of their freemium pricing plan, so point for &lt;a class="reference external" href="https://zapier.com/"&gt;Zapier&lt;/a&gt;.  I haven’t read much of their
docs, but it’s apparent in the web UI that there’s support for at least a somewhat
arbitrary chaining of more than 2 components, another win for &lt;a class="reference external" href="https://zapier.com/"&gt;Zapier&lt;/a&gt;.  I also found
their options for dealing with different “fields” in data, &lt;a class="reference external" href="https://ablog.readthedocs.io/manual/ablog-configuration-options/?highlight=feeds#blog-feeds"&gt;atom feed&lt;/a&gt; items in this
case, to be significantly more rich than those offered by &lt;a class="reference external" href="https://ifttt.com/"&gt;IFTTT&lt;/a&gt;, &lt;a class="reference external" href="https://zapier.com/"&gt;Zapier&lt;/a&gt; streaks
ahead.  They do indeed have components that interact with LinkedIn, so &lt;a class="reference external" href="https://zapier.com/"&gt;Zapier&lt;/a&gt; FTW in
this very cursory and superficial scoring.&lt;/p&gt;
&lt;p&gt;In the current moment, I’m very curious to see how the more restrictive &lt;a class="reference external" href="https://ifttt.com/"&gt;IFTTT&lt;/a&gt;
compares in the long term to the more feature-rich and composible &lt;a class="reference external" href="https://zapier.com/"&gt;Zapier&lt;/a&gt; in terms of
user experience, user maintainability, and &lt;em&gt;internal&lt;/em&gt; maintainability.  Even if I were
to pay enough attention to keep up on this, not very likely, I’m even less likely to get
access to the kind of proprietary internal discussion that could yield such insight.
C’est la capitalisme.  At any rate, It’s been fun to reflect on the issue while
automating some things.&lt;/p&gt;
&lt;/section&gt;
</content>
    <link href="https://www.rpatterson.net/blog/ttw-reflections-from-future/"/>
    <summary>Playing with IFTTT and Zapier has me remembering the TTW programming fallout
and debate.</summary>
    <category term="IFTTT" label="IFTTT"/>
    <category term="Plone" label="Plone"/>
    <category term="TTW" label="TTW"/>
    <category term="Zapier" label="Zapier"/>
    <category term="Zope" label="Zope"/>
    <published>2021-04-07T00:00:00+00:00</published>
  </entry>
  <entry>
    <id>https://www.rpatterson.net/blog/sphinx-page-dirs/</id>
    <title>Using Directories for Sphinx Pages</title>
    <updated>2021-04-05T00:00:00+00:00</updated>
    <author>
      <name>Ross Patterson</name>
    </author>
    <content type="html">&lt;section id="using-directories-for-sphinx-pages"&gt;

&lt;blockquote&gt;
&lt;div&gt;&lt;p&gt;Creating &lt;a class="reference external" href="https://www.sphinx-doc.org/en/master/"&gt;Sphinx&lt;/a&gt; pages as &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;./foo/index.rst&lt;/span&gt;&lt;/code&gt; has a number of benefits over
&lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;./foo.rst&lt;/span&gt;&lt;/code&gt; including path consistency and organizing content.&lt;/p&gt;
&lt;/div&gt;&lt;/blockquote&gt;
&lt;p&gt;When &lt;a class="reference external" href="../migrating-from-plone-to-ablog/"&gt;I first started playing with ABlog&lt;/a&gt;, and thus learning more about Python’s
wonderful documentation tool &lt;a class="reference external" href="https://www.sphinx-doc.org/en/master/"&gt;Sphinx&lt;/a&gt;, I observed that &lt;a class="reference external" href="https://www.sphinx-doc.org/en/master/"&gt;Sphinx&lt;/a&gt; actually creates a
directory in the built HTML output for each &lt;a class="reference external" href="https://docutils.sourceforge.io/rst.html"&gt;reStructuredText&lt;/a&gt; &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;./**.rst&lt;/span&gt;&lt;/code&gt; file in the
source.  For example, if the source for this post had been
&lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;./blog/sphinx-page-dirs.rst&lt;/span&gt;&lt;/code&gt;, &lt;a class="reference external" href="https://www.sphinx-doc.org/en/master/"&gt;Sphinx&lt;/a&gt; would render that to
&lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;./_website/blog/sphinx-page-dirs/index.html&lt;/span&gt;&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;One consequence of this is that a relative link to another area of the site will be
broken in the rendered HTML build output if it’s correct in the &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;./**.rst&lt;/span&gt;&lt;/code&gt; source
file.  For example, if &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;./about.rst&lt;/span&gt;&lt;/code&gt; contains a link such as:&lt;/p&gt;
&lt;div class="highlight-rst notranslate"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;See &lt;span class="s"&gt;`the blog area to see all posts &lt;/span&gt;&lt;span class="si"&gt;&amp;lt;./blog/&amp;gt;&lt;/span&gt;&lt;span class="s"&gt;`_&lt;/span&gt;.
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;The &lt;a class="reference external" href="https://www.sphinx-doc.org/en/master/"&gt;Sphinx&lt;/a&gt; HTML build output will render that &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;./about.rst&lt;/span&gt;&lt;/code&gt; file to
&lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;./about/index.html&lt;/span&gt;&lt;/code&gt; and thus the link will point to &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;/about/blog/&lt;/span&gt;&lt;/code&gt; instead of the
intended &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;/blog/&lt;/span&gt;&lt;/code&gt;.  I dimly recall encountering such path issues with other types of
reST path references as well.  Path consistency is also particularly handy for editors
or other tools that can infer enough to open files from path references in other files,
such as &lt;a class="reference external" href="https://www.gnu.org/software/emacs/manual/html_node/emacs/FFAP.html"&gt;FFAP&lt;/a&gt; in &lt;a class="reference external" href="https://www.gnu.org/software/emacs/"&gt;Emacs&lt;/a&gt;.  Writing the source reST for a page as
&lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;./about/index.rst&lt;/span&gt;&lt;/code&gt; in the first place nicely avoids those problems and empowers such
tools.&lt;/p&gt;
&lt;p&gt;I’m not a big fan of embedding code of one sort into content (or code) of another sort.
For example, as big an &lt;a class="reference external" href="https://orgmode.org/"&gt;Org-mode&lt;/a&gt; fan as I am, I’ve never been able to get into using
&lt;a class="reference external" href="https://orgmode.org/worg/org-contrib/babel/"&gt;Babel&lt;/a&gt;.  In this case, when writing documentation or other text content, I resist
embedding code into source text files and prefer to have such code in separate files
next to the text.  If the code I want to include in documentation is actual used code,
as was the case with &lt;a class="reference external" href="../migrating-from-plone-to-ablog/"&gt;the export/import scripts that migrated this blog&lt;/a&gt;, then it saves
time copying and pasting and prevents out-of-date content when forgetting to do so.
Even if it is example or demonstration code of more than a few lines, having it in
separate sibling files supports using all our favorite tools (static analysis, IDEs,
editors, etc.) which further prevents incorrect or misleading examples.  So instead of
&lt;a class="reference external" href="https://docutils.sourceforge.io/docs/ref/rst/restructuredtext.html#literal-blocks"&gt;reStructuredText’s :: literal blocks&lt;/a&gt; or Markdown’s indented code blocks I’d much
rather include code from separate files.  Using a directory for such pages,
e.g. &lt;code class="docutils literal notranslate"&gt;&lt;span class="pre"&gt;./blog/migrating-from-plone-to-ablog/&lt;/span&gt;&lt;/code&gt;, also allows us to keep such related
files organized neatly together.&lt;/p&gt;
&lt;p&gt;Finally, using a directory for a &lt;a class="reference external" href="https://www.sphinx-doc.org/en/master/"&gt;Sphinx&lt;/a&gt; page supports putting all related content
into a separate VCS repository checkout, such as via &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;submodule&lt;/span&gt; &lt;span class="pre"&gt;...&lt;/span&gt;&lt;/code&gt;.  This can
be very useful for sharing code related to one page without having to share the source
and VCS history for the whole site.  For example, I have an upcoming post with a
non-trivial amount of working and tested demo code I want to share.  I can use this
approach to make that code and VCS history public while still preserving the option of
having private content or VCS history for the rest of this site.&lt;/p&gt;
&lt;/section&gt;
</content>
    <link href="https://www.rpatterson.net/blog/sphinx-page-dirs/"/>
    <summary>Creating Sphinx pages as ./foo/index.rst has a number of benefits over
./foo.rst including path consistency and organizing content.</summary>
    <category term="ABlog" label="ABlog"/>
    <category term="Python" label="Python"/>
    <category term="Sphinx" label="Sphinx"/>
    <category term="reStructuredText" label="reStructuredText"/>
    <published>2021-04-05T00:00:00+00:00</published>
  </entry>
</feed>
