<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Docker on Haseeb Majid</title>
    <link>https://haseebmajid.dev/tags/docker/</link>
    <description>Recent content in Docker on Haseeb Majid</description>
    <generator>Hugo -- gohugo.io</generator>
    <language>en</language>
    <lastBuildDate>Wed, 21 Dec 2022 00:00:00 +0000</lastBuildDate><atom:link href="https://haseebmajid.dev/tags/docker/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>TIL: How to Use DinD, localhost &amp; Gitlab CI</title>
      <link>https://haseebmajid.dev/posts/2022-12-21-til-how-to-use-dind-localhost-gitlab-ci/</link>
      <pubDate>Wed, 21 Dec 2022 00:00:00 +0000</pubDate>
      <author>Me</author>
      <guid>https://haseebmajid.dev/posts/2022-12-21-til-how-to-use-dind-localhost-gitlab-ci/</guid>
      <description>&lt;p&gt;&lt;strong&gt;TIL: How to Use DinD, localhost &amp;amp; Gitlab CI&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;In this post, I will go over how you can use docker-compose and Gitlab CI.
In this example, we will be running playwright tests directly on the Gitlab runner.
The tests will start a SvelteKit server also running on the Gitlab runner. The SvelteKit
server will connect to PocketBase (backend) running in docker-compose.&lt;/p&gt;
&lt;p&gt;So essentially we need a way for something running locally to connect to something running in
docker in Gitlab CI (on a runner). This is a pattern I am using in my new app &lt;a href=&#34;https://bookmarkey.app&#34;&gt;Bookmarkey&lt;/a&gt; &lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p><strong>TIL: How to Use DinD, localhost &amp; Gitlab CI</strong></p>
<p>In this post, I will go over how you can use docker-compose and Gitlab CI.
In this example, we will be running playwright tests directly on the Gitlab runner.
The tests will start a SvelteKit server also running on the Gitlab runner. The SvelteKit
server will connect to PocketBase (backend) running in docker-compose.</p>
<p>So essentially we need a way for something running locally to connect to something running in
docker in Gitlab CI (on a runner). This is a pattern I am using in my new app <a href="https://bookmarkey.app">Bookmarkey</a> <sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>.</p>
<p>Let&rsquo;s pretend we have a <code>docker-compose.yml</code> file which looks something like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">services</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">pocketbase</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">ghcr.io/muchobien/pocketbase:latest</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">ports</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="s1">&#39;9090:8090&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">volumes</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">./pb_data:/pb_data</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">/pb_public</span><span class="w">
</span></span></span></code></pre></div><p>and our <code>package.json</code> scripts section looks like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;scripts&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;test&#34;</span><span class="p">:</span> <span class="s2">&#34;playwright test&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">},</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Finally the most important file let&rsquo;s look at the gitlab ci file <code>.gitlab-ci.yml</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">stages</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">test</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">tests:e2e</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">stage</span><span class="p">:</span><span class="w"> </span><span class="l">test</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">only</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">merge_request</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">mcr.microsoft.com/playwright:v1.29.0-jammy</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">services</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">docker:dind</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">variables</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">DOCKER_DRIVER</span><span class="p">:</span><span class="w"> </span><span class="l">overlay2</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">DOCKER_HOST</span><span class="p">:</span><span class="w"> </span><span class="l">tcp://docker:2375</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">VITE_POCKET_BASE_URL</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;http://docker:9090&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">script</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="c"># ... Installing docker and docker compose</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">docker compose up -d</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">npm run test</span><span class="w">
</span></span></span></code></pre></div><p>Since all Gitlab CI jobs run in Docker. If we want to run docker-compose inside a job we need to use the dind service <sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup>.
We can then use docker normally i.e. using <code>docker compose</code> to start PocketBase. The most important line here is normally
to connect to PocketBase I would use <code>http://localhost:8090</code>. However, since we are using the <code>dind</code> service we need to use
<code>docker</code> instead of <code>localhost</code>. Hence this <code>VITE_POCKET_BASE_URL: 'http://docker:9090'</code>, passed to my SvelteKit app.</p>
<p>I spent about two days debugging this issue, even though I&rsquo;d solved this problem before 🤦‍♂️. So I decided to make a quick
post so hopefully you can avoid wasting your time.</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p><a href="https://gitlab.com/banter-bus/bookmarkey/gui/-/blob/e575e5a97feb70227fd6aae366ce4fc9beacafe2/.gitlab-ci.yml">https://gitlab.com/banter-bus/bookmarkey/gui/-/blob/e575e5a97feb70227fd6aae366ce4fc9beacafe2/.gitlab-ci.yml</a>&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p>Read more about <a href="/posts/20-05-01-how-to-use-dind-with-gitlab-ci/">dind here</a>&#160;<a href="#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded>
    </item>
    
    <item>
      <title>How to use DotBot to personalise your VSCode Devcontainers</title>
      <link>https://haseebmajid.dev/posts/2022-12-15-how-to-use-dotbot-to-personalise-your-vscode-devcontainers/</link>
      <pubDate>Thu, 15 Dec 2022 00:00:00 +0000</pubDate>
      <author>Me</author>
      <guid>https://haseebmajid.dev/posts/2022-12-15-how-to-use-dotbot-to-personalise-your-vscode-devcontainers/</guid>
      <description>&lt;details
  class=&#34;notice warning&#34;
  open=&#34;true&#34;
&gt;
    &lt;summary class=&#34;notice-title&#34;&gt;Devcontainers&lt;/summary&gt;
  
  This article assumes you are already familiar with dev containers.
You can read more about &lt;a href=&#34;https://code.visualstudio.com/docs/devcontainers/containers&#34;&gt;devcontainers here&lt;/a&gt;.
&lt;/details&gt;

&lt;p&gt;&lt;img
        loading=&#34;lazy&#34;
        src=&#34;../../posts/2022-12-15-how-to-use-dotbot-to-personalise-your-vscode-devcontainers/images/say-docker.jpeg&#34;
        type=&#34;&#34;
        alt=&#34;Docker Meme&#34;
        
      /&gt;&lt;/p&gt;
&lt;p&gt;In this article, we will go over how you can personalise your dev containers. Devcontainers allow us to create consistent development environments. One of the main advantages of dev containers is we can provide a &amp;ldquo;one button&amp;rdquo; setup for new developers.
We do this by using a container (Docker), and we end up developing inside a container. Much like if we used &lt;code&gt;docker exec -it ubuntu /bin/bash&lt;/code&gt;.
Except it provides a few nice conveniences such as copying (into the container) over the project files and our ssh keys.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<details
  class="notice warning"
  open="true"
>
    <summary class="notice-title">Devcontainers</summary>
  
  This article assumes you are already familiar with dev containers.
You can read more about <a href="https://code.visualstudio.com/docs/devcontainers/containers">devcontainers here</a>.
</details>

<p><img
        loading="lazy"
        src="/posts/2022-12-15-how-to-use-dotbot-to-personalise-your-vscode-devcontainers/images/say-docker.jpeg"
        type=""
        alt="Docker Meme"
        
      /></p>
<p>In this article, we will go over how you can personalise your dev containers. Devcontainers allow us to create consistent development environments. One of the main advantages of dev containers is we can provide a &ldquo;one button&rdquo; setup for new developers.
We do this by using a container (Docker), and we end up developing inside a container. Much like if we used <code>docker exec -it ubuntu /bin/bash</code>.
Except it provides a few nice conveniences such as copying (into the container) over the project files and our ssh keys.</p>
<p>However one of the issues that can arise from this is how you get your dev tools/programs in the dev container.
For example, I use fish shell but lots of Docker containers default to using bash. I also don&rsquo;t want to pollute the Docker file
with a bunch of my specific dev tools. If every developer does that you could end up with a very large Docker file.
This will also mean it takes longer for the dev container to build.</p>
<p>One way we can do this is by using DotBot and a dotfiles repo. I will assume you are familiar with everything we&rsquo;ve covered up to this point.
You have a dotfiles repo which uses DotBot, has profiles and has plugins installed. In this example, we will be using the <a href="https://github.com/bryant1410/dotbot-apt"><code>dotbot-apt</code> plugin</a>.</p>
<h2 id="dotfiles">Dotfiles</h2>
<p>Let&rsquo;s go to our dotfiles repo which we will assume looks like:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">├── ....
</span></span><span class="line"><span class="cl">├── bashrc
</span></span><span class="line"><span class="cl">├── fish
</span></span><span class="line"><span class="cl">│   └── fish.config
</span></span><span class="line"><span class="cl">├── .gitconfig
</span></span><span class="line"><span class="cl">├── install-profile
</span></span><span class="line"><span class="cl">├── install-standalone
</span></span><span class="line"><span class="cl">├── meta
</span></span><span class="line"><span class="cl">│   ├── configs
</span></span><span class="line"><span class="cl">│   │   └── git.yaml
</span></span><span class="line"><span class="cl">│   ├── dotbot
</span></span><span class="line"><span class="cl">│   ├── dotbot-apt
</span></span><span class="line"><span class="cl">│   ├── base.yaml
</span></span><span class="line"><span class="cl">│   └── profiles
</span></span><span class="line"><span class="cl">│       └── linux
</span></span><span class="line"><span class="cl">└── vscode
</span></span></code></pre></div><h3 id="configs">configs</h3>
<p>It may look something like the above. Let&rsquo;s create some new configs specific to our dev container. In this case, we will assume all the
dev containers we will use will be Debian based. So let&rsquo;s create a file <code>meta/configs/packages.debian.yaml</code> which may look like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl">- <span class="nt">apt</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">jq</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">fzf</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">vim</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">make</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">zoxide</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">exa</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">fish</span><span class="w">
</span></span></span></code></pre></div><p>This will be used to install the specific dev tools I need such as <code>jq</code> and <code>fzf</code>. Next, I want to make sure my fish shell config also gets set up
correctly so we will create another file called <code>meta/configs/shell.yaml</code> which looks like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl">- <span class="nt">link</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">~/.config/fish</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">path</span><span class="p">:</span><span class="w"> </span><span class="l">fish/**</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">glob</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">create</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span></code></pre></div><h3 id="profiles">profiles</h3>
<p>This will copy (symlink) all of my fish config files to <code>~/.config/fish/</code> directory in the dev container from the dotfiles repo.
Next, let us create a new profile <code>meta/profiles/devcontainer</code> which will look like:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">packages.debian-sudo
</span></span><span class="line"><span class="cl">shell
</span></span></code></pre></div><p>Remember by appending <code>-sudo</code> to <code>packages.debian</code> we will run those directives as root i.e. <code>apt</code>.</p>
<h3 id="install-script">Install Script</h3>
<p>So what we have done is create a new profile which will install some of the dev tools we need and copy over our fish config.
Now we have to do one final thing create a new file at the root called <code>install.devcontainer.sh</code> (you can call this whatever
you want, just remember the name). This file looks like:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="cp">#!/usr/bin/env bash
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">./install-profile devcontainer
</span></span></code></pre></div><p>The reason we need this we need to provide an executable file in our VS Code config. We couldn&rsquo;t just run specify this
<code>./install-profile devcontainer</code>. We will see this a bit later.</p>
<h3 id="structure">Structure</h3>
<p>Our repo structure now looks like</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">├── ....
</span></span><span class="line"><span class="cl">├── bashrc
</span></span><span class="line"><span class="cl">├── fish
</span></span><span class="line"><span class="cl">│   └── fish.config
</span></span><span class="line"><span class="cl">├── .gitconfig
</span></span><span class="line"><span class="cl">├── install-profile
</span></span><span class="line"><span class="cl">├── install-standalone
</span></span><span class="line"><span class="cl">├── meta
</span></span><span class="line"><span class="cl">│   ├── configs
</span></span><span class="line"><span class="cl">│   │   ├── shell.yaml
</span></span><span class="line"><span class="cl">│   │   ├── packages.debian.yaml
</span></span><span class="line"><span class="cl">│   │   └── git.yaml
</span></span><span class="line"><span class="cl">│   ├── dotbot
</span></span><span class="line"><span class="cl">│   ├── dotbot-apt
</span></span><span class="line"><span class="cl">│   ├── base.yaml
</span></span><span class="line"><span class="cl">│   └── profiles
</span></span><span class="line"><span class="cl">│       └── linux
</span></span><span class="line"><span class="cl">└── vscode
</span></span></code></pre></div><p>Now let&rsquo;s move on to the repository that is using dev containers. We are going to use a super simple example,
just to demonstrate. Let&rsquo;s create a new file <code>.devcontainer/devcontainer.json</code> which looks like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="nt">&#34;name&#34;</span><span class="p">:</span> <span class="s2">&#34;Go&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;image&#34;</span><span class="p">:</span> <span class="s2">&#34;mcr.microsoft.com/devcontainers/go:0-1.18&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;features&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;ghcr.io/devcontainers/features/node:1&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;version&#34;</span><span class="p">:</span> <span class="s2">&#34;lts&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1">// Configure tool-specific properties.
</span></span></span><span class="line"><span class="cl">  <span class="nt">&#34;customizations&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// Configure properties specific to VS Code.
</span></span></span><span class="line"><span class="cl">    <span class="nt">&#34;vscode&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="c1">// Set *default* container specific settings.json values on container create.
</span></span></span><span class="line"><span class="cl">      <span class="nt">&#34;settings&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;go.toolsManagement.checkForUpdates&#34;</span><span class="p">:</span> <span class="s2">&#34;local&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;go.useLanguageServer&#34;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;go.gopath&#34;</span><span class="p">:</span> <span class="s2">&#34;/go&#34;</span>
</span></span><span class="line"><span class="cl">      <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1">// Use &#39;forwardPorts&#39; to make a list of ports inside the container available locally.
</span></span></span><span class="line"><span class="cl">  <span class="c1">// &#34;forwardPorts&#34;: [],
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1">// Use &#39;postCreateCommand&#39; to run commands after the container is created.
</span></span></span><span class="line"><span class="cl">  <span class="c1">// &#34;postCreateCommand&#34;: &#34;go version&#34;,
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1">// Set `remoteUser` to `root` to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root.
</span></span></span><span class="line"><span class="cl">  <span class="nt">&#34;remoteUser&#34;</span><span class="p">:</span> <span class="s2">&#34;vscode&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>This is the default one generated by VS Code for Golang projects (when using the command palette). Normally we would have a custom Docker image
we are using. Perhaps in a future post, I will go over how to use dev containers with an existing custom Docker image. But for this example,
we will just use the Microsoft provided golang image <code>mcr.microsoft.com/devcontainers/go:0-1.18</code>.</p>
<details
  class="notice warning"
  open="true"
>
    <summary class="notice-title">Extension</summary>
  
  You need to have the <a href="https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers">devcontainer extension</a> installed in VS Code.
</details>

<p>This is enough to create a dev container, we can open the command palette on VS Code and run <code>rebuild and reopen in container</code>. However
this is one final thing we need to do.</p>
<p>Open your <code>settings.json</code> file and add something like so:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="c1">// ...
</span></span></span><span class="line"><span class="cl">  <span class="nt">&#34;dotfiles.repository&#34;</span><span class="p">:</span> <span class="s2">&#34;hmajid2301/dotfiles&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;dotfiles.targetPath&#34;</span><span class="p">:</span> <span class="s2">&#34;~/dotfiles&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;dotfiles.installCommand&#34;</span><span class="p">:</span> <span class="s2">&#34;~/dotfiles/install.devcontainer.sh&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="c1">// ...
</span></span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><ul>
<li><code>dotfiles.repository</code>: You will need to update the repo <code>hmajid2301/dotfiles</code> to point to your dotfiles repo and it must be accessible on github.</li>
<li><code>dotfiles.targetPath</code>: The <code>targetPath</code> is where in the devcontainer we will git clone our dotfiles repo.</li>
<li><code>dotfiles.installCommand</code>: The executable it will run after the devcontainer is set up. If you called it something else you will need to update that here as well.</li>
</ul>
<p>That&rsquo;s it, now we can have a common dev container set up and personalise with our dotfiles and specific dev tools we want using DotBot.</p>
<h2 id="appendix">Appendix</h2>
<ul>
<li><a href="https://gitlab.com/hmajid2301/dotfiles/-/tree/6b83e990861654506e8ecc756af75cf431438a4a">My Dotfiles</a></li>
<li><a href="https://gitlab.com/hmajid2301/dotfiles/-/blob/77ee6056ae1a1b4ad066348e2b6a3dd6109a409a/meta/profiles/devcontainer">My devcontainer DotBot profile</a></li>
</ul>
]]></content:encoded>
    </item>
    
    <item>
      <title>Running Gitlab CI jobs in Docker using docker-compose</title>
      <link>https://haseebmajid.dev/posts/2022-08-08-running-gitlab-ci-jobs-in-docker-using-docker-compose/</link>
      <pubDate>Mon, 08 Aug 2022 00:00:00 +0000</pubDate>
      <author>Me</author>
      <guid>https://haseebmajid.dev/posts/2022-08-08-running-gitlab-ci-jobs-in-docker-using-docker-compose/</guid>
      <description>&lt;p&gt;Shameless plug: This is related to a EuroPython 2022 talk I am giving, &lt;a href=&#34;../../talks/my-journey-using-docker-as-a-developer-tool&#34;&gt;My Journey Using Docker as a Development Tool&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;For most of my common dev tasks, I&amp;rsquo;ve started to rely on &lt;code&gt;docker&lt;/code&gt;/&lt;code&gt;docker compose&lt;/code&gt; to run commands locally. I have also
started using vscode&amp;rsquo;s &lt;code&gt;.devcontainers&lt;/code&gt;, to provide a consistent environment for all developers using a project.&lt;/p&gt;
&lt;p&gt;The main reason for this is to avoid needing to install dependencies on my host machine. In theory, all I
should need is a Docker daemon and a CLI (docker CLI) to interact with that Daemon. This also makes it
far easier for any new developer to start working on my project and get set up.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>Shameless plug: This is related to a EuroPython 2022 talk I am giving, <a href="/talks/my-journey-using-docker-as-a-developer-tool">My Journey Using Docker as a Development Tool</a>.</p>
<p>For most of my common dev tasks, I&rsquo;ve started to rely on <code>docker</code>/<code>docker compose</code> to run commands locally. I have also
started using vscode&rsquo;s <code>.devcontainers</code>, to provide a consistent environment for all developers using a project.</p>
<p>The main reason for this is to avoid needing to install dependencies on my host machine. In theory, all I
should need is a Docker daemon and a CLI (docker CLI) to interact with that Daemon. This also makes it
far easier for any new developer to start working on my project and get set up.</p>
<p>What inspired me to do this change now (in my banter bus project) was I wanted to upgrade to
python 3:10 to use some of the new typing features released. However when I tried to upgrade my CI pipeline
started failing, after hours of trying to debug it. I ended up using Docker and everything ran smoothly.</p>
<p>Now to have a more consistent environment between my local environment and CI. So in theory, it means
less chance of something passing locally but failing in CI.</p>
<p>Now we know why we want to do it. let&rsquo;s look at how we do it.</p>
<h2 id="before">Before</h2>
<p>Let&rsquo;s take a look at what a typical CI pipeline may look for a Python project (using banter bus).
In this example, we will be using a FastAPI web service which uses Poetry to manage its dependencies.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yml" data-lang="yml"><span class="line"><span class="cl"><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">python:3.10.5</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">variables</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">DOCKER_DRIVER</span><span class="p">:</span><span class="w"> </span><span class="l">overlay2</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">PIP_CACHE_DIR</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;${CI_PROJECT_DIR}/.cache/pip&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">PIP_DOWNLOAD_DIR</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;.pip&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">DOCKER_HOST</span><span class="p">:</span><span class="w"> </span><span class="l">tcp://docker:2375</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">cache</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">key</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;${CI_JOB_NAME}&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">paths</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">.cache/pip</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">.venv</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">.test</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">services</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">mongo:4.4.4</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">alias</span><span class="p">:</span><span class="w"> </span><span class="l">banter-bus-database</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">redis:6.2.4</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">alias</span><span class="p">:</span><span class="w"> </span><span class="l">banter-bus-message-queue</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">registry.gitlab.com/banter-bus/banter-bus-management-api:test</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">alias</span><span class="p">:</span><span class="w"> </span><span class="l">banter-bus-management-api</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">registry.gitlab.com/banter-bus/banter-bus-management-api/database-seed:latest</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">alias</span><span class="p">:</span><span class="w"> </span><span class="l">banter-bus-database-seed</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">variables</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">MONGO_INITDB_ROOT_USERNAME</span><span class="p">:</span><span class="w"> </span><span class="l">banterbus</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">MONGO_INITDB_ROOT_PASSWORD</span><span class="p">:</span><span class="w"> </span><span class="l">banterbus</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">MONGO_INITDB_DATABASE</span><span class="p">:</span><span class="w"> </span><span class="l">test</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">BANTER_BUS_MANAGEMENT_API_DB_USERNAME</span><span class="p">:</span><span class="w"> </span><span class="l">banterbus</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">BANTER_BUS_MANAGEMENT_API_DB_PASSWORD</span><span class="p">:</span><span class="w"> </span><span class="l">banterbus</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">BANTER_BUS_MANAGEMENT_API_DB_HOST</span><span class="p">:</span><span class="w"> </span><span class="l">banter-bus-database</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">BANTER_BUS_MANAGEMENT_API_DB_PORT</span><span class="p">:</span><span class="w"> </span><span class="m">27017</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">BANTER_BUS_MANAGEMENT_API_DB_NAME</span><span class="p">:</span><span class="w"> </span><span class="l">test</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">BANTER_BUS_MANAGEMENT_API_WEB_PORT</span><span class="p">:</span><span class="w"> </span><span class="m">8090</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">BANTER_BUS_MANAGEMENT_API_CLIENT_ID</span><span class="p">:</span><span class="w"> </span><span class="l">client_id</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">BANTER_BUS_MANAGEMENT_API_USE_AUTH</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;False&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">MONGO_HOSTNAME</span><span class="p">:</span><span class="w"> </span><span class="l">banter-bus-database:27017</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">BANTER_BUS_CORE_API_DB_USERNAME</span><span class="p">:</span><span class="w"> </span><span class="l">banterbus</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">BANTER_BUS_CORE_API_DB_PASSWORD</span><span class="p">:</span><span class="w"> </span><span class="l">banterbus</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">BANTER_BUS_CORE_API_DB_HOST</span><span class="p">:</span><span class="w"> </span><span class="l">banter-bus-database</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">BANTER_BUS_CORE_API_DB_PORT</span><span class="p">:</span><span class="w"> </span><span class="m">27017</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">BANTER_BUS_CORE_API_DB_NAME</span><span class="p">:</span><span class="w"> </span><span class="l">test</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">BANTER_BUS_CORE_API_MANAGEMENT_API_URL</span><span class="p">:</span><span class="w"> </span><span class="l">http://banter-bus-management-api</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">BANTER_BUS_CORE_API_MANAGEMENT_API_PORT</span><span class="p">:</span><span class="w"> </span><span class="m">8090</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">BANTER_BUS_CORE_API_CLIENT_ID</span><span class="p">:</span><span class="w"> </span><span class="l">client_id</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">BANTER_BUS_CORE_API_USE_AUTH</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;False&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">BANTER_BUS_CORE_API_MESSAGE_QUEUE_HOST</span><span class="p">:</span><span class="w"> </span><span class="l">banter-bus-message-queue</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">BANTER_BUS_CORE_API_MESSAGE_QUEUE_PORT</span><span class="p">:</span><span class="w"> </span><span class="m">6379</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">stages</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">test</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">before_script</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">pip download --dest=${PIP_DOWNLOAD_DIR} poetry</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">pip install --find-links=${PIP_DOWNLOAD_DIR} poetry</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">poetry config virtualenvs.in-project true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">poetry install -vv</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">test:lint</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">stage</span><span class="p">:</span><span class="w"> </span><span class="l">test</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">only</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">merge_request</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">script</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">poetry run pre-commit run --all-files</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">test:unit-tests</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">stage</span><span class="p">:</span><span class="w"> </span><span class="l">test</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">only</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">merge_request</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">script</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">poetry run pytest -v tests/unit</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">test:integration-tests</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">stage</span><span class="p">:</span><span class="w"> </span><span class="l">test</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">only</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">merge_request</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">extends</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">.test</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">script</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">poetry run pytest -v tests/integration</span><span class="w">
</span></span></span></code></pre></div><p>The above looks quite complicated, but very simply we install our dependencies for each job the <code>before_script</code> section is used in all jobs.
All jobs also use <code>python:3.9.8</code> image, this is where our code is cloned into the CI pipeline.</p>
<p>Where our <code>.pre-commit-config.yaml</code> looks something like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yml" data-lang="yml"><span class="line"><span class="cl"><span class="nt">repos</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">repo</span><span class="p">:</span><span class="w"> </span><span class="l">https://github.com/pre-commit/pre-commit-hooks</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">rev</span><span class="p">:</span><span class="w"> </span><span class="l">v3.3.0</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">hooks</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">id</span><span class="p">:</span><span class="w"> </span><span class="l">check-yaml</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">args</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">&#34;--allow-multiple-documents&#34;</span><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">repo</span><span class="p">:</span><span class="w"> </span><span class="l">local</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">hooks</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">id</span><span class="p">:</span><span class="w"> </span><span class="l">forbidden-files</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">forbidden files</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">entry</span><span class="p">:</span><span class="w"> </span><span class="l">found copier update rejection files; review them and remove them</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">language</span><span class="p">:</span><span class="w"> </span><span class="l">fail</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">files</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;\\.rej$&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">id</span><span class="p">:</span><span class="w"> </span><span class="l">black</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">black</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">entry</span><span class="p">:</span><span class="w"> </span><span class="l">poetry run black</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">language</span><span class="p">:</span><span class="w"> </span><span class="l">system</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">types</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="l">python]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">id</span><span class="p">:</span><span class="w"> </span><span class="l">flake8</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">flake8</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">entry</span><span class="p">:</span><span class="w"> </span><span class="l">poetry run flake8</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">language</span><span class="p">:</span><span class="w"> </span><span class="l">system</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">types</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="l">python]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">id</span><span class="p">:</span><span class="w"> </span><span class="l">isort</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">isort</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">entry</span><span class="p">:</span><span class="w"> </span><span class="l">poetry run isort --settings-path=.</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">language</span><span class="p">:</span><span class="w"> </span><span class="l">system</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">types</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="l">python]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">id</span><span class="p">:</span><span class="w"> </span><span class="l">pyupgrade</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">pyupgrade</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">entry</span><span class="p">:</span><span class="w"> </span><span class="l">poetry run pyupgrade</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">language</span><span class="p">:</span><span class="w"> </span><span class="l">system</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">types</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="l">python]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">args</span><span class="p">:</span><span class="w"> </span><span class="p">[</span>--<span class="l">py310-plus]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">id</span><span class="p">:</span><span class="w"> </span><span class="l">mypy</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">mypy</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">description</span><span class="p">:</span><span class="w"> </span><span class="l">Check python types.</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">entry</span><span class="p">:</span><span class="w"> </span><span class="l">poetry run mypy</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">language</span><span class="p">:</span><span class="w"> </span><span class="l">system</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">types</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="l">python]</span><span class="w">
</span></span></span></code></pre></div><p><code>pre-commit</code> is a library we can use to add pre-commit hooks before we commit our code to git. Adding some checks that
the code is consistent with the rules we defined. We can also just use it as a lint job, multiple linting tools together. Simplified. Hence
here we are checking for code formatting, linting, import sorting etc. The details don&rsquo;t matter but at the moment we need to have
a virtualenv locally to run this.</p>
<h3 id="integration-tests">Integration Tests</h3>
<p>A slightly more interesting job is integration tests, it requires other docker containers, as our tests need Postgres and Redis to run.
We can define these as services and then reference them in our job like so:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yml" data-lang="yml"><span class="line"><span class="cl"><span class="nt">test:integration-tests</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">stage</span><span class="p">:</span><span class="w"> </span><span class="l">test</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">only</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">merge_request</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">extends</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">.test</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">script</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">poetry run pytest -v tests/integration</span><span class="w">
</span></span></span></code></pre></div><p>Note the <code>extends</code> clause, which essentially merges the <code>.test</code> section with our job so it will look something like:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yml" data-lang="yml"><span class="line"><span class="cl"><span class="nt">test:integration-tests</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">stage</span><span class="p">:</span><span class="w"> </span><span class="l">test</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">only</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">merge_request</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">services</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">mongo:4.4.4</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">alias</span><span class="p">:</span><span class="w"> </span><span class="l">banter-bus-database</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">redis:6.2.4</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">alias</span><span class="p">:</span><span class="w"> </span><span class="l">banter-bus-message-queue</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">registry.gitlab.com/banter-bus/banter-bus-management-api:test</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">alias</span><span class="p">:</span><span class="w"> </span><span class="l">banter-bus-management-api</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">registry.gitlab.com/banter-bus/banter-bus-management-api/database-seed:latest</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">alias</span><span class="p">:</span><span class="w"> </span><span class="l">banter-bus-database-seed</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">variables</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">MONGO_INITDB_ROOT_USERNAME</span><span class="p">:</span><span class="w"> </span><span class="l">banterbus</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">MONGO_INITDB_ROOT_PASSWORD</span><span class="p">:</span><span class="w"> </span><span class="l">banterbus</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">MONGO_INITDB_DATABASE</span><span class="p">:</span><span class="w"> </span><span class="l">test</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">BANTER_BUS_MANAGEMENT_API_DB_USERNAME</span><span class="p">:</span><span class="w"> </span><span class="l">banterbus</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">BANTER_BUS_MANAGEMENT_API_DB_PASSWORD</span><span class="p">:</span><span class="w"> </span><span class="l">banterbus</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">BANTER_BUS_MANAGEMENT_API_DB_HOST</span><span class="p">:</span><span class="w"> </span><span class="l">banter-bus-database</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">BANTER_BUS_MANAGEMENT_API_DB_PORT</span><span class="p">:</span><span class="w"> </span><span class="m">27017</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">BANTER_BUS_MANAGEMENT_API_DB_NAME</span><span class="p">:</span><span class="w"> </span><span class="l">test</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">BANTER_BUS_MANAGEMENT_API_WEB_PORT</span><span class="p">:</span><span class="w"> </span><span class="m">8090</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">BANTER_BUS_MANAGEMENT_API_CLIENT_ID</span><span class="p">:</span><span class="w"> </span><span class="l">client_id</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">BANTER_BUS_MANAGEMENT_API_USE_AUTH</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;False&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">MONGO_HOSTNAME</span><span class="p">:</span><span class="w"> </span><span class="l">banter-bus-database:27017</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">BANTER_BUS_CORE_API_DB_USERNAME</span><span class="p">:</span><span class="w"> </span><span class="l">banterbus</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  	</span><span class="nt">BANTER_BUS_CORE_API_DB_PASSWORD</span><span class="p">:</span><span class="w"> </span><span class="l">banterbus</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  	</span><span class="nt">BANTER_BUS_CORE_API_DB_HOST</span><span class="p">:</span><span class="w"> </span><span class="l">banter-bus-database</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  	</span><span class="nt">BANTER_BUS_CORE_API_DB_PORT</span><span class="p">:</span><span class="w"> </span><span class="m">27017</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  	</span><span class="nt">BANTER_BUS_CORE_API_DB_NAME</span><span class="p">:</span><span class="w"> </span><span class="l">test</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  	</span><span class="nt">BANTER_BUS_CORE_API_MANAGEMENT_API_URL</span><span class="p">:</span><span class="w"> </span><span class="l">http://banter-bus-management-api</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  	</span><span class="nt">BANTER_BUS_CORE_API_MANAGEMENT_API_PORT</span><span class="p">:</span><span class="w"> </span><span class="m">8090</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  	</span><span class="nt">BANTER_BUS_CORE_API_CLIENT_ID</span><span class="p">:</span><span class="w"> </span><span class="l">client_id</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  	</span><span class="nt">BANTER_BUS_CORE_API_USE_AUTH</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;False&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  	</span><span class="nt">BANTER_BUS_CORE_API_MESSAGE_QUEUE_HOST</span><span class="p">:</span><span class="w"> </span><span class="l">banter-bus-message-queue</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  	</span><span class="nt">BANTER_BUS_CORE_API_MESSAGE_QUEUE_PORT</span><span class="p">:</span><span class="w"> </span><span class="m">6379</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">script</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">poetry run pytest -v tests/integration</span><span class="w">
</span></span></span></code></pre></div><p>We also need to define a bunch of environment variables in this case so our containers can communicate
with each other. Now, these are of course specific to my apps. But you can imagine a real-life project also
needing a bunch of environment variables. As you can see this can get a bit messy and what is
running locally may differ slightly from what is running in CI.</p>
<p>I have been caught out by these env variables in the past. Note variables like
<code>BANTER_BUS_CORE_API_MANAGEMENT_API_URL: http://banter-bus-management-api</code>. The name of the
container must match the URL we have provided</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yml" data-lang="yml"><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">registry.gitlab.com/banter-bus/banter-bus-management-api:test</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">alias</span><span class="p">:</span><span class="w"> </span><span class="l">banter-bus-management-api</span><span class="w">
</span></span></span></code></pre></div><p>Docker DNS (link to DNS) is clever enough to work out the IP address.
This is also different now to how we are running it locally.</p>
<h2 id="after">After</h2>
<p>Now we are running all our dev tasks in docker. We will use docker-compose to manage all of the containers,
docker-compose makes managing multiple containers a lot easier. We define all of them in our
<code>docker-compose.yml</code> file.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yml" data-lang="yml"><span class="line"><span class="cl"><span class="nt">services</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">app</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">container_name</span><span class="p">:</span><span class="w"> </span><span class="l">banter-bus-core-api</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">build</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">context</span><span class="p">:</span><span class="w"> </span><span class="l">.</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">dockerfile</span><span class="p">:</span><span class="w"> </span><span class="l">Dockerfile</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">target</span><span class="p">:</span><span class="w"> </span><span class="l">development</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">cache_from</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span>- <span class="l">registry.gitlab.com/banter-bus/banter-bus-core-api:development</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">environment</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">XDG_DATA_HOME</span><span class="p">:</span><span class="w"> </span><span class="l">/commandhistory/</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">BANTER_BUS_CORE_API_DB_USERNAME</span><span class="p">:</span><span class="w"> </span><span class="l">banterbus</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">BANTER_BUS_CORE_API_DB_PASSWORD</span><span class="p">:</span><span class="w"> </span><span class="l">banterbus</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">BANTER_BUS_CORE_API_DB_HOST</span><span class="p">:</span><span class="w"> </span><span class="l">banter-bus-database</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">BANTER_BUS_CORE_API_DB_PORT</span><span class="p">:</span><span class="w"> </span><span class="m">27017</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">BANTER_BUS_CORE_API_DB_NAME</span><span class="p">:</span><span class="w"> </span><span class="l">test</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">BANTER_BUS_CORE_API_MANAGEMENT_API_URL</span><span class="p">:</span><span class="w"> </span><span class="l">http://banter-bus-management-api</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">BANTER_BUS_CORE_API_MANAGEMENT_API_PORT</span><span class="p">:</span><span class="w"> </span><span class="m">8090</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">BANTER_BUS_CORE_API_CLIENT_ID</span><span class="p">:</span><span class="w"> </span><span class="l">client_id</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">BANTER_BUS_CORE_API_USE_AUTH</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;False&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">BANTER_BUS_CORE_API_MESSAGE_QUEUE_HOST</span><span class="p">:</span><span class="w"> </span><span class="l">banter-bus-message-queue</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">BANTER_BUS_CORE_API_MESSAGE_QUEUE_PORT</span><span class="p">:</span><span class="w"> </span><span class="m">6379</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">ports</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="m">127.0.0.1</span><span class="p">:</span><span class="m">8080</span><span class="p">:</span><span class="m">8080</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">volumes</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">./:/app</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">/app/.venv/</span><span class="w"> </span><span class="c"># This stops local .venv getting mounted</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">depends_on</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">database</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">management-api</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">message-queue</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">database-seed</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">management-api</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">container_name</span><span class="p">:</span><span class="w"> </span><span class="l">banter-bus-management-api</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">registry.gitlab.com/banter-bus/banter-bus-management-api:test</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">environment</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">BANTER_BUS_MANAGEMENT_API_DB_USERNAME</span><span class="p">:</span><span class="w"> </span><span class="l">banterbus</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">BANTER_BUS_MANAGEMENT_API_DB_PASSWORD</span><span class="p">:</span><span class="w"> </span><span class="l">banterbus</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">BANTER_BUS_MANAGEMENT_API_DB_HOST</span><span class="p">:</span><span class="w"> </span><span class="l">banter-bus-database</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">BANTER_BUS_MANAGEMENT_API_DB_NAME</span><span class="p">:</span><span class="w"> </span><span class="l">banter_bus_management_api</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">BANTER_BUS_MANAGEMENT_API_WEB_PORT</span><span class="p">:</span><span class="w"> </span><span class="m">8090</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">BANTER_BUS_MANAGEMENT_API_CLIENT_ID</span><span class="p">:</span><span class="w"> </span><span class="l">client_id</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">BANTER_BUS_MANAGEMENT_API_USE_AUTH</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;False&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">ports</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="m">127.0.0.1</span><span class="p">:</span><span class="m">8090</span><span class="p">:</span><span class="m">8090</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">depends_on</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">database</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">database</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">container_name</span><span class="p">:</span><span class="w"> </span><span class="l">banter-bus-database</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">mongo:4.4.4</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">environment</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">MONGO_INITDB_ROOT_USERNAME</span><span class="p">:</span><span class="w"> </span><span class="l">banterbus</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">MONGO_INITDB_ROOT_PASSWORD</span><span class="p">:</span><span class="w"> </span><span class="l">banterbus</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">MONGO_INITDB_DATABASE</span><span class="p">:</span><span class="w"> </span><span class="l">banterbus</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">volumes</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">/data/db</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">ports</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="m">27017</span><span class="p">:</span><span class="m">27017</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">database-gui</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">container_name</span><span class="p">:</span><span class="w"> </span><span class="l">banter-bus-database-gui</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">mongoclient/mongoclient:4.0.1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">depends_on</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">database</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">environment</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">MONGOCLIENT_DEFAULT_CONNECTION_URL=mongodb://banterbus:banterbus@banter-bus-database:27017</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">volumes</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">/data/db mongoclient/mongoclient</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">ports</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="m">127.0.0.1</span><span class="p">:</span><span class="m">4000</span><span class="p">:</span><span class="m">3000</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">database-seed</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">container_name</span><span class="p">:</span><span class="w"> </span><span class="l">banter-bus-database-seed</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">registry.gitlab.com/banter-bus/banter-bus-management-api/database-seed:latest</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">environment</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">MONGO_INITDB_ROOT_USERNAME</span><span class="p">:</span><span class="w"> </span><span class="l">banterbus</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">MONGO_INITDB_ROOT_PASSWORD</span><span class="p">:</span><span class="w"> </span><span class="l">banterbus</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">MONGO_INITDB_DATABASE</span><span class="p">:</span><span class="w"> </span><span class="l">banter_bus_management_api</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">MONGO_HOSTNAME</span><span class="p">:</span><span class="w"> </span><span class="l">banter-bus-database:27017</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">depends_on</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">database</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">message-queue</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">container_name</span><span class="p">:</span><span class="w"> </span><span class="l">banter-bus-message-queue</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">redis:6.2.4</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">volumes</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">/data/datastore /data</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">ports</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="m">127.0.0.1</span><span class="p">:</span><span class="m">6379</span><span class="p">:</span><span class="m">6379</span><span class="w">
</span></span></span></code></pre></div><p>Note: This file was already defined just not used in CI because I wanted to provide an easy way to start up my &ldquo;tech stack&rdquo;.
So the file had gone unused.</p>
<p>How do run our dev tasks?</p>
<ul>
<li>lint: <code>docker compose run app poetry run pre-commit run --all-files</code></li>
<li>integration tests: <code>docker compose run app poetry run pytest -v tests/integration</code></li>
</ul>
<p>Then our CI pipelines could look simply like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yml" data-lang="yml"><span class="line"><span class="cl"><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">docker</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">services</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">docker:dind</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">variables</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">DOCKER_DRIVER</span><span class="p">:</span><span class="w"> </span><span class="l">overlay2</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">DOCKER_HOST</span><span class="p">:</span><span class="w"> </span><span class="l">tcp://docker:2375</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">before_script</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">docker compose build</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">stages</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">test</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">test:lint</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">stage</span><span class="p">:</span><span class="w"> </span><span class="l">test</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">only</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">merge_request</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">script</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">docker compose run app poetry run pre-commit run --all-files</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">test:unit-tests</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">stage</span><span class="p">:</span><span class="w"> </span><span class="l">test</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">only</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">merge_request</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">script</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">docker compose run app poetry run pytest -v tests/unit</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">test:integration</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">stage</span><span class="p">:</span><span class="w"> </span><span class="l">test</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">only</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">merge_request</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">script</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="w"> </span><span class="l">docker compose run app poetry run pytest -v tests/integration</span><span class="w">
</span></span></span></code></pre></div><p>Now before job we build our docker images, <code>docker compose build</code>.
Then to run the dev task we do something like:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">docker compose run app &lt;<span class="nb">command</span> to run&gt;
</span></span></code></pre></div><p>So to run unit tests we could do:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">docker compose run app poetry run pytest -v tests/unit
</span></span></code></pre></div><h3 id="aside">Aside</h3>
<p>We could simplify this if we use <code>makefile</code> and make the target be  <code>poetry run pytest -v tests/unit</code>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-makefile" data-lang="makefile"><span class="line"><span class="cl"><span class="nf">.PHONY</span><span class="o">:</span> <span class="n">unit_tests</span>
</span></span><span class="line"><span class="cl"><span class="nf">unit_tests</span><span class="o">:</span> <span class="c">## Run all the unit tests
</span></span></span><span class="line"><span class="cl">	@poetry run pytest -v tests/unit
</span></span></code></pre></div><p>Then our ci job would look something like:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yml" data-lang="yml"><span class="line"><span class="cl"><span class="nt">test:unit-tests</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">stage</span><span class="p">:</span><span class="w"> </span><span class="l">test</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">only</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">merge_request</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">script</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">make unit_tests</span><span class="w">
</span></span></span></code></pre></div><p>Which I think is a lot more readable and a lot easier to type. We can also
leverage auto-complete on the terminal and add help targets. So a user can see all the targets
they can run.</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>How DNS works with Docker</title>
      <link>https://haseebmajid.dev/posts/2020-10-27-how-dns-works-with-docker/</link>
      <pubDate>Tue, 27 Oct 2020 00:00:00 +0000</pubDate>
      <author>Me</author>
      <guid>https://haseebmajid.dev/posts/2020-10-27-how-dns-works-with-docker/</guid>
      <description>&lt;p&gt;In this article, we will briefly go over what DNS (domain name system) is and explain how it is used in conjunction
with Docker 🐳.&lt;/p&gt;
&lt;h2 id=&#34;dns&#34;&gt;DNS&lt;/h2&gt;
&lt;p&gt;You can think of DNS like a phonebook, except instead of people&amp;rsquo;s name and phone numbers, it stores domains names and
IP addresses (this can be either IPv4 or IPv6). Where a domain name is used to identify resources i.e. &lt;code&gt;google.com&lt;/code&gt; is a
domain name. This is how DNS works:&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>In this article, we will briefly go over what DNS (domain name system) is and explain how it is used in conjunction
with Docker 🐳.</p>
<h2 id="dns">DNS</h2>
<p>You can think of DNS like a phonebook, except instead of people&rsquo;s name and phone numbers, it stores domains names and
IP addresses (this can be either IPv4 or IPv6). Where a domain name is used to identify resources i.e. <code>google.com</code> is a
domain name. This is how DNS works:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">google.com:     8.8.8.8
</span></span><span class="line"><span class="cl">cloudflare.com: 1.1.1.1
</span></span></code></pre></div><h3 id="example">Example</h3>
<p>You can manually send a DNS request (and get a response) using the <code>dig</code> command. So for example, we can do something
like this.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">dig +short google.com
</span></span><span class="line"><span class="cl">172.217.169.78
</span></span></code></pre></div><h3 id="records">Records</h3>
<p>Each DNS entry can be of varying types, some of the most common DNS types (referred to as records) are:</p>
<p>A: Points a domain name to an IPv4 address i.e. <code>8.8.8.8</code>
AAAA: Same as an A record except points to an IPv6 address i.e. <code>2001:db8:0:1</code>
CNAME: Canonical Name points one domain to another domain name, one common use case is to point <code>www.example.com</code> -&gt; <code>example.com</code>. This way we only need to update the A record of <code>example.com</code>, not both domains.</p>
<h4 id="aaaa-example">AAAA Example</h4>
<p>To specify a AAAA (quad A) record we can do something like:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">dig +short google.com AAAA
</span></span><span class="line"><span class="cl">2a00:1450:4009:810::200e
</span></span></code></pre></div><details
  class="notice important"
  open="true"
>
    <summary class="notice-title">More Details</summary>
  
  In a future article, I will do a deeper dive into the mechanics of how DNS works and the actual process of converting a domain name to an IP address.
</details>

<p>So that&rsquo;s DNS in a nutshell! On to how it relates to Docker.</p>
<details
  class="notice tip"
  open="true"
>
    <summary class="notice-title">tl:dr</summary>
  
  DNS is a system used to convert domain names into IP addresses because it&rsquo;s much easier for humans to remember names as compared with numbers.
</details>

<h2 id="docker">Docker</h2>
<p>For the sake of this article, we will be using the following docker-compose file:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">version</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;3.5&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">services</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">web_server</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">container_name</span><span class="p">:</span><span class="w"> </span><span class="l">nginx</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">build</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">context</span><span class="p">:</span><span class="w"> </span><span class="l">.</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">dockerfile</span><span class="p">:</span><span class="w"> </span><span class="l">docker/nginx/Dockerfile</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">ports</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="m">80</span><span class="p">:</span><span class="m">80</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">depends_on</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">app</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">app</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">container_name</span><span class="p">:</span><span class="w"> </span><span class="l">flask</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">build</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">context</span><span class="p">:</span><span class="w"> </span><span class="l">.</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">dockerfile</span><span class="p">:</span><span class="w"> </span><span class="l">docker/flask/Dockerfile</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">env_file</span><span class="p">:</span><span class="w"> </span><span class="l">docker/database.conf</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">expose</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="m">8080</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">depends_on</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">database</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">database</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">container_name</span><span class="p">:</span><span class="w"> </span><span class="l">postgres</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">postgres:latest</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">env_file</span><span class="p">:</span><span class="w"> </span><span class="l">docker/database.conf</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">ports</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="m">5432</span><span class="p">:</span><span class="m">5432</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">volumes</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">db_volume:/var/lib/postgresql</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">volumes</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">db_volume</span><span class="p">:</span><span class="w">
</span></span></span></code></pre></div><p>It will create three containers, Nginx, a flask app and a Postgres database, when we run <code>docker-compose up --build</code>, in particular take <strong>note</strong> of the <code>container_name</code>(s): <code>postgres</code>, <code>nginx</code>, <code>flask</code>.</p>
<details
  class="notice tip"
  open="true"
>
    <summary class="notice-title">Source Code</summary>
  
  The source code for those Docker containers can be found
<a href="https://gitlab.com/hmajid2301/articles/-/tree/master/7.%20Multi%20Docker%20Container%20with%20Nginx%2C%20Flask%20and%C2%A0MySQL/source_code">here</a>
</details>

<h3 id="nginx">Nginx</h3>
<p>Our <code>nginx</code> config file looks something like:</p>
<pre tabindex="0"><code class="language-conf" data-lang="conf">server {
  listen 80;
  server_name _;

  location / {
    try_files $uri @app;
  }

  location @app {
    include /etc/nginx/uwsgi_params;
    uwsgi_pass flask:8080;
  }
}</code></pre>
<p>This Nginx configuration file tells Nginx to pass any requests sent on <code>/</code> path to the
uwsgi server running in the <code>flask</code> docker container.
Now taking a look at the <code>location @app</code> section you&rsquo;ll notice for <code>uwsgi_pass</code> we don&rsquo;t specify an IP address to send the requests
to. Instead, we use the container name, this is because within Docker containers we don&rsquo;t have to specify the other Docker
container&rsquo;s IP address to connect to it we can specify the container name. Docker&rsquo;s DNS will resolve the name into an IP address for us.</p>
<h3 id="nginx-example">Nginx Example</h3>
<p>So if I open a shell on the <code>nginx</code> container:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">docker <span class="nb">exec</span> -it nginx bash
</span></span></code></pre></div><p>Then we can do something like:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">apt update <span class="o">&amp;&amp;</span> apt install dnsutils
</span></span><span class="line"><span class="cl">dig flask +short
</span></span><span class="line"><span class="cl">172.23.0.3
</span></span></code></pre></div><p>This is particularly useful because Docker containers get assigned an IP if you don&rsquo;t specify one
(in the <code>docker-compose.yml</code>) file. Taking a look at the IP assigned to the <code>flask</code>
the container matches the IP address returned by the dig command.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">docker inspect -f <span class="s1">&#39;{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}&#39;</span> flask
</span></span><span class="line"><span class="cl">172.23.0.3
</span></span></code></pre></div><h3 id="flask-example">Flask Example</h3>
<p>Similarly in the <code>flask</code> container if we want to connect to the <code>postgres</code> database, we can just specify the host
using the container name <code>postgres</code> rather than an IP in our connection URI. As shown in the example below:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">DATABASE_CONNECTION_URI</span> <span class="o">=</span> <span class="sa">f</span><span class="s1">&#39;postgresql+psycopg2://</span><span class="si">{</span><span class="n">user</span><span class="si">}</span><span class="s1">:</span><span class="si">{</span><span class="n">password</span><span class="si">}</span><span class="s1">@postgres:5432/</span><span class="si">{</span><span class="n">database</span><span class="si">}</span><span class="s1">&#39;</span>
</span></span></code></pre></div><details
  class="notice info"
  open="true"
>
    <summary class="notice-title">Example</summary>
  
  The example above is a URI used by the SQLAlchemy library to connect to the Postgres database.
</details>

<h2 id="deep-diver">Deep Diver</h2>
<p>Let&rsquo;s take a slightly closer look into Docker&rsquo;s architecture to understand what is going on here.</p>
<h3 id="docker-engine--explained">Docker Engine 🏭 Explained</h3>
<blockquote>
<p>Docker Engine is an open-source containerization technology for building and containerizing your applications. - <a href="https://docs.docker.com/engine/">https://docs.docker.com/engine/</a></p>
</blockquote>
<p>It contains the following components:</p>
<ul>
<li>A server with a long-running daemon process dockerd</li>
<li>APIs which specify interfaces that programs can use to talk to and instruct the Docker daemon</li>
<li>A command-line interface (CLI) client docker</li>
</ul>
<p>When we install Docker we are also installing the Docker Engine.</p>
<details
  class="notice info"
  open="true"
>
    <summary class="notice-title">More Details</summary>
  
  In a future article, I will do a deeper dive into the Docker Engine as well 🐳.
</details>

<p>Briefly, how it works is we use the CLI i.e. <code>docker run</code>/<code>docker-compose</code>, which makes
API requests (on our behalf) to the Docker daemon. The Docker daemon then interacts with containerd, which is responsible for the creation/deletion of our containers. Essentially containerd is a container supervisor.</p>
<h3 id="docker-engine-and-dns">Docker Engine and DNS</h3>
<p>Now how does Docker Engine relate to DNS? As long as the two containers are on the same
network we can use the container name and resolve it using DNS. Each Docker container has a DNS resolver that forwards
DNS queries to Docker Engine, which acts as a DNS server. Docker
Engine then checks if the DNS query belongs to a container on the network that the requested container belongs to.
If it does, then Docker Engine looks up the IP address that matches a container name in its key-value store and
returns that IP back to the requesting container.</p>
<p><img
        loading="lazy"
        src="/posts/2020-10-27-how-dns-works-with-docker/images/docker_engine.png"
        type=""
        alt="Docker Engine DNS"
        
      /></p>
<details
  class="notice tip"
  open="true"
>
    <summary class="notice-title">Normal Queries</summary>
  
  For all other DNS queries the Docker Engine will use the host machine&rsquo;s DNS settings,
unless overwritten (explained below in the <code>Misc</code> section).
</details>

<details
  class="notice info"
  open="true"
>
    <summary class="notice-title">Daemon vs Engine</summary>
  
  <blockquote>
<p>Docker Daemon checks the client request and communicates with the Docker components to perform a service whereas, Docker Engine or Docker is the base engine installed on your host machine to build and run containers using Docker components and services - Anjali Nair, <a href="https://www.quora.com/What-is-the-difference-between-the-Docker-Engine-and-Docker-Daemon">Quora</a></p>
</blockquote>
</details>

<h2 id="misc">Misc</h2>
<details
  class="notice info"
  open="true"
>
    <summary class="notice-title">Docker DNS Settings</summary>
  
  We can customise Docker&rsquo;s default DNS settings by using the <code>--dns</code> flag, for example, to use Google&rsquo;s DNS you could
go <code>--dns 8.8.8.8</code>. You can also provide your DNS records for the container to use by using the <code>--extra_hosts</code> flag.
For example <code>--extra_hosts somehost:162.242.195.82</code>.
</details>

<details
  class="notice warnings"
  open="true"
>
    <summary class="notice-title">Docker DNS Settings</summary>
  
  Custom hosts defined in the <code>/etc/hosts</code> file are ignored. They must be passed in using the <code>extra_hosts</code> flag.
</details>

<h2 id="appendix">Appendix</h2>
<ul>
<li><a href="https://www.cloudflare.com/en-gb/learning/dns/what-is-dns/">What is DNS?</a> by CloudFlare</li>
<li><a href="https://www.bluehost.com/help/article/dns-records-explained">DNS Records</a> Explained</li>
<li><a href="https://www.serverwatch.com/server-news/how-docker-engine-works-to-enable-containers/">Docker Engine</a></li>
<li><a href="https://stackoverflow.com/questions/41645665/how-containerd-compares-to-runc">Docker in detail</a> SO Post</li>
<li><a href="https://success.mirantis.com/article/networking">Docker Swarm Architecture</a> (relevant to normal Docker)</li>
</ul>
]]></content:encoded>
    </item>
    
    <item>
      <title>How to use Gitlab CI, Pytest and docker-compose together</title>
      <link>https://haseebmajid.dev/posts/2020-06-22-how-to-use-gitlab-ci-pytest-and-docker-compose-together/</link>
      <pubDate>Mon, 22 Jun 2020 00:00:00 +0000</pubDate>
      <author>Me</author>
      <guid>https://haseebmajid.dev/posts/2020-06-22-how-to-use-gitlab-ci-pytest-and-docker-compose-together/</guid>
      <description>&lt;p&gt;On a recent project, I was working on, I wanted to test my web service using docker-compose where I can run and kill
Docker containers used by the application and see how my web application reacts to that. In this article, we will
go over how you start docker containers using docker-compose from within Gitlab CI.&lt;/p&gt;
&lt;p&gt;&lt;img
        loading=&#34;lazy&#34;
        src=&#34;../../posts/2020-06-22-how-to-use-gitlab-ci-pytest-and-docker-compose-together/images/main.png&#34;
        type=&#34;&#34;
        alt=&#34;main&#34;
        
      /&gt;&lt;/p&gt;
&lt;p&gt;The diagram above is a visualisation of what we are trying to achieve. We want to spawn Docker containers using docker-compose
from within our job. The spawning and destruction of these Docker containers will be done via our Python code. We can achieve
this by using dind (Docker in Docker). I have written a previous article on this topic which you can read more about
&lt;a href=&#34;../../posts/2020-05-01-how-to-use-dind-with-gitlab-ci/&#34;&gt;here&lt;/a&gt;. This article assumes you already somewhat familiar
with Docker, docker-compose and Pytest.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>On a recent project, I was working on, I wanted to test my web service using docker-compose where I can run and kill
Docker containers used by the application and see how my web application reacts to that. In this article, we will
go over how you start docker containers using docker-compose from within Gitlab CI.</p>
<p><img
        loading="lazy"
        src="/posts/2020-06-22-how-to-use-gitlab-ci-pytest-and-docker-compose-together/images/main.png"
        type=""
        alt="main"
        
      /></p>
<p>The diagram above is a visualisation of what we are trying to achieve. We want to spawn Docker containers using docker-compose
from within our job. The spawning and destruction of these Docker containers will be done via our Python code. We can achieve
this by using dind (Docker in Docker). I have written a previous article on this topic which you can read more about
<a href="/posts/2020-05-01-how-to-use-dind-with-gitlab-ci/">here</a>. This article assumes you already somewhat familiar
with Docker, docker-compose and Pytest.</p>
<p>This compose file will be used to start our Docker containers.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">version</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;3&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">services</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">service1</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">container_name</span><span class="p">:</span><span class="w"> </span><span class="l">container1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">docker</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">command</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">&#34;tail&#34;</span><span class="p">,</span><span class="w"> </span><span class="s2">&#34;-f&#34;</span><span class="p">,</span><span class="w"> </span><span class="s2">&#34;/dev/null&#34;</span><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">service2</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">container_name</span><span class="p">:</span><span class="w"> </span><span class="l">container2</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">docker</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">command</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">&#34;tail&#34;</span><span class="p">,</span><span class="w"> </span><span class="s2">&#34;-f&#34;</span><span class="p">,</span><span class="w"> </span><span class="s2">&#34;/dev/null&#34;</span><span class="p">]</span><span class="w">
</span></span></span></code></pre></div><h2 id="gitlab-ci">Gitlab CI</h2>
<p>Next, we have our <code>.gitlab-ci.yml</code> file, this file is used to tell Gitlab CI what our CI jobs should do.
In this example, we have one job called <code>test:integration</code> which will run our integration tests. But before we do that
we need a way to access the Docker daemon from within Gitlab CI, this can be done by using the <code>docker:dind</code> service.</p>
<p>The docker:dind image automatically using its entry point starts a docker daemon. We need to use this daemon to
start/stop our Docker images within CI. The docker:dind (dind = Docker in Docker) image is almost identical to
the docker image. The difference being the dind image starts a Docker daemon. In this example, the job will
use the docker image as the client and connect to the daemon running in this container.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">services</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">docker:dind</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">tests:integration</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">stage</span><span class="p">:</span><span class="w"> </span><span class="l">test</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">hmajid2301/dind-docker-compose</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">script</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">pip install pytest docker lovely-pytest-docker</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">pytest -s tests/test_integration.py</span><span class="w">
</span></span></span></code></pre></div><p>The job itself is very simple, it uses a container which already comes with <code>docker</code> and <code>docker-compose</code>. Next we
install the dependencies we need for our tests. Then it runs our tests.</p>
<h2 id="tests">Tests</h2>
<p>Now onto our actual tests file. It looks more complicated than it is:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">docker</span> <span class="k">as</span> <span class="nn">docker_py</span>
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">pytest</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">docker_client</span> <span class="o">=</span> <span class="n">docker_py</span><span class="o">.</span><span class="n">from_env</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="n">docker_compose</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nd">@pytest.fixture</span><span class="p">(</span><span class="n">scope</span><span class="o">=</span><span class="s2">&#34;session&#34;</span><span class="p">,</span> <span class="n">autouse</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">docker</span><span class="p">(</span><span class="n">docker_services</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="k">global</span> <span class="n">docker_compose</span>
</span></span><span class="line"><span class="cl">    <span class="n">docker_compose</span> <span class="o">=</span> <span class="n">docker_services</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nd">@pytest.fixture</span><span class="p">(</span><span class="n">scope</span><span class="o">=</span><span class="s2">&#34;session&#34;</span><span class="p">,</span> <span class="n">autouse</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">setup</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">    <span class="n">docker_compose</span><span class="o">.</span><span class="n">start</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="k">yield</span>
</span></span><span class="line"><span class="cl">    <span class="n">docker_compose</span><span class="o">.</span><span class="n">shutdown</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">kill_container</span><span class="p">(</span><span class="n">container_name</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="n">container</span> <span class="o">=</span> <span class="n">get_container</span><span class="p">(</span><span class="n">container_name</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">container</span><span class="o">.</span><span class="n">kill</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="n">container</span><span class="o">.</span><span class="n">remove</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">get_container</span><span class="p">(</span><span class="n">container_name</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="n">containers</span> <span class="o">=</span> <span class="n">docker_client</span><span class="o">.</span><span class="n">containers</span><span class="o">.</span><span class="n">list</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="n">container</span> <span class="ow">in</span> <span class="n">containers</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="n">container</span><span class="o">.</span><span class="n">name</span> <span class="o">==</span> <span class="n">container_name</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="k">return</span> <span class="n">container</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">start_container</span><span class="p">(</span><span class="n">service_name</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="n">docker_compose</span><span class="o">.</span><span class="n">start</span><span class="p">(</span><span class="n">service_name</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">test_two_containers</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">    <span class="n">containers</span> <span class="o">=</span> <span class="n">docker_client</span><span class="o">.</span><span class="n">containers</span><span class="o">.</span><span class="n">list</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="k">assert</span> <span class="nb">len</span><span class="p">(</span><span class="n">containers</span><span class="p">)</span> <span class="o">==</span> <span class="mi">2</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">test_kill_container1</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">    <span class="n">kill_container</span><span class="p">(</span><span class="s2">&#34;container1&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">containers</span> <span class="o">=</span> <span class="n">docker_client</span><span class="o">.</span><span class="n">containers</span><span class="o">.</span><span class="n">list</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="n">container1</span> <span class="o">=</span> <span class="n">get_container</span><span class="p">(</span><span class="s2">&#34;container1&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">assert</span> <span class="nb">len</span><span class="p">(</span><span class="n">containers</span><span class="p">)</span> <span class="o">==</span> <span class="mi">1</span>
</span></span><span class="line"><span class="cl">    <span class="k">assert</span> <span class="ow">not</span> <span class="n">container1</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">test_start_container1</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">    <span class="n">start_container</span><span class="p">(</span><span class="s2">&#34;service1&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">containers</span> <span class="o">=</span> <span class="n">docker_client</span><span class="o">.</span><span class="n">containers</span><span class="o">.</span><span class="n">list</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="n">container1</span> <span class="o">=</span> <span class="n">get_container</span><span class="p">(</span><span class="s2">&#34;container1&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">assert</span> <span class="nb">len</span><span class="p">(</span><span class="n">containers</span><span class="p">)</span> <span class="o">==</span> <span class="mi">2</span>
</span></span><span class="line"><span class="cl">    <span class="k">assert</span> <span class="n">container11</span>
</span></span></code></pre></div><p>The first part is the setup, we will use the python Docker library,<code>docker</code>. Which allow us to use Python code to
control our Docker daemon. The first <code>@pytest-fixture</code> called <code>docker</code> allows us to use the
<code>lovely-pytest-docker</code> library. To give us a <code>docker_compose</code> object which will allow us to again use Python code to
control our <code>docker-compose.yml</code> file (start-up/stop containers). The library also has some very nice features such
as waiting for containers or executing commands within containers. You can find the available functions
<a href="https://github.com/lovelysystems/lovely-pytest-docker/blob/master/src/lovely/pytest/docker/compose.py">here</a>.
Now we can access the <code>docker_compose</code> object by using our <code>docker_compose</code> global variable.</p>
<p>The reason we have both a library for Docker and docker-compose is because at the moment there is no way to use
<code>lovely-pytest-docker</code> (as far as I&rsquo;m aware) to stop a single container. So we need to use the standard <code>docker</code>
library to do that. We also use the standard <code>docker</code> library to find out if a container is running.</p>
<p>Next, we have the <code>setup()</code> fixture which we auto use, this means the fixture is run before our test, normally a fixture
would only be called once it has been referred to within another function. In this function, we start both of our containers
in our <code>docker-compose</code> file. This is the same as running <code>docker-compose up --build -d</code>. Next we <code>yield</code>, how exactly the
<code>yield</code> command works I won&rsquo;t go over in this article, all you have to know is that everything after the yield will only
be run after all of our tests. In this case we teardown our containers (stop them). This is the same as running <code>docker-compose down</code>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">docker</span> <span class="k">as</span> <span class="nn">docker_py</span>
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">pytest</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">docker_client</span> <span class="o">=</span> <span class="n">docker_py</span><span class="o">.</span><span class="n">from_env</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="n">docker_compose</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nd">@pytest.fixture</span><span class="p">(</span><span class="n">scope</span><span class="o">=</span><span class="s2">&#34;session&#34;</span><span class="p">,</span> <span class="n">autouse</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">docker</span><span class="p">(</span><span class="n">docker_services</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="k">global</span> <span class="n">docker_compose</span>
</span></span><span class="line"><span class="cl">    <span class="n">docker_compose</span> <span class="o">=</span> <span class="n">docker_services</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nd">@pytest.fixture</span><span class="p">(</span><span class="n">scope</span><span class="o">=</span><span class="s2">&#34;session&#34;</span><span class="p">,</span> <span class="n">autouse</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">setup</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">    <span class="n">docker_compose</span><span class="o">.</span><span class="n">start</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="k">yield</span>
</span></span><span class="line"><span class="cl">    <span class="n">docker_compose</span><span class="o">.</span><span class="n">shutdown</span><span class="p">()</span>
</span></span></code></pre></div><p>The next part of our file contains some helper functions I&rsquo;ve written. These functions can be used by multiple tests.
This helps our tests file stay more DRY (do not repeat yourself). You may well want to make these part of a
(helper) class that you expose as a fixture. If you wanted to structure these properly so they can be accessed by
more than one file. Also whilst we are on this topic, we may want to move our fixture to <code>conftest.py</code>, again to allow
other files to use the same fixture we have defined here. But to keep this example simpler we will leave it here.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">kill_container</span><span class="p">(</span><span class="n">container_name</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="n">container</span> <span class="o">=</span> <span class="n">get_container</span><span class="p">(</span><span class="n">container_name</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">container</span><span class="o">.</span><span class="n">kill</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="n">container</span><span class="o">.</span><span class="n">remove</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">get_container</span><span class="p">(</span><span class="n">container_name</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="n">containers</span> <span class="o">=</span> <span class="n">docker_client</span><span class="o">.</span><span class="n">containers</span><span class="o">.</span><span class="n">list</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="n">container</span> <span class="ow">in</span> <span class="n">containers</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="n">container</span><span class="o">.</span><span class="n">name</span> <span class="o">==</span> <span class="n">container_name</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="k">return</span> <span class="n">container</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">start_container</span><span class="p">(</span><span class="n">service_name</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="n">docker_compose</span><span class="o">.</span><span class="n">start</span><span class="p">(</span><span class="n">service_name</span><span class="p">)</span>
</span></span></code></pre></div><p>Finally, onto our actual tests, in reality, these tests are very boring and not super useful but they should give you an
idea what you can do. Such as killing the database container and then check how your Python application responds. Then
you can start the database container and again check how your Python application responds.
You can read more about how you can test Python Flask applications with pytest
<a href="https://medium.com/@hmajid2301/testing-mocking-a-connexion-flask-application-with-pytest-bacfd07099eb">here</a>.</p>
<p>So what do our tests do? Well, the first one checks the number of containers running is equal to 2.
The next one kills <code>container1</code> and checks that only one container is running and it&rsquo;s not <code>container1</code>.
Our final test starts <code>container1</code> and checks that it is running and the number of containers running
is back to 2. After this final test has completed then the <code>setup</code> fixture will run its <code>docker_compose.shutdown()</code>
command.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">test_two_containers</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">    <span class="n">containers</span> <span class="o">=</span> <span class="n">docker_client</span><span class="o">.</span><span class="n">containers</span><span class="o">.</span><span class="n">list</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="k">assert</span> <span class="nb">len</span><span class="p">(</span><span class="n">containers</span><span class="p">)</span> <span class="o">==</span> <span class="mi">2</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">test_kill_container1</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">    <span class="n">kill_container</span><span class="p">(</span><span class="s2">&#34;container1&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">containers</span> <span class="o">=</span> <span class="n">docker_client</span><span class="o">.</span><span class="n">containers</span><span class="o">.</span><span class="n">list</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="n">container1</span> <span class="o">=</span> <span class="n">get_container</span><span class="p">(</span><span class="s2">&#34;container1&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">assert</span> <span class="nb">len</span><span class="p">(</span><span class="n">containers</span><span class="p">)</span> <span class="o">==</span> <span class="mi">1</span>
</span></span><span class="line"><span class="cl">    <span class="k">assert</span> <span class="ow">not</span> <span class="n">container1</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">test_start_container1</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">    <span class="n">start_container</span><span class="p">(</span><span class="s2">&#34;service&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">containers</span> <span class="o">=</span> <span class="n">docker_client</span><span class="o">.</span><span class="n">containers</span><span class="o">.</span><span class="n">list</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="n">container1</span> <span class="o">=</span> <span class="n">get_container</span><span class="p">(</span><span class="s2">&#34;container1&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">assert</span> <span class="nb">len</span><span class="p">(</span><span class="n">containers</span><span class="p">)</span> <span class="o">==</span> <span class="mi">2</span>
</span></span><span class="line"><span class="cl">    <span class="k">assert</span> <span class="n">container1</span>
</span></span></code></pre></div><p>That&rsquo;s it we&rsquo;ve managed to start/stop Docker containers from within Gitlab CI, using DinD. In a future article I will
explain how you could run your tests within a Docker container you&rsquo;ve started. Say you had three containers <code>nginx</code>, <code>flask</code>
and <code>postgres</code> and you wanted to run your tests within the <code>flask</code> container. But for now that&rsquo;s it, thanks for reading!</p>
<h2 id="appendix">Appendix</h2>
<ul>
<li><a href="https://gitlab.com/hmajid2301/blog/-/tree/main/content/posts/2020-06-22-how-to-use-gitlab-ci-pytest-and-docker-compose-together/source_code">Example source code</a></li>
</ul>
]]></content:encoded>
    </item>
    
    <item>
      <title>How to use DinD with Gitlab CI</title>
      <link>https://haseebmajid.dev/posts/2020-05-01-how-to-use-dind-with-gitlab-ci/</link>
      <pubDate>Fri, 01 May 2020 00:00:00 +0000</pubDate>
      <author>Me</author>
      <guid>https://haseebmajid.dev/posts/2020-05-01-how-to-use-dind-with-gitlab-ci/</guid>
      <description>&lt;p&gt;Like most developers, we want to be able to automate as many and as much of processes as possible. Pushing Docker
images to a registry is a task that can easily be automated. In this article, we will cover how you can use
Gitlab CI to build and publish your Docker images, to the Gitlab registry. However, you can also very easily
edit this to push your images to DockerHub as well.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>Like most developers, we want to be able to automate as many and as much of processes as possible. Pushing Docker
images to a registry is a task that can easily be automated. In this article, we will cover how you can use
Gitlab CI to build and publish your Docker images, to the Gitlab registry. However, you can also very easily
edit this to push your images to DockerHub as well.</p>
<p>A quick aside on terminology related to Docker:</p>
<ul>
<li>container: An instance of an image is called a container (<code>docker run</code>)</li>
<li>image: A set of immutable layers (<code>docker build</code>)</li>
<li>hub: The official registry where you can get more Docker images from (<code>docker pull</code>)</li>
</ul>
<h2 id="example">Example</h2>
<p>Here is an example <code>.gitlab-ci.yml</code> file which can be used to build and push your Docker images to the Gitlab registry.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">variables</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">DOCKER_DRIVER</span><span class="p">:</span><span class="w"> </span><span class="l">overlay2</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">services</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">docker:dind</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">stages</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">publish</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">publish-docker</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">stage</span><span class="p">:</span><span class="w"> </span><span class="l">publish</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">docker</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">script</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">export VERSION_TAG=v1.2.3</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">docker login ${CI_REGISTRY} -u gitlab-ci-token -p ${CI_BUILD_TOKEN}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">docker build -t ${CI_REGISTRY_IMAGE}:latest -t ${CI_REGISTRY_IMAGE}:${VERSION_TAG}  .</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">docker push ${CI_REGISTRY_IMAGE}:latest</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">docker push ${CI_REGISTRY_IMAGE}:${VERSION_TAG}</span><span class="w">
</span></span></span></code></pre></div><h2 id="explained">Explained</h2>
<p>The code above may be a bit confusing, it might be a lot to take in. So now we will break it down line by line.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">variables</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">DOCKER_DRIVER</span><span class="p">:</span><span class="w"> </span><span class="l">overlay2</span><span class="w">
</span></span></span></code></pre></div><p>In our first couple of lines, we define some variables which will be used by all our jobs (the variables are global).
We define a variable <code>DOCKER_DRIVER: overlay2</code>, this helps speed our Docker containers a bit because by default it
uses <code>vfs</code> which is slower
<a href="https://docs.gitlab.com/ce/ci/docker/using_docker_build.html#using-the-overlayfs-driver">learn more here</a>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">random-job</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">stage</span><span class="p">:</span><span class="w"> </span><span class="l">publish</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">variables</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">DOCKER_DRIVER</span><span class="p">:</span><span class="w"> </span><span class="l">overlay2</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">script</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">echo &#34;HELLO&#34;</span><span class="w">
</span></span></span></code></pre></div><blockquote>
<p>Note we could just as easily define <code>variables</code> just within our job as well like you see in the example above.</p>
</blockquote>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">services</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">docker:dind</span><span class="w">
</span></span></span></code></pre></div><p>The next couple of lines define a service. A service is a Docker image which links during our job(s). Again in this
example, it is defined globally and will link to all of our jobs. We could very easily define it within our job just
like in the <code>variables</code> example. The <a href="https://github.com/docker-library/docker/blob/157869f94ea90e2acb4d0f77045d99079ead821c/18.02/dind/dockerd-entrypoint.sh"><code>docker:dind</code></a>
image automatically using its <code>entrypoint</code> starts a docker daemon. We need to use this daemon to build/push our
Docker images within CI.</p>
<p>The <code>docker:dind</code> (dind = Docker in Docker) image is almost identical to the <code>docker</code> image. The difference being the dind image
starts a Docker daemon. In this example, the job will use the <code>docker</code> image as the client and connect to the daemon
running in this container.</p>
<p>We could also just use the <code>dind</code> image in our job and simply start <code>dockerd</code> (&amp; = in the background) in the first line.
The <code>dockerd</code> command starts the Docker daemon as a client, so we can then communicate with the other Docker daemon.
It would achieve the same outcome. I think the service approach is a bit cleaner but as already stated either approach
would work.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">publish-docker</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">stage</span><span class="p">:</span><span class="w"> </span><span class="l">publish</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">docker:dind</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">script</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">dockerd &amp;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="l">...</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">docker push ${CI_REGISTRY_IMAGE}:${VERSION_TAG}</span><span class="w">
</span></span></span></code></pre></div><blockquote>
<p>Info: One common use case of Gitlab CI services is to spin up databases like MySQL. We can then connect to it within our job, run our tests. It can simplify our jobs by quite a bit.</p>
</blockquote>
<blockquote>
<p>Note: There are several other ways we could also build/push our images. This is the <a href="https://gitlab.com/gitlab-examples/docker/blob/master/.gitlab-ci.yml">recommended approach</a>.</p>
</blockquote>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">stages</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">publish</span><span class="w">
</span></span></span></code></pre></div><p>Next, we define our stages and give them names. Each job must have a valid stage attached to it. Stages are used to
determine when a job will be run in our CI pipeline. If two jobs have the same stage, then they will run in parallel.
The stages defined earlier will run first so order does matter. However in this example, we only have one stage and
one job so this isn&rsquo;t super important, more just something to keep in mind.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">publish-docker</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">stage</span><span class="p">:</span><span class="w"> </span><span class="l">publish</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="l">...</span><span class="w">
</span></span></span></code></pre></div><p>Now we define our job, where <code>publish-docker</code> is the name of our job on <code>Gitlab CI</code> pipeline. We then define
what <code>stage</code> the job should run in, in this case, this job will run during the <code>publish</code> stage.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">publish-docker</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="l">...</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">docker</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="l">...</span><span class="w">
</span></span></span></code></pre></div><p>Then we define what Docker image to use in this job. In this job, we will use the <code>docker</code> image. This
image has all the commands we need to <code>build</code> and <code>push</code> our Docker images. It will act as the client making
requests to the <code>dind</code> daemon.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">script</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">export VERSION_TAG=v1.2.3</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">docker login ${CI_REGISTRY} -u gitlab-ci-token -p ${CI_BUILD_TOKEN}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">docker build -t ${CI_REGISTRY_IMAGE}:latest -t ${CI_REGISTRY_IMAGE}:${VERSION_TAG}  .</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">docker push ${CI_REGISTRY_IMAGE}:latest</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">docker push ${CI_REGISTRY_IMAGE}:${VERSION_TAG}</span><span class="w">
</span></span></span></code></pre></div><p>Finally, we get to the real meat and potatoes of the CI file. The bit of code that builds and pushes are Docker
images to the registry:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl">- <span class="l">export VERSION_TAG=v1.2.3</span><span class="w">
</span></span></span></code></pre></div><p>It is often a good idea to tag our images, in this case, I&rsquo;m using a release name. You could get this from say your
<code>setup.py</code> or <code>package.json</code> file as well. In my Python projects I usually use this command
<code>export VERSION_TAG=$(cat setup.py | grep version | head -1 | awk -F= '{ print $2 }' | sed 's/[&quot;,]//g' | tr -d &quot;'&quot;)</code>,
to parse my <code>setup.py</code> for the version number. But this can be whatever you want it to be. Here we have just kept it
static to make things simpler but in reality, you&rsquo;ll probably want to retrieve it programmatically (the version number).</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl">- <span class="l">docker login ${CI_REGISTRY} -u gitlab-ci-token -p ${CI_BUILD_TOKEN}</span><span class="w">
</span></span></span></code></pre></div><p>Then we log in to our Gitlab registry, the environment variables <code>$CI_REGISTRY</code> and <code>CI_BUILD_TOKEN</code> are predefined
Gitlab variables that are injected into our environment. You can read more about them
<a href="https://docs.gitlab.com/ee/ci/variables/predefined_variables.html">here</a>. Since we are pushing to our Gitlab registry
we can just use the credentials defined within environment i.e. <code>username=gitlab-ci-token</code> and password a throwaway
token.</p>
<blockquote>
<p>Note: You can only do this on protected branches/tags.</p>
</blockquote>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl">- <span class="l">docker build -t ${CI_REGISTRY_IMAGE}:latest -t ${CI_REGISTRY_IMAGE}:${VERSION_TAG}  .</span><span class="w">
</span></span></span><span class="line"><span class="cl">- <span class="l">docker push ${CI_REGISTRY_IMAGE}:latest</span><span class="w">
</span></span></span><span class="line"><span class="cl">- <span class="l">docker push ${CI_REGISTRY_IMAGE}:${VERSION_TAG}</span><span class="w">
</span></span></span></code></pre></div><p>Finally, we run our normal commands to build and push our images. The place where you can find your images will depend
on the project name and your username but it should follow this format</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">registry.gitlab.com/&lt;username&gt;/&lt;project_name&gt;/&lt;tag&gt;
</span></span></code></pre></div><h3 id="optional-push-to-dockerhub">(Optional) Push to DockerHub</h3>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl">- <span class="l">docker login -u hmajid2301 -p ${DOCKER_PASSWORD}</span><span class="w">
</span></span></span><span class="line"><span class="cl">- <span class="l">export IMAGE_NAME=&#34;hmajid2301/example_project&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl">- <span class="l">docker build -t ${IMAGE_NAME}:latest -t ${IMAGE_NAME}:${VERSION_TAG}  .</span><span class="w">
</span></span></span><span class="line"><span class="cl">- <span class="l">docker push ${IMAGE_NAME}:latest</span><span class="w">
</span></span></span><span class="line"><span class="cl">- <span class="l">docker push ${IMAGE_NAME}:${VERSION_TAG}</span><span class="w">
</span></span></span></code></pre></div><p>We can also push our images to DockerHub, with the code shown above. We need to first login to DockerHub. Then change
the name of our image <code>&lt;username&gt;/&lt;project_name&gt;</code>.</p>
<h2 id="appendix">Appendix</h2>
<ul>
<li>A good <a href="https://stackoverflow.com/questions/47280922/role-of-docker-in-docker-dind-service-in-gitlab-ci">Stackoverflow Post</a></li>
<li><a href="https://docs.gitlab.com/ee/ci/docker/using_docker_build.html">Gitlab CI Docs</a></li>
<li><a href="https://gitlab.com/gitlab-examples/docker/blob/master/.gitlab-ci.yml">Gitlab Example</a></li>
</ul>
]]></content:encoded>
    </item>
    
    <item>
      <title>Building A Simple Flask App with SQLalchemy and Docker</title>
      <link>https://haseebmajid.dev/posts/2018-11-24-building-a-simple-flask-app-with-sqlalchemy-and-docker/</link>
      <pubDate>Sat, 24 Nov 2018 00:00:00 +0000</pubDate>
      <author>Me</author>
      <guid>https://haseebmajid.dev/posts/2018-11-24-building-a-simple-flask-app-with-sqlalchemy-and-docker/</guid>
      <description>&lt;p&gt;SQLAlchemy is an object-relational mapper (ORM), it allow us to interact with a database using Python functions and objects. For example, if we have a table called
&lt;code&gt;Cats&lt;/code&gt; we could retrieve every row with a command like &lt;code&gt;Cats.query.all()&lt;/code&gt;. The main advantage of this is that it allows us to abstract away the SQL.&lt;/p&gt;
&lt;p&gt;Docker &amp;#x1f433; allows us to quickly bring up a database within a Docker container, this means we don&amp;rsquo;t have to set up and configure a database on our local machine. We can simply kill the Docker container when we are done with the database. In this article, I will show you how you can create a very simple RESTful API using Flask and SQLAlchemy, which will connect to a database running in a Docker container.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>SQLAlchemy is an object-relational mapper (ORM), it allow us to interact with a database using Python functions and objects. For example, if we have a table called
<code>Cats</code> we could retrieve every row with a command like <code>Cats.query.all()</code>. The main advantage of this is that it allows us to abstract away the SQL.</p>
<p>Docker &#x1f433; allows us to quickly bring up a database within a Docker container, this means we don&rsquo;t have to set up and configure a database on our local machine. We can simply kill the Docker container when we are done with the database. In this article, I will show you how you can create a very simple RESTful API using Flask and SQLAlchemy, which will connect to a database running in a Docker container.</p>
<p><strong>NOTE:</strong> Flask server will be running locally, not in a Docker container.</p>
<p>In this example, I will be using Postgres but it should be easy enough to use any other relational database, such as MySQL. I will also be using <code>flask-sqlalchemy</code>, which is a wrapper around <code>SQLAlchemy</code>, it simplifies our code and means we can use less boilerplate code.</p>
<h2 id="prerequisites">Prerequisites</h2>
<ul>
<li><a href="https://docs.docker.com/install/">Install Docker</a></li>
<li>(optional) <a href="https://docs.docker.com/compose/install/">Install docker-compose</a></li>
<li>Install Python3.6</li>
<li>Install the following dependencies, using <code>pip install -r requirements.txt</code> (or pip3 instead of pip)</li>
</ul>
<p>Where requirements.txt is:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">flask==1.0.2
</span></span><span class="line"><span class="cl">flask-sqlalchemy==2.3.0
</span></span><span class="line"><span class="cl">psycopg2==2.7.6.1
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">flask</span> <span class="kn">import</span> <span class="n">Flask</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">.models</span> <span class="kn">import</span> <span class="n">db</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">.</span> <span class="kn">import</span> <span class="n">config</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">create_app</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">    <span class="n">flask_app</span> <span class="o">=</span> <span class="n">Flask</span><span class="p">(</span><span class="vm">__name__</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">flask_app</span><span class="o">.</span><span class="n">config</span><span class="p">[</span><span class="s1">&#39;SQLALCHEMY_DATABASE_URI&#39;</span><span class="p">]</span> <span class="o">=</span> <span class="n">config</span><span class="o">.</span><span class="n">DATABASE_CONNECTION_URI</span>
</span></span><span class="line"><span class="cl">    <span class="n">flask_app</span><span class="o">.</span><span class="n">config</span><span class="p">[</span><span class="s1">&#39;SQLALCHEMY_TRACK_MODIFICATIONS&#39;</span><span class="p">]</span> <span class="o">=</span> <span class="kc">False</span>
</span></span><span class="line"><span class="cl">    <span class="n">flask_app</span><span class="o">.</span><span class="n">app_context</span><span class="p">()</span><span class="o">.</span><span class="n">push</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="n">db</span><span class="o">.</span><span class="n">init_app</span><span class="p">(</span><span class="n">flask_app</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">db</span><span class="o">.</span><span class="n">create_all</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">flask_app</span>
</span></span></code></pre></div><p>The init file has one function <code>create_app()</code>, which funnily enough creates our Flask app with this line <code>Flask(__name__)</code>. It then assigns a URI, from the <code>config.py</code> file, to the Flask app&rsquo;s configuration. This URI is used to connect to the Postgres database.</p>
<p>One important thing about this function is that we have to use Flask contexts. Since Flask can have multiple apps we have to specify which app we are using with SQLAlchemy, hence we push the context with our newly created app. Else we would see the following error, <a href="http://flask-sqlalchemy.pocoo.org/2.3/contexts/">more information here</a>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">No application found. Either work inside a view function or push an application context.
</span></span></code></pre></div><p>After pushing our context, we link our <code>db</code> to the Flask app with the following line <code>db.init_app(flask_app)</code>. We then create all of our tables (in the database) if they don&rsquo;t already exist, using <code>db.create_all()</code>. The tables are created using the classes defined in <code>models.py</code>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">os</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">user</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">environ</span><span class="p">[</span><span class="s1">&#39;POSTGRES_USER&#39;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="n">password</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">environ</span><span class="p">[</span><span class="s1">&#39;POSTGRES_PASSWORD&#39;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="n">host</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">environ</span><span class="p">[</span><span class="s1">&#39;POSTGRES_HOST&#39;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="n">database</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">environ</span><span class="p">[</span><span class="s1">&#39;POSTGRES_DB&#39;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="n">port</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">environ</span><span class="p">[</span><span class="s1">&#39;POSTGRES_PORT&#39;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">DATABASE_CONNECTION_URI</span> <span class="o">=</span> <span class="sa">f</span><span class="s1">&#39;postgresql+psycopg2://</span><span class="si">{</span><span class="n">user</span><span class="si">}</span><span class="s1">:</span><span class="si">{</span><span class="n">password</span><span class="si">}</span><span class="s1">@</span><span class="si">{</span><span class="n">host</span><span class="si">}</span><span class="s1">:</span><span class="si">{</span><span class="n">port</span><span class="si">}</span><span class="s1">/</span><span class="si">{</span><span class="n">database</span><span class="si">}</span><span class="s1">&#39;</span>
</span></span></code></pre></div><p>This module&rsquo;s only job at the moment is to generate this URI, but could easily be extended to add extra configuration variables if required.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">DATABASE_CONNECTION_URI</span> <span class="o">=</span> <span class="sa">f</span><span class="s1">&#39;postgresql+psycopg2://</span><span class="si">{</span><span class="n">user</span><span class="si">}</span><span class="s1">:</span><span class="si">{</span><span class="n">password</span><span class="si">}</span><span class="s1">@</span><span class="si">{</span><span class="n">host</span><span class="si">}</span><span class="s1">:</span><span class="si">{</span><span class="n">port</span><span class="si">}</span><span class="s1">/</span><span class="si">{</span><span class="n">database</span><span class="si">}</span><span class="s1">&#39;</span>
</span></span></code></pre></div><p><strong>NOTE:</strong> F-strings used for formatting strings (as shown above) can only be used with Python3.6.</p>
<p>These are examples of the variables that need to get passed as environment variables to the Flask app.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">POSTGRES_USER=test
</span></span><span class="line"><span class="cl">POSTGRES_PASSWORD=password
</span></span><span class="line"><span class="cl">POSTGRES_HOST=localhost
</span></span><span class="line"><span class="cl">POSTGRES_PORT=5432
</span></span><span class="line"><span class="cl">POSTGRES_DB=example
</span></span></code></pre></div><p><strong>NOTE:</strong> If you&rsquo;re running the Flask app in a Docker container you will need to change the variable <code>POSTGRES_HOST=postgres</code>, (from localhost)
where <code>postgres</code> is the Docker container name we are connecting to.</p>
<p><strong>WARNING:</strong> Make sure these are the same values passed to the Flask app and the Postgres database.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">flask_sqlalchemy</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">db</span> <span class="o">=</span> <span class="n">flask_sqlalchemy</span><span class="o">.</span><span class="n">SQLAlchemy</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">Cats</span><span class="p">(</span><span class="n">db</span><span class="o">.</span><span class="n">Model</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="n">__tablename__</span> <span class="o">=</span> <span class="s1">&#39;cats&#39;</span>
</span></span><span class="line"><span class="cl">    <span class="nb">id</span> <span class="o">=</span> <span class="n">db</span><span class="o">.</span><span class="n">Column</span><span class="p">(</span><span class="n">db</span><span class="o">.</span><span class="n">Integer</span><span class="p">,</span> <span class="n">primary_key</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">name</span> <span class="o">=</span> <span class="n">db</span><span class="o">.</span><span class="n">Column</span><span class="p">(</span><span class="n">db</span><span class="o">.</span><span class="n">String</span><span class="p">(</span><span class="mi">100</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">    <span class="n">price</span> <span class="o">=</span> <span class="n">db</span><span class="o">.</span><span class="n">Column</span><span class="p">(</span><span class="n">db</span><span class="o">.</span><span class="n">Integer</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">breed</span> <span class="o">=</span> <span class="n">db</span><span class="o">.</span><span class="n">Column</span><span class="p">(</span><span class="n">db</span><span class="o">.</span><span class="n">String</span><span class="p">(</span><span class="mi">100</span><span class="p">))</span>
</span></span></code></pre></div><p>This module defines our classes which then become tables within our database. For example, the class <code>Cats</code> (cats) is the table name and each attribute becomes a column in that table. So the <code>cats</code> table with have four columns id, name, price and breed.</p>
<p>The <code>db</code> variable is imported from here by the <code>__init__.py</code> file, that&rsquo;s how the <code>db.create_all()</code> function knows which classes/tables to create in the database.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">json</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">flask</span> <span class="kn">import</span> <span class="n">request</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">.</span> <span class="kn">import</span> <span class="n">create_app</span><span class="p">,</span> <span class="n">database</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">.models</span> <span class="kn">import</span> <span class="n">Cats</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">app</span> <span class="o">=</span> <span class="n">create_app</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nd">@app.route</span><span class="p">(</span><span class="s1">&#39;/&#39;</span><span class="p">,</span> <span class="n">methods</span><span class="o">=</span><span class="p">[</span><span class="s1">&#39;GET&#39;</span><span class="p">])</span>
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">fetch</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">    <span class="n">cats</span> <span class="o">=</span> <span class="n">database</span><span class="o">.</span><span class="n">get_all</span><span class="p">(</span><span class="n">Cats</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">all_cats</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="n">cat</span> <span class="ow">in</span> <span class="n">cats</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">new_cat</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;id&#34;</span><span class="p">:</span> <span class="n">cat</span><span class="o">.</span><span class="n">id</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;name&#34;</span><span class="p">:</span> <span class="n">cat</span><span class="o">.</span><span class="n">name</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;price&#34;</span><span class="p">:</span> <span class="n">cat</span><span class="o">.</span><span class="n">price</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="s2">&#34;breed&#34;</span><span class="p">:</span> <span class="n">cat</span><span class="o">.</span><span class="n">breed</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="n">all_cats</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">new_cat</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">json</span><span class="o">.</span><span class="n">dumps</span><span class="p">(</span><span class="n">all_cats</span><span class="p">),</span> <span class="mi">200</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nd">@app.route</span><span class="p">(</span><span class="s1">&#39;/add&#39;</span><span class="p">,</span> <span class="n">methods</span><span class="o">=</span><span class="p">[</span><span class="s1">&#39;POST&#39;</span><span class="p">])</span>
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">add</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">    <span class="n">data</span> <span class="o">=</span> <span class="n">request</span><span class="o">.</span><span class="n">get_json</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="n">name</span> <span class="o">=</span> <span class="n">data</span><span class="p">[</span><span class="s1">&#39;name&#39;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">    <span class="n">price</span> <span class="o">=</span> <span class="n">data</span><span class="p">[</span><span class="s1">&#39;price&#39;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">    <span class="n">breed</span> <span class="o">=</span> <span class="n">data</span><span class="p">[</span><span class="s1">&#39;breed&#39;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">database</span><span class="o">.</span><span class="n">add_instance</span><span class="p">(</span><span class="n">Cats</span><span class="p">,</span> <span class="n">name</span><span class="o">=</span><span class="n">name</span><span class="p">,</span> <span class="n">price</span><span class="o">=</span><span class="n">price</span><span class="p">,</span> <span class="n">breed</span><span class="o">=</span><span class="n">breed</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">json</span><span class="o">.</span><span class="n">dumps</span><span class="p">(</span><span class="s2">&#34;Added&#34;</span><span class="p">),</span> <span class="mi">200</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nd">@app.route</span><span class="p">(</span><span class="s1">&#39;/remove/&lt;cat_id&gt;&#39;</span><span class="p">,</span> <span class="n">methods</span><span class="o">=</span><span class="p">[</span><span class="s1">&#39;DELETE&#39;</span><span class="p">])</span>
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">remove</span><span class="p">(</span><span class="n">cat_id</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="n">database</span><span class="o">.</span><span class="n">delete_instance</span><span class="p">(</span><span class="n">Cats</span><span class="p">,</span> <span class="nb">id</span><span class="o">=</span><span class="n">cat_id</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">json</span><span class="o">.</span><span class="n">dumps</span><span class="p">(</span><span class="s2">&#34;Deleted&#34;</span><span class="p">),</span> <span class="mi">200</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nd">@app.route</span><span class="p">(</span><span class="s1">&#39;/edit/&lt;cat_id&gt;&#39;</span><span class="p">,</span> <span class="n">methods</span><span class="o">=</span><span class="p">[</span><span class="s1">&#39;PATCH&#39;</span><span class="p">])</span>
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">edit</span><span class="p">(</span><span class="n">cat_id</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="n">data</span> <span class="o">=</span> <span class="n">request</span><span class="o">.</span><span class="n">get_json</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="n">new_price</span> <span class="o">=</span> <span class="n">data</span><span class="p">[</span><span class="s1">&#39;price&#39;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">    <span class="n">database</span><span class="o">.</span><span class="n">edit_instance</span><span class="p">(</span><span class="n">Cats</span><span class="p">,</span> <span class="nb">id</span><span class="o">=</span><span class="n">cat_id</span><span class="p">,</span> <span class="n">price</span><span class="o">=</span><span class="n">new_price</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">json</span><span class="o">.</span><span class="n">dumps</span><span class="p">(</span><span class="s2">&#34;Edited&#34;</span><span class="p">),</span> <span class="mi">200</span>
</span></span></code></pre></div><p>This is a simple Flask file, which creates our app by calling <code>create_app()</code> function from <code>__init__.py</code> module.
Then it defines four functions for our four routes for the &ldquo;RESTful&rdquo; API:</p>
<ul>
<li>GET: Get information about all the cats</li>
<li>POST: Add a new cat</li>
<li>DELETE: Remove a cat</li>
<li>PATCH: Edit a cat&rsquo;s price</li>
</ul>
<h2 id="databasepy">database.py</h2>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">.models</span> <span class="kn">import</span> <span class="n">db</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">get_all</span><span class="p">(</span><span class="n">model</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="n">data</span> <span class="o">=</span> <span class="n">model</span><span class="o">.</span><span class="n">query</span><span class="o">.</span><span class="n">all</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">data</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">add_instance</span><span class="p">(</span><span class="n">model</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="n">instance</span> <span class="o">=</span> <span class="n">model</span><span class="p">(</span><span class="o">**</span><span class="n">kwargs</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">db</span><span class="o">.</span><span class="n">session</span><span class="o">.</span><span class="n">add</span><span class="p">(</span><span class="n">instance</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">commit_changes</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">delete_instance</span><span class="p">(</span><span class="n">model</span><span class="p">,</span> <span class="nb">id</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="n">model</span><span class="o">.</span><span class="n">query</span><span class="o">.</span><span class="n">filter_by</span><span class="p">(</span><span class="nb">id</span><span class="o">=</span><span class="nb">id</span><span class="p">)</span><span class="o">.</span><span class="n">delete</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="n">commit_changes</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">edit_instance</span><span class="p">(</span><span class="n">model</span><span class="p">,</span> <span class="nb">id</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="n">instance</span> <span class="o">=</span> <span class="n">model</span><span class="o">.</span><span class="n">query</span><span class="o">.</span><span class="n">filter_by</span><span class="p">(</span><span class="nb">id</span><span class="o">=</span><span class="nb">id</span><span class="p">)</span><span class="o">.</span><span class="n">all</span><span class="p">()[</span><span class="mi">0</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="n">attr</span><span class="p">,</span> <span class="n">new_value</span> <span class="ow">in</span> <span class="n">kwargs</span><span class="o">.</span><span class="n">items</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">        <span class="nb">setattr</span><span class="p">(</span><span class="n">instance</span><span class="p">,</span> <span class="n">attr</span><span class="p">,</span> <span class="n">new_value</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">commit_changes</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">commit_changes</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">    <span class="n">db</span><span class="o">.</span><span class="n">session</span><span class="o">.</span><span class="n">commit</span><span class="p">()</span>
</span></span></code></pre></div><p>This module is created so we can abstract away how we interact with the database. We simply use
the functions in this module to interact with the database. This means it&rsquo;s easier to change the
library we use to interact with the database. It also means that if for some reason we need to change how we interact with the database. We only have to change it in a single module (this one).</p>
<p>The <code>app.py</code> module calls functions in this file to interact with database.</p>
<ul>
<li>GET - <code>get_all()</code></li>
<li>POST - <code>add_instance()</code></li>
<li>DELETE - <code>delete_instance()</code></li>
<li>PUT - <code>edit_instance()</code></li>
</ul>
<p>Some functions use this special keyword called <code>**kwargs</code>, kwargs (keyword arguments) could be called anything but it&rsquo;s best practice to call it kwargs. This allows
the caller of the function to pass in an arbitrary number of keyword arguments.</p>
<p>Let&rsquo;s take a look at the <code>add_instance()</code> function as an example. The function is called in <code>app.py</code> like so <code>database.add_instance(Cats, name=name, price=price, breed=breed)</code> the <code>model=Cats</code> and <code>kwargs</code> is the rest of the arguments which are passed onto the cats model so we can add our cat object to the database.</p>
<p><strong>NOTE:</strong> The <code>kwargs</code> just stores the arguments as a dictionary, the <code>**</code> operator unpacks our dictionary
and passes them as keyword arguments.</p>
<h2 id="docker-compose">Docker Compose</h2>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">version</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;3.5&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">services</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">database</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">container_name</span><span class="p">:</span><span class="w"> </span><span class="l">postgres</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">postgres:latest</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">env_file</span><span class="p">:</span><span class="w"> </span><span class="l">database.conf</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">ports</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="m">5432</span><span class="p">:</span><span class="m">5432</span><span class="w">  
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">volumes</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">db_volume:/var/lib/postgresql</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">volumes</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">db_volume</span><span class="p">:</span><span class="w">
</span></span></span></code></pre></div><p>For development, I like to use docker-compose. In docker compose we can specify Docker containers using YAML. It can help to simplify the commands we need to type when trying to build/run multiple Docker containers. In this example, we only define a single Docker container.</p>
<p>Taking a look at the file:</p>
<p>First, we define our version number <code>version: '3.5'</code>, it is recommended by Docker that you use at least version 3. You can find
<a href="https://docs.docker.com/compose/compose-file/compose-versioning/">more information here</a>.</p>
<p>Then we give our a service name, in this case, <code>database</code>. I like to name my services with what they are used for generic names such as <code>web server</code>, <code>database</code> or <code>message broker</code>. This means I can change the underlying technology without changing the service name.</p>
<p>After this we name our container <code>postgres</code>, this is the name of the Docker container.
It can be used to interact with the container (to kill it or exec onto it) without using an ID.</p>
<p>We use the official Postgres image on <a href="https://hub.docker.com/_/postgres/">Docker Hub</a>, we pull the image that is tagged with <code>latest</code>.</p>
<p>This image requires us to use some variables to set it up such as username, password and database. We pass these in the form of a file to make things a bit simpler (the same `database.conf as defined above).</p>
<p>We then map the host port 5432 to the guest Docker container port 5432, this is the port that Postgres listens on. You could change the host port if you wanted to something else like <code>9000</code> say, this means all traffic on the host on port 9000 will be sent to the Postgres container on port 5432. We would also need to update the environment variable the Flask app is using.</p>
<p>Finally, we mount a volume so that our data is persistent, without this when the database Docker container is killed you would lose all of your data. By mounting <code>db_volume</code> even when you kill the container, like when you want to update the Postgres image, your data will persist.</p>
<h2 id="running-our-application">Running our application</h2>
<p>To build and run our Docker container with docker-compose:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">docker-compose up --build -d
</span></span></code></pre></div><p>The equivalent commands using just normal Docker would be</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">docker volume create --name db_volume
</span></span><span class="line"><span class="cl">docker run -d --name postgres -p 5432:5432 <span class="se">\
</span></span></span><span class="line"><span class="cl">           --env-file docker/database.conf <span class="se">\
</span></span></span><span class="line"><span class="cl">           -v db_volume:/var/lib/postgresql postgres:latest
</span></span></code></pre></div><p>To start our Flask app</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">docker-compose up --build
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># In a new terminal</span>
</span></span><span class="line"><span class="cl">virtualenv .venv
</span></span><span class="line"><span class="cl"><span class="nb">source</span> .venv/bin/activate
</span></span><span class="line"><span class="cl">pip install -r requirements.txt
</span></span><span class="line"><span class="cl"><span class="c1"># To load env variables</span>
</span></span><span class="line"><span class="cl"><span class="nb">export</span> <span class="k">$(</span>xargs &lt; database.conf<span class="k">)</span>
</span></span><span class="line"><span class="cl"><span class="nb">export</span> <span class="nv">FLASK_APP</span><span class="o">=</span>src/example/app.py
</span></span><span class="line"><span class="cl">flask run
</span></span><span class="line"><span class="cl"><span class="c1"># Running on http://127.0.0.1:5000</span>
</span></span></code></pre></div><p>You can send HTTP requests to your Flask server on <code>127.0.0.1:5000</code>, you can either use a REST client like Postman or Insomnia. You can also use cURL on the cli.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">curl -XPOST -H <span class="s2">&#34;Content-type: application/json&#34;</span> -d <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="s1">&#39;{&#34;name&#34;: &#34;catty mcCatFace&#34;, &#34;price&#34;: 5000, &#34;breed&#34;: &#34;bengal&#34;}&#39;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="s1">&#39;127.0.0.1:5000/add&#39;</span>
</span></span></code></pre></div><h2 id="appendix">Appendix</h2>
<ul>
<li><a href="https://gitlab.com/hmajid2301/blog/-/tree/main/content/posts/2018-11-24-building-a-simple-flask-app-with-sqlalchemy-and-docker/source_code">Example source code</a></li>
<li><a href="https://www.getpostman.com/">Postman</a></li>
<li><a href="https://insomnia.rest/">Insomnia</a></li>
</ul>
]]></content:encoded>
    </item>
    
    <item>
      <title>Using Multiple Docker Containers to Setup Nginx, Flask and Postgres</title>
      <link>https://haseebmajid.dev/posts/2018-11-19-using-multiple-docker-containers-to-setup-nginx-flask-and-postgres/</link>
      <pubDate>Mon, 19 Nov 2018 00:00:00 +0000</pubDate>
      <author>Me</author>
      <guid>https://haseebmajid.dev/posts/2018-11-19-using-multiple-docker-containers-to-setup-nginx-flask-and-postgres/</guid>
      <description>&lt;h2 id=&#34;terminology&#34;&gt;Terminology&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Docker Image: Is a file used to execute code in a Docker container, built from a set of instructions.&lt;/li&gt;
&lt;li&gt;Docker Container: Is a Docker image that is being executed or run.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Docker &amp;#x1f433; is a relatively new and exciting technology, Docker is a containerisation tool. The main benefits of using Docker is that you can
use the same environment for development, testing and production. Since Docker environments are consistent this means if the application works
in the testing environment it will also work in production.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<h2 id="terminology">Terminology</h2>
<ul>
<li>Docker Image: Is a file used to execute code in a Docker container, built from a set of instructions.</li>
<li>Docker Container: Is a Docker image that is being executed or run.</li>
</ul>
<p>Docker &#x1f433; is a relatively new and exciting technology, Docker is a containerisation tool. The main benefits of using Docker is that you can
use the same environment for development, testing and production. Since Docker environments are consistent this means if the application works
in the testing environment it will also work in production.</p>
<p>Another big advantage of Docker is that I don&rsquo;t need to download all the dependencies on my own machine directly. To build an entire application and run it all I need is a Dockerfile so if I&rsquo;m the go a lot and using lots of different machines, I can easily set up my own development environment simply by using Docker. All Docker does it execute a set of instructions so since the instructions are the same, the environment Docker will create (Docker container) will also be the same.</p>
<p>In this tutorial, I will show you how to set up a Python &#x1f40d; application using multiple Docker containers. In theory, you could have one big Docker container
that has Nginx, Flask and Postgres but I prefer to split the application up. For example into its core components, web server (Nginx), application (Flask) and
database (Postgres). The main advantage of this is that it makes it easier to replace components of the application and also makes it easier to detect errors
as you can see which container is cauing the error.</p>
<p><strong>Note</strong>: Everything in this tutorial has been tested on Ubuntu Linux.</p>
<h2 id="prerequisites">Prerequisites</h2>
<ul>
<li><a href="https://docs.docker.com/install/">Install Docker</a></li>
<li>(optional) <a href="https://docs.docker.com/compose/install/">Install docker-compose</a></li>
</ul>
<h2 id="nginx">Nginx</h2>
<p>The first Docker container called Nginx will be the main gateway into our application it will be used as a proxy server. It will receive HTTP requests and forward them onto our Python application.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dockerfile" data-lang="dockerfile"><span class="line"><span class="cl"><span class="k">FROM</span><span class="w"> </span><span class="s">nginx:latest</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="k">RUN</span> rm /etc/nginx/conf.d/default.conf<span class="err">
</span></span></span><span class="line"><span class="cl"><span class="k">COPY</span> docker/nginx/example.conf /etc/nginx/conf.d/<span class="err">
</span></span></span></code></pre></div><p>This is a very simple dockerfile that takes uses the latest Nginx docker image. It then removes the default configuration and adds our configuration.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">server {
</span></span><span class="line"><span class="cl">  listen 80;
</span></span><span class="line"><span class="cl">  server_name _;
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  location / {
</span></span><span class="line"><span class="cl">    try_files $uri @app;
</span></span><span class="line"><span class="cl">  }
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  location @app {
</span></span><span class="line"><span class="cl">    include /etc/nginx/uwsgi_params;
</span></span><span class="line"><span class="cl">    uwsgi_pass flask:8080;
</span></span><span class="line"><span class="cl">  }
</span></span><span class="line"><span class="cl">}
</span></span></code></pre></div><p>This is a simple Nginx configuration file which listens for traffic on port 80 (HTTP). It then passes on the data to uWSGI (hence <code>location /</code>). We then pass the HTTP request to another Docker container called <code>flask</code> on port 8080. This configuration cannot be used for https. <strong>Warning</strong> Only use https to send secure data. The reason we give it the container name <code>flask</code> rather than <code>localhost</code> is because this is how Docker networking works by default (bridge networking) to allow container to container communication. We do a something thing to allow when connecting Flask container to the Postgres container.</p>
<h2 id="flask">Flask</h2>
<blockquote>
<p>NOTE: Link to the <a href="https://gitlab.com/hmajid2301/articles/-/tree/master/7.%20Multi%20Docker%20Container%20with%20Nginx%2C%20Flask%20and%C2%A0MySQL/source_code">Python app source code</a> in <code>source_code/src/example/</code> this is the code that is turned into the <code>tar</code> in the <code>dist</code> folder.</p>
</blockquote>
<p>The second Docker container will contain our Python application running on a uWSGI server. The uWSGI server is a web application server based on the WSGI specification will allow Python to communicate with web servers. In this case, it essentially acts as middleware between Nginx and Flask translating requests between them. So essentially uWSGI receives an HTTP request from Nginx and translates into something Flask can understand. This container stores all the core Python code required for this simple API.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dockerfile" data-lang="dockerfile"><span class="line"><span class="cl"><span class="c"># Base Image</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="k">FROM</span><span class="w"> </span><span class="s">python:3.6-alpine</span><span class="w"> </span><span class="k">as</span><span class="w"> </span><span class="s">BASE</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="k">RUN</span> apk add --no-cache linux-headers g++ postgresql-dev gcc build-base linux-headers ca-certificates python3-dev libffi-dev libressl-dev libxslt-dev<span class="err">
</span></span></span><span class="line"><span class="cl"><span class="k">RUN</span> pip wheel --wheel-dir<span class="o">=</span>/root/wheels psycopg2<span class="err">
</span></span></span><span class="line"><span class="cl"><span class="k">RUN</span> pip wheel --wheel-dir<span class="o">=</span>/root/wheels cryptography<span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="c"># Actual Image</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="k">FROM</span><span class="w"> </span><span class="s">python:3.6-alpine</span><span class="w"> </span><span class="k">as</span><span class="w"> </span><span class="s">RELEASE</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="k">EXPOSE</span><span class="w"> </span><span class="s">8080</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="k">WORKDIR</span><span class="w"> </span><span class="s">/app</span> <span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="k">ENV</span> <span class="nv">POSTGRES_USER</span><span class="o">=</span><span class="s2">&#34;&#34;</span> <span class="nv">POSTGRES_PASSWORD</span><span class="o">=</span><span class="s2">&#34;&#34;</span> <span class="nv">POSTGRES_HOST</span><span class="o">=</span>postgres <span class="nv">POSTGRES_PORT</span><span class="o">=</span><span class="m">5432</span> <span class="nv">POSTGRES_DB</span><span class="o">=</span><span class="s2">&#34;&#34;</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="k">COPY</span> dist/ ./dist/<span class="err">
</span></span></span><span class="line"><span class="cl"><span class="k">COPY</span> docker/flask/uwsgi.ini ./<span class="err">
</span></span></span><span class="line"><span class="cl"><span class="k">COPY</span> --from<span class="o">=</span>BASE /root/wheels /root/wheels<span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="k">RUN</span> apk add --no-cache build-base linux-headers postgresql-dev pcre-dev libpq uwsgi-python3 <span class="o">&amp;&amp;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">    pip install --no-index --find-links<span class="o">=</span>/root/wheels /root/wheels/* <span class="o">&amp;&amp;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">    pip install dist/*<span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="k">CMD</span> <span class="p">[</span><span class="s2">&#34;uwsgi&#34;</span><span class="p">,</span> <span class="s2">&#34;--ini&#34;</span><span class="p">,</span> <span class="s2">&#34;/app/uwsgi.ini&#34;</span><span class="p">]</span><span class="err">
</span></span></span></code></pre></div><p>This dockerfile uses a relatively new Docker feature called multi-stage builds. Here we use a base image (Python3.6) to generate some Python wheel files. These wheel files require specific Linux dependencies that we don&rsquo;t actually need in our main Docker container. Then we define our actual image and copy over the wheel files that we need for our application. This is done to help reduce the size of our Docker image file, we want to try to make the image as possible (as much as it makes sense). At the end of the dockerfile, the <code>BASE</code> image is destroyed.</p>
<p>So our actual Docker image is a bit more interesting. It does the following;</p>
<ul>
<li>It exposes port 8080, this actually doesn&rsquo;t do anything but is simply for documentation purposes</li>
<li>It then creates a default directory <code>/app/</code></li>
<li>We define some environment variables that are required by this container (all used to connect to Postgres). <strong>Please Note</strong> in a production environment you don&rsquo;t want to expose passwords and username as environment variables on your docker containers, instead, you should use a secrets stores such as <a href="https://www.vaultproject.io/">HashiCorp Vault</a>. These variables will be defined when we run the container</li>
<li>It copies the <code>dist</code> folder which has our Python package as a tar file. You can generate this file if you have a <code>setup.py</code> file and run <code>python setup.py sdist</code> in the same folder as your <code>setup.py</code></li>
<li>It copies the <code>uwsgi.ini</code> used to configure the uWSGI server</li>
<li>It copies all the wheels folder from the <code>BASE</code> image hence the <code>--from=BASE</code></li>
<li>Then we install the wheels and our everything in the <code>dist</code> folder, which our Python code as a <code>tar</code></li>
<li>Finally when the Docker image will be run it will execute <code>uwsgi --ini /app/uwsgi.ini</code>. Using the uwsgi.ini file we copied into the image</li>
</ul>
<p>In theory, you could simply copy and install the requirements.txt and copy all the source code to the <code>/app</code> folder. However, I prefer to generate and install the actual Python package I think it&rsquo;s cleaner and you only have to copy a single <code>tar</code> file. However, this does require you to run the command to generate the <code>dist</code> folder before you try to build the Docker image.</p>
<p><strong>Note</strong>: The environment variables POSTGRES_ should be the same values as defined in database.conf.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="cl"><span class="k">[uwsgi]</span>
</span></span><span class="line"><span class="cl"><span class="na">socket</span> <span class="o">=</span> <span class="s">:8080</span>
</span></span><span class="line"><span class="cl"><span class="na">module</span> <span class="o">=</span> <span class="s">example.wsgi:app</span>
</span></span><span class="line"><span class="cl"><span class="na">master</span> <span class="o">=</span> <span class="s">1</span>
</span></span><span class="line"><span class="cl"><span class="na">processes</span> <span class="o">=</span> <span class="s">4</span>
</span></span><span class="line"><span class="cl"><span class="na">plugin</span> <span class="o">=</span> <span class="s">python</span>
</span></span></code></pre></div><p>This is the configuration file used by the uWSGI server. This is where we define which port uWSGI listens for traffic on in this cases it&rsquo;s 8080. <strong>Note</strong> that since we&rsquo;ve defined <code>socket</code> you cannot access the uWSGI server directly you need a web server in front of it, if you wanted to use just uWSGI you would change this option to <code>http</code>. The other import option is the <code>module</code>, we point it to our installed module example then the wsgi module and app variable. Hence the <code>module=example.wsgi:app</code>. In this example the <code>wsgi.py</code> module calls <code>create_app()</code> function which creates the Flask app.</p>
<h2 id="postgres">Postgres</h2>
<p>The Postgres image is simpler the latest Postgres image from Docker hub, then we pass some environment variables to it to configure it.</p>
<pre tabindex="0"><code class="language-conf" data-lang="conf">POSTGRES_USER=test
POSTGRES_PASSWORD=password
POSTGRES_HOST=postgres
POSTGRES_PORT=5432
POSTGRES_DB=example
</code></pre><p>The environment variables passed look something like this.
<strong>NOTE</strong> You don&rsquo;t need to pass the port or the host to Postgres Docker container. These are used by the Flask container.</p>
<h2 id="docker-compose">Docker Compose</h2>
<p>So we&rsquo;ve defined our dockerfile and configuration files used by those dockerfiles but how do we actually use Docker. One way we can use docker is to define it using docker-compose. Here we define a set of services and Docker will automatically run and build those services and handle the networking for us. I personally use docker-compose for development as it saves a lot of time running the <code>docker build</code> and <code>docker run</code> commands for each Docker image/container.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">version</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;3.5&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">services</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">web_server</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">container_name</span><span class="p">:</span><span class="w"> </span><span class="l">nginx</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">build</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">context</span><span class="p">:</span><span class="w"> </span><span class="l">.</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">dockerfile</span><span class="p">:</span><span class="w"> </span><span class="l">docker/nginx/Dockerfile</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">ports</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="m">80</span><span class="p">:</span><span class="m">80</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">depends_on</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">app</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">app</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">container_name</span><span class="p">:</span><span class="w"> </span><span class="l">flask</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">build</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">context</span><span class="p">:</span><span class="w"> </span><span class="l">.</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">dockerfile</span><span class="p">:</span><span class="w"> </span><span class="l">docker/flask/Dockerfile</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">env_file</span><span class="p">:</span><span class="w"> </span><span class="l">docker/database.conf</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">expose</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="m">8080</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">depends_on</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">database</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">database</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">container_name</span><span class="p">:</span><span class="w"> </span><span class="l">postgres</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">postgres:latest</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">env_file</span><span class="p">:</span><span class="w"> </span><span class="l">docker/database.conf</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">ports</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="m">5432</span><span class="p">:</span><span class="m">5432</span><span class="w">  
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">volumes</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">db_volume:/var/lib/postgresql</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">volumes</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">db_volume</span><span class="p">:</span><span class="w">
</span></span></span></code></pre></div><p>So at the top of the file we define the version number of docker-compose, it is recommended that users use version 3 now. Then we define our services, one service equals one Docker container. Each service is given a name, such as web_server, app and database.</p>
<h3 id="web_server">web_server</h3>
<p>This service runs our Nginx server. We call the container <code>nginx</code> for obvious reasons. We pass it the location of the dockerfile to build. The build <code>context: .</code> simply means relative to this current working directory so we when we COPY file we will copy them relative to docker-compose.yml file. The final thing we do publish our ports so all traffic on the host machine on port 80 is mapped to the Docker container also on port 80. We could use any port on our guest machine say we used 8001, then we would access the web server by going to <code>localhost:8001</code>. The final part specifies this container depends on the app container so app will be run before this container.</p>
<h3 id="app">app</h3>
<p>This is a relatively simple service, again we point it to our flask dockerfile and set build context to the current folder. Then we pass some environment variables as a file, the variables are taken from this file (same <code>database.conf</code> as defined above). These variables are used to allow Python to connect to the database. Finally, we expose port 8080, again this for documentation purposes so other users know this container expects traffic on port 8080. Since we need to connect to the database when we set up our app we depend on the database container being run first.</p>
<h3 id="database">database</h3>
<p>This service doesn&rsquo;t have its own dockerfile but instead uses the official Postgres image. It then passes some environment variables as a file, the same file that gets passed to Flask container. We don&rsquo;t actually need the host or port variables but it&rsquo;s easier to maintain a single file in this case. We then map the host port 5432 to the guest Docker container port 5432. This is the port that Postgres listens on. Like with Nginx you can set the host port to whatever you want, but make sure you change this in <code>database.conf</code> and update <code>POSTGRES_PORT</code> variable. Finally, we mount a volume so that data is persistent, without this when the database Docker container was killed you would lose all your data. By mounting <code>db_volume</code> even you kill the container to say update the Postgres image your data will persist.</p>
<h3 id="docker-compose-buildrun">Docker Compose Build/Run</h3>
<p>To actually run the docker-compose file (in the same folder as <code>docker-compose.yml</code>), you can do something like below. Where <code>-d</code> means it runs in the background. This will build all three services and once it has built our Docker images it will run those Docker images as Docker containers.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">docker-compose up --build -d
</span></span></code></pre></div><h3 id="docker-buildrun">Docker Build/Run</h3>
<p>One important thing to note is <code>docker-compose</code> should not really be used in production for several reasons, such as downtime when updating your Docker containers. If your deploying to only 1 host docker-compose should be fine but in reality, most applications required high availability and zero downtime updates, in this case, you should at using a container orchestration tools such as Kuberenetes. So an alternative approach is to build and run each Docker container yourself, the equivalent commands for this <code>docker-compose.yml</code> file would be.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># Build our images first</span>
</span></span><span class="line"><span class="cl">docker build -f docker/nginx/Dockerfile -t nginx .
</span></span><span class="line"><span class="cl">docker build -f docker/flask/Dockerfile -t flask .
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Run our containers</span>
</span></span><span class="line"><span class="cl">docker run -d --name nginx -p 80:80 nginx
</span></span><span class="line"><span class="cl">docker run -d --name flask -p <span class="m">8080</span>  --env-file docker/database.conf flask
</span></span><span class="line"><span class="cl">docker volume create --name db_volume
</span></span><span class="line"><span class="cl">docker run -d --name postgres -p 5432:5432 <span class="se">\
</span></span></span><span class="line"><span class="cl">           ---------------------------------------------------------------------------------------------------env-file docker/database.conf <span class="se">\
</span></span></span><span class="line"><span class="cl">           -v db_volume:/var/lib/postgresql postgres:latest
</span></span></code></pre></div><p><strong>Note</strong>: After you&rsquo;ve built your own images you can push them to either a public or private Docker registry so you or other people can access them. This is a common way to access your images during your CI pipeline. In fact base images like <code>postgres:latest</code> are taken from the <a href="https://hub.docker.com/_/postgres/">official Docker registry</a>.</p>
<h2 id="appendix">Appendix</h2>
<ul>
<li><a href="https://gitlab.com/hmajid2301/blog/-/tree/main/content/posts/2018-11-19-using-multiple-docker-containers-to-setup-nginx-flask-and-postgres/source_code">Example source code</a></li>
</ul>
]]></content:encoded>
    </item>
    
    <item>
      <title>Running Expo/React Native in Docker</title>
      <link>https://haseebmajid.dev/posts/2018-10-31-running-expo-react-native-in-docker/</link>
      <pubDate>Wed, 31 Oct 2018 00:00:00 +0000</pubDate>
      <author>Me</author>
      <guid>https://haseebmajid.dev/posts/2018-10-31-running-expo-react-native-in-docker/</guid>
      <description>&lt;p&gt;Running Expo/React Native in a Docker container can sometimes cause issues. In this example, I will be running
Docker 🐳 within a guest VM (Ubuntu) which will run on my host machine (Windows). My host machine will also
be running another VM as the Android emulator (Genymotion) for Expo to connect to. You can get a more
detailed post about how to connect two VMs together
&lt;a href=&#34;https://haseebmajid.dev/posts/react-native-with-virtualbox/&#34;&gt;here&lt;/a&gt;,
#Plug 🔌🔌🔌. Since I&amp;rsquo;ve set up networking on those two VMs already as far as Expo is concerned
it might as well be running on the host machine (Windows). Also in this example, I will be testing
this on an Android device.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>Running Expo/React Native in a Docker container can sometimes cause issues. In this example, I will be running
Docker 🐳 within a guest VM (Ubuntu) which will run on my host machine (Windows). My host machine will also
be running another VM as the Android emulator (Genymotion) for Expo to connect to. You can get a more
detailed post about how to connect two VMs together
<a href="https://haseebmajid.dev/posts/react-native-with-virtualbox/">here</a>,
#Plug 🔌🔌🔌. Since I&rsquo;ve set up networking on those two VMs already as far as Expo is concerned
it might as well be running on the host machine (Windows). Also in this example, I will be testing
this on an Android device.</p>
<p><img
        loading="lazy"
        src="/posts/2018-10-31-running-expo-react-native-in-docker/images/docker-nyan.gif"
        type=""
        alt="Original Image: https://maraaverick.rbind.io/2017/11/docker-izing-your-work-in-r/ and https://tutuappapkdownload.com/expo-apk/"
        
      /></p>
<h2 id="prerequisites">Prerequisites</h2>
<ul>
<li><a href="https://docs.docker.com/install/">Install Docker</a></li>
<li>(optional) <a href="https://docs.docker.com/compose/install/">Install docker-compose</a></li>
<li>Android device/emulator to test on</li>
</ul>
<h2 id="docker">Docker</h2>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;main&#34;</span><span class="p">:</span> <span class="s2">&#34;node_modules/expo/AppEntry.js&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;private&#34;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;scripts&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;android&#34;</span><span class="p">:</span> <span class="s2">&#34;expo-cli start --android&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;ios&#34;</span><span class="p">:</span> <span class="s2">&#34;expo-cli start --ios&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;start&#34;</span><span class="p">:</span> <span class="s2">&#34;expo-cli start&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;dependencies&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;expo&#34;</span><span class="p">:</span> <span class="s2">&#34;30.0.0&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;expo-cli&#34;</span><span class="p">:</span> <span class="s2">&#34;2.2.4&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;react&#34;</span><span class="p">:</span> <span class="s2">&#34;16.3.1&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;react-native&#34;</span><span class="p">:</span> <span class="s2">&#34;https://github.com/expo/react-native/archive/sdk-30.0.0.tar.gz&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>The <code>package.json</code> file I will be using the following example is a very barebones file, just including the minimum
packages required to run Expo.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-docker" data-lang="docker"><span class="line"><span class="cl"><span class="k">FROM</span><span class="w"> </span><span class="s">node:latest</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="k">LABEL</span> <span class="nv">version</span><span class="o">=</span><span class="m">1</span>.2.1<span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="k">ENV</span> <span class="nv">ADB_IP</span><span class="o">=</span><span class="s2">&#34;192.168.1.1&#34;</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="k">ENV</span> <span class="nv">REACT_NATIVE_PACKAGER_HOSTNAME</span><span class="o">=</span><span class="s2">&#34;192.255.255.255&#34;</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="k">EXPOSE</span><span class="w"> </span><span class="s">19000</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="k">EXPOSE</span><span class="w"> </span><span class="s">19001</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="k">RUN</span> apt-get update <span class="o">&amp;&amp;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">    apt-get install android-tools-adb<span class="err">
</span></span></span><span class="line"><span class="cl"><span class="k">WORKDIR</span><span class="w"> </span><span class="s">/app</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="k">COPY</span> package.json yarn.lock app.json ./<span class="err">
</span></span></span><span class="line"><span class="cl"><span class="k">RUN</span> yarn --network-timeout <span class="m">100000</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="k">CMD</span> adb connect <span class="nv">$ADB_IP</span> <span class="o">&amp;&amp;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">        yarn run android<span class="err">
</span></span></span></code></pre></div><p><code>FROM node:latest</code></p>
<p>Tells us which Docker Image we are using as a base, in this case, the official node.js image. This is because it
will have a lot of the dependencies we need already installed such as yarn and npm.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">ENV ADB_IP=&#34;192.168.1.1&#34;
</span></span><span class="line"><span class="cl">ENV REACT_NATIVE_PACKAGER_HOSTNAME=&#34;192.255.255.255&#34;
</span></span></code></pre></div><p>Sets an environment variable which can be accessed during runtime of the Docker container. Strictly speaking, these
don&rsquo;t need to be here because we can always inject into the Docker container at runtime, but I like to have the
environment variables documented.</p>
<p>The <strong>ADB_IP</strong> is IP of the Android device 📱 to connect to. The <strong>REACT_NATIVE_PACKAGER_HOSTNAME</strong> environment variable is
very important because it sets which IP address Expo (cli) is running on, this is the IP Address your phone will try to
connect to. If this is not set correctly, you&rsquo;ll get an error similar to Figure 1. You can work out the correct IP
address on Linux by using the following command. The first one should host IP (192.168.27.128 on my machine).</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">hostname -I
</span></span><span class="line"><span class="cl">192.168.27.128 192.168.130.128 172.17.0.1 172.19.0.1 172.18.0.1
</span></span></code></pre></div><p>The reason this environment variable needs to be set is because by default the React Native packager
(which expo relies on) picks the first IP it sees on the machine, hence you can run expo on your host machine
fine but when you run in a Docker container you cannot connect to it because it&rsquo;s trying to use the Docker
IP address (one of the ones starting with 172.xxx.xxx.xxx).</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">EXPOSE 19000
</span></span><span class="line"><span class="cl">EXPOSE 19001
</span></span></code></pre></div><p>This is essentially meta data letting the user of the Docker container know that they can access data on those ports.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">RUN apt-get update <span class="o">&amp;&amp;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">    apt-get install android-tools-adb
</span></span></code></pre></div><p>Install Android Debugging Bridge (ADB), which is used to connect to an Android device and debug the application.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">COPY package.json yarn.lock app.json ./
</span></span><span class="line"><span class="cl">RUN yarn --network-timeout 100000
</span></span></code></pre></div><p>Copy some important files from host to Docker container. The <code>package.json</code> and <code>yarn.lock</code> are used to install
the dependencies and <code>app.json</code> is required by expo as a bare minimum.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">CMD adb connect $ADB_IP &amp;&amp; \
</span></span><span class="line"><span class="cl">    yarn run android
</span></span><span class="line"><span class="cl">    # runs expo-cli start --android
</span></span></code></pre></div><p><img
        loading="lazy"
        src="/posts/2018-10-31-running-expo-react-native-in-docker/images/error-emulator.png"
        type=""
        alt="Figure 1: Could not connect error 😢"
        
      /></p>
<h2 id="running-docker">Running Docker</h2>
<p>This command runs when the Docker Image is first to run, every other command is used to build to the image itself. This
uses an environment variable passed into the Docker container and connects to the Android device at $ADB<em>IP. Then run
the <strong>android</strong> command in _package.json</em>. Then you can simply run the following commands to build and start your Docker container.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">docker build -t expo-android .
</span></span><span class="line"><span class="cl">docker run -e <span class="nv">ADB_IP</span><span class="o">=</span>192.168.112.101 <span class="se">\
</span></span></span><span class="line"><span class="cl">            -e <span class="nv">REACT_NATIVE_PACKAGER_HOSTNAME</span><span class="o">=</span>192.168.1.1 <span class="se">\
</span></span></span><span class="line"><span class="cl">            -p 19000:19000 <span class="se">\
</span></span></span><span class="line"><span class="cl">            -p 19001:19001 <span class="se">\
</span></span></span><span class="line"><span class="cl">            expo-android
</span></span></code></pre></div><ul>
<li>-t is used to name the image (expo-android)</li>
<li>. tells Docker where the Dockerfile is (in the current directory)</li>
<li>&ndash;env sets environment used by Docker container when it starts to run (REACT_NATIVE_PACKAGER_HOSTNAME andADB_IP are overwritten using these new values)</li>
<li>-p publishes ports, in this example, it maps port 19000 on the host to port 19000 on the Docker container (and also 19001), as we need to access port 19000 and 19001 so that Expo (expo-cli) can connect to our Android device.</li>
</ul>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">version</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;3.5&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">services</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">expo_android</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">container_name</span><span class="p">:</span><span class="w"> </span><span class="l">expo_android</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">build</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">context</span><span class="p">:</span><span class="w"> </span><span class="l">.</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">dockerfile</span><span class="p">:</span><span class="w"> </span><span class="l">Dockerfile</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">env_file</span><span class="p">:</span><span class="w"> </span><span class="l">.env</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">ports</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="m">19000</span><span class="p">:</span><span class="m">19000</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="m">19001</span><span class="p">:</span><span class="m">19001</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">volumes</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">${PWD}/:/app/</span><span class="w">
</span></span></span></code></pre></div><p>Since expo is being used to build mobile phone applications Docker isn&rsquo;t going to be used in production. I prefer to use
docker-compose to do the building and running, it means I can run one simple command and do the building and running in
one step. Quick aside docker-compose is great for development, especially when you need to run multiple Docker container,
but is not really built to be used in production. Look at using a container orchestration tool such as Kubernetes.</p>
<p>I also mount my current directory on the host machine to /app/ directory on the docker container, this is so that any
files that change on my host machine will also change in the Docker container, rather than having to build the
Docker container again.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">docker-compose up --build -d
</span></span></code></pre></div><h3 id="environment-variables">Environment Variables</h3>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-env" data-lang="env"><span class="line"><span class="cl"><span class="nv">ADB_IP</span><span class="o">=</span><span class="s2">&#34;192.168.112.101&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nv">REACT_NATIVE_PACKAGER_HOSTNAME</span><span class="o">=</span><span class="s2">&#34;192.168.27.128&#34;</span>
</span></span></code></pre></div><p>An example <code>.env</code> file used to pass environment variables (using docker-compose) to the Docker container.</p>
<h2 id="appendix">Appendix</h2>
<ul>
<li><a href="https://gitlab.com/hmajid2301/blog/-/tree/main/content/posts/2018-10-31-running-expo-react-native-in-docker/source_code">Example source code</a></li>
<li><a href="https://medium.freecodecamp.org/a-beginner-friendly-introduction-to-containers-vms-and-docker-79a9e3e119b">Docker explained</a></li>
<li><a href="https://github.com/react-community/create-react-native-app/issues/81">GitHub issue around could not connect errors</a></li>
<li><a href="https://www.genymotion.com/">Genymotion emulator</a></li>
<li><a href="https://ezgif.com/overlay">GIF overlay creator (Nyan Docker)</a></li>
<li>React logo from <a href="https://seeklogo.com/vector-logo/273845/react">here</a></li>
</ul>
]]></content:encoded>
    </item>
    
  </channel>
</rss>
