<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Docker on David Hamann</title><link>https://davidhamann.de/tags/docker/</link><description>Recent content in Docker on David Hamann</description><generator>Hugo</generator><language>en</language><copyright>&amp;copy; David Hamann</copyright><lastBuildDate>Fri, 13 May 2022 00:00:00 +0000</lastBuildDate><atom:link href="https://davidhamann.de/tags/docker/feed.xml" rel="self" type="application/rss+xml"/><item><title>Database backups via mysqldump: from MariaDB container to S3</title><link>https://davidhamann.de/2022/05/13/mysqldump-docker-container-to-s3/</link><pubDate>Fri, 13 May 2022 00:00:00 +0000</pubDate><guid>https://davidhamann.de/2022/05/13/mysqldump-docker-container-to-s3/</guid><description>&lt;p&gt;For a little side project I wanted an easy way to perform regular backups of a MariaDB database and upload the resultant dump gzipped to S3.&lt;/p&gt;
&lt;p&gt;Here are the steps to make this happen.&lt;/p&gt;
&lt;h2 id="the-setup"&gt;The setup&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Docker container running MariaDB&lt;/li&gt;
&lt;li&gt;Docker engine running on a AWS EC2 instance&lt;/li&gt;
&lt;li&gt;An S3 bucket as the destination for the dumps&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="writing-the-backup-script"&gt;Writing the backup script&lt;/h2&gt;
&lt;p&gt;We begin with writing our shell script, &lt;code&gt;backup.sh&lt;/code&gt;, which we will later execute in regular intervals from our host:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;#!/bin/bash
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;set&lt;/span&gt; -e
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;trap&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;/usr/bin/rm -f $temp_file; /usr/bin/rm -f &amp;#34;$temp_file.gz&amp;#34;&amp;#39;&lt;/span&gt; EXIT
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;temp_file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;/usr/bin/mktemp&lt;span class="k"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# create db dump&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;/usr/bin/docker &lt;span class="nb"&gt;exec&lt;/span&gt; &amp;lt;my container&amp;gt; sh -c &lt;span class="s1"&gt;&amp;#39;exec mysqldump --hex-blob -uroot -p&amp;#34;$MYSQL_PASSWORD&amp;#34; &amp;lt;my database&amp;gt;&amp;#39;&lt;/span&gt; &amp;gt; &lt;span class="nv"&gt;$temp_file&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# gzip&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;/usr/bin/gzip &lt;span class="nv"&gt;$temp_file&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# upload to S3&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;/usr/bin/aws s3api put-object --bucket &amp;lt;my bucket&amp;gt; --key &lt;span class="s2"&gt;&amp;#34;&amp;lt;some directory/prefix&amp;gt;/&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;date -u &lt;span class="s1"&gt;&amp;#39;+%Y-%m-%d_%H-%M-%S&amp;#39;&lt;/span&gt;&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;.dump.sql.gz&amp;#34;&lt;/span&gt; --body &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$temp_file&lt;/span&gt;&lt;span class="s2"&gt;.gz&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;exit&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;What are we doing here?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;First, we make sure to have a temp file for our operation (&lt;code&gt;mktemp&lt;/code&gt;) and register a trap for cleaning up this temp file (with or without &lt;code&gt;.gz&lt;/code&gt; extension) on exit.&lt;/li&gt;
&lt;li&gt;Next, we perform the dump via &lt;code&gt;mysqldump&lt;/code&gt; running inside the &lt;code&gt;mariadb&lt;/code&gt; container. The result is written to our temp file.&lt;/li&gt;
&lt;li&gt;Note that we&amp;rsquo;re using &lt;code&gt;--hex-blob&lt;/code&gt; to get data from binary columns hex-encoded and not garbled up&lt;/li&gt;
&lt;li&gt;After writing our dump file, we gzip it&lt;/li&gt;
&lt;li&gt;Finally, we upload the gzipped file via the aws cli tool to a bucket of our choice and give it a timestamp&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="setting-permissions-for-s3-upload"&gt;Setting permissions for S3 upload&lt;/h2&gt;
&lt;p&gt;To allow our EC2 instance to put objects into our bucket, we&amp;rsquo;ll adjust the policy like so:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;Effect&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Allow&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;Action&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;s3:ListBucket&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;s3:PutObject&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;Resource&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;arn:aws:s3:::&amp;lt;my bucket&amp;gt;/&amp;lt;some directory/prefix&amp;gt;/*&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="installing-the-cron-job"&gt;Installing the cron job&lt;/h2&gt;
&lt;p&gt;To let our backup script run every nth hour, we set up a cronjob via &lt;code&gt;crontab -e&lt;/code&gt;:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;0 */6 * * * /path/to/script/backup.sh &amp;amp;&amp;amp; /usr/bin/curl -s https://ping.allgood.systems/l/j/&amp;lt;random id&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Here, we are running the script every 6 hours and – on success – issue a HTTP request via &lt;code&gt;curl&lt;/code&gt; to a monitoring system. The latter part is optional but a good way to get notified should your script ever stop running at the expected interval. The monitoring service I use is &lt;a href="https://allgood.systems"&gt;allgood.systems&lt;/a&gt;, which I recently built.&lt;/p&gt;
&lt;h2 id="cleaning-up-old-backups"&gt;Cleaning up old backups&lt;/h2&gt;
&lt;p&gt;Once the backup files are in your S3 bucket you can decide how to proceed. Generally, it is recommended to archive some of the dumps at a different location.&lt;/p&gt;
&lt;p&gt;To regularly clean up your old files I would additionally recommend setting up a &amp;ldquo;Lifecycle rule&amp;rdquo; for your S3 bucket or even just a prefix in that bucket. This would then automatically assign an expiry date to your uploaded files.&lt;/p&gt;
&lt;h2 id="final-thoughts"&gt;Final thoughts&lt;/h2&gt;
&lt;p&gt;If you are working with a larger/busier/more critical database, there are a couple of things you might want to tweak (e.g. in regards to buffering, single transactions, checking for enough space, etc.) or choose a different approach altogether (e.g. full snapshots, setting up replication, etc.).&lt;/p&gt;</description></item><item><title>Dockerfile Entrypoint: "file not found"</title><link>https://davidhamann.de/2021/11/09/docker-entrypoint-not-found/</link><pubDate>Tue, 09 Nov 2021 00:00:00 +0000</pubDate><guid>https://davidhamann.de/2021/11/09/docker-entrypoint-not-found/</guid><description>&lt;p&gt;I was working with a fairly simple Dockerfile, defining an entrypoint and always got a &amp;ldquo;not found&amp;rdquo; error when trying to run the container.&lt;/p&gt;
&lt;p&gt;My &lt;code&gt;Dockerfile&lt;/code&gt; looked something like this:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;FROM python:3.9-alpine

[...]

WORKDIR /app
COPY . /app

RUN [&amp;#34;chmod&amp;#34;, &amp;#34;+x&amp;#34;, &amp;#34;./entrypoint.sh&amp;#34;]
ENTRYPOINT [&amp;#39;./entrypoint.sh&amp;#39;]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Building the image worked without issues, but running the container was giving me the mentioned error:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;$ docker build -t what/ever:latest
[...]
$ docker run --rm what/ever:latest
/bin/sh: [./entrypoint.sh]: not found
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;I made sure that my &lt;code&gt;entrypoint.sh&lt;/code&gt; file actually existed by overriding the entrypoint and inspecting the container:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;$ docker run -it --rm --entrypoint sh what/ever:latest
/app # ls -l
total 28
-rwxr-xr-x 1 root root 137 Nov 7 19:32 entrypoint.sh
[...]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The file existed at the expected location, so what&amp;rsquo;s the issue?&lt;/p&gt;
&lt;h2 id="its-the-quotes"&gt;It&amp;rsquo;s the quotes!&lt;/h2&gt;
&lt;p&gt;After verifying my Dockerfile again, I saw my – in retrospect – obvious mistake: the quotes!&lt;/p&gt;
&lt;p&gt;Since the &lt;code&gt;ENTRYPOINT&lt;/code&gt; list is parsed as a JSON array, it needs to be in double-quotes, not single-quotes.&lt;/p&gt;
&lt;p&gt;Adjusting the &lt;code&gt;Dockerfile&lt;/code&gt; like follows fixes the issue:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;# before
ENTRYPOINT [&amp;#39;./entrypoint.sh&amp;#39;]

# after
ENTRYPOINT [&amp;#34;./entrypoint.sh&amp;#34;]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;In case the &lt;code&gt;entrypoint.sh&lt;/code&gt; script would really be missing, the error would also be different than the one above. You would rather get something more descriptive like:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;starting container process caused: exec: &amp;#34;./entrypoint.sh&amp;#34;: stat ./entrypoint.sh: no such file or directory
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>Connecting to a host service from within a container using Docker for Mac</title><link>https://davidhamann.de/2020/10/11/connect-to-docker-host-macos/</link><pubDate>Sun, 11 Oct 2020 00:00:00 +0000</pubDate><guid>https://davidhamann.de/2020/10/11/connect-to-docker-host-macos/</guid><description>&lt;h3 id="tldr"&gt;TL;DR&lt;/h3&gt;
&lt;p&gt;Use &lt;code&gt;host.docker.internal&lt;/code&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;When you are running Docker on Linux and want to access services on the host from within a container, you can make use of the &lt;code&gt;docker0&lt;/code&gt; bridge interface (&lt;code&gt;ip a s docker0&lt;/code&gt;). This does not work when running Docker for Mac as the interface is inside a separate virtual machine (which you can confirm by getting a shell in that vm: &lt;code&gt;screen ~/Library/Containers/com.docker.docker/Data/vms/0/tty&lt;/code&gt;) and thus not visible on the local host.&lt;/p&gt;
&lt;p&gt;To workaround this, a reliable way to get to the macOS host is to use the &lt;code&gt;host.docker.internal&lt;/code&gt; DNS name which will always resolve to an IP where the host is reachable.&lt;/p&gt;
&lt;h3 id="example"&gt;Example&lt;/h3&gt;
&lt;p&gt;Let&amp;rsquo;s say you want to run some script in the container that connects to a specific HTTP target and want to observe the requests through a proxy running on your macOS machine (e.g. Burp). Then you would do:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;# Run some container
docker run -it alpine:latest /bin/sh

# ... install the dependencies for your script ...
# then run the script and target your local (!) intercept proxy
python3 exploit.py http://host.docker.internal:8081/whatever

# now observe the request in the proxy application running on the host
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;img alt="Capturing the request" loading="lazy" src="https://davidhamann.de/images/docker-mac-host-proxy.png"&gt;&lt;/p&gt;</description></item></channel></rss>