<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Containers on Major Hayden</title><link>https://major.io/tags/containers/</link><description>Recent content in Containers on Major Hayden</description><generator>Hugo</generator><language>en</language><managingEditor>major@mhtx.net (Major Hayden)</managingEditor><webMaster>major@mhtx.net (Major Hayden)</webMaster><copyright>All content licensed &lt;a href="https://creativecommons.org/licenses/by-sa/4.0/"&gt;CC BY-SA 4.0&lt;/a&gt;</copyright><lastBuildDate>Mon, 09 Nov 2026 20:39:15 -0600</lastBuildDate><atom:link href="https://major.io/tags/containers/index.xml" rel="self" type="application/rss+xml"/><item><title>Automatic container updates with Podman quadlets</title><link>https://major.io/p/podman-quadlet-automatic-updates/</link><pubDate>Fri, 19 Sep 2025 00:00:00 +0000</pubDate><author>major@mhtx.net (Major Hayden)</author><guid>https://major.io/p/podman-quadlet-automatic-updates/</guid><description>&lt;p&gt;Running containers at home or in production often means juggling updates across multiple services.&#10;While orchestration platforms like Kubernetes handle this automatically, what about those simple deployments on a single host?&lt;/p&gt;&#10;&lt;p&gt;Podman&amp;rsquo;s quadlet system integrates containers directly with systemd, and when combined with automatic updates, you get a robust solution that keeps your containers fresh without manual intervention.&lt;/p&gt;&#10;&lt;p&gt;Let&amp;rsquo;s explore how to set up automatic container updates using Podman quadlets on Fedora, turning container management into a hands-off operation that just works.&lt;/p&gt;&#10;&lt;h2 id="setting-up-a-basic-quadlet"&gt;Setting up a basic quadlet&lt;/h2&gt;&#10;&lt;p&gt;First, let&amp;rsquo;s create a simple quadlet for running a Valkey database service under a user account.&#10;Quadlet files for user services live in &lt;code&gt;~/.config/containers/systemd/&lt;/code&gt;.&lt;/p&gt;&#10;&lt;p&gt;Create the directory if it doesn&amp;rsquo;t exist:&lt;/p&gt;&#10;&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;mkdir -p ~/.config/containers/systemd/&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Then create your quadlet file:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-ini" data-lang="ini"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# ~/.config/containers/systemd/valkey.container&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;[Container]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt;ContainerName&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;valkey&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt;Image&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;docker.io/valkey/valkey:latest&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt;Label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;io.containers.autoupdate=registry&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt;PublishPort&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;16379:6379&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt;Volume&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;valkey_data:/data&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;[Service]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt;Restart&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;always&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;[Install]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt;WantedBy&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;default.target&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The magic happens with the &lt;code&gt;Label=io.containers.autoupdate=registry&lt;/code&gt; line.&#10;This label tells Podman that this container should be automatically updated when a newer image is available in the registry.&lt;/p&gt;&#10;&lt;p&gt;After creating the file, reload systemd and start your container:&lt;/p&gt;&#10;&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;systemctl --user daemon-reload&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;systemctl --user start valkey.service&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Your container is now running as a user systemd service!&#10;Check its status with:&lt;/p&gt;&#10;&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;systemctl --user status valkey.service&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;podman ps&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="enabling-automatic-updates"&gt;Enabling automatic updates&lt;/h2&gt;&#10;&lt;p&gt;Podman ships with a systemd timer that checks for container updates.&#10;The &lt;code&gt;podman-auto-update.timer&lt;/code&gt; runs daily by default, but you need to enable it for your user:&lt;/p&gt;&#10;&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;systemctl --user &lt;span class="nb"&gt;enable&lt;/span&gt; --now podman-auto-update.timer&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;You can check when the next update will run:&lt;/p&gt;&#10;&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;systemctl --user list-timers podman-auto-update.timer&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;When the timer triggers, it runs &lt;code&gt;podman auto-update&lt;/code&gt;, which:&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Checks all containers with the &lt;code&gt;io.containers.autoupdate&lt;/code&gt; label&lt;/li&gt;&#10;&lt;li&gt;Pulls newer images if available&lt;/li&gt;&#10;&lt;li&gt;Restarts containers with the new image&lt;/li&gt;&#10;&lt;li&gt;Keeps the old image in case you need to roll back&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;h2 id="customizing-update-behavior"&gt;Customizing update behavior&lt;/h2&gt;&#10;&lt;p&gt;The &lt;code&gt;io.containers.autoupdate&lt;/code&gt; label supports different values for various update strategies:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-ini" data-lang="ini"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Always pull the latest image from the registry&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt;Label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;io.containers.autoupdate=registry&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Only update if the local image changes (useful for locally built images)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt;Label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;io.containers.autoupdate=local&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;You can also customize when updates occur by creating a timer override:&lt;/p&gt;&#10;&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;systemctl --user edit podman-auto-update.timer&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Add these lines to run updates every 6 hours instead of daily:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-ini" data-lang="ini"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;[Timer]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt;OnCalendar&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt;OnCalendar&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;*-*-* 00,06,12,18:00:00&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="monitoring-updates"&gt;Monitoring updates&lt;/h2&gt;&#10;&lt;p&gt;Track what&amp;rsquo;s happening with your automatic updates using journalctl:&lt;/p&gt;&#10;&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="c1"&gt;# View recent auto-update logs&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;journalctl --user -u podman-auto-update.service -n &lt;span class="m"&gt;50&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Follow updates in real-time&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;journalctl --user -u podman-auto-update.service -f&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Check a specific container&amp;#39;s restart history&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;journalctl --user -u valkey.service &lt;span class="p"&gt;|&lt;/span&gt; grep Started&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="further-reading"&gt;Further reading&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;a href="https://docs.podman.io/en/latest/markdown/podman-auto-update.1.html"&gt;Podman documentation on auto-updates&lt;/a&gt; - Official documentation for the auto-update feature&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://docs.podman.io/en/latest/markdown/podman-systemd.unit.5.html"&gt;Systemd Quadlet documentation&lt;/a&gt; - Complete reference for quadlet unit files&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://www.redhat.com/sysadmin/quadlet-podman"&gt;Red Hat&amp;rsquo;s guide to Podman quadlets&lt;/a&gt; - Excellent introduction with more examples&lt;/li&gt;&#10;&lt;/ul&gt;&#10;</description></item><item><title>Connect Caddy to Porkbun</title><link>https://major.io/p/caddy-porkbun/</link><pubDate>Thu, 29 Feb 2024 00:00:00 +0000</pubDate><author>major@mhtx.net (Major Hayden)</author><guid>https://major.io/p/caddy-porkbun/</guid><description>&lt;p&gt;I recently told a coworker about &lt;a href="https://caddyserver.com/"&gt;Caddy&lt;/a&gt;, a small web and proxy server with a very simple configuration.&#10;It also has a handy feature where it manages your TLS certificate for you automatically.&lt;/p&gt;&#10;&lt;p&gt;However, one problem I had at home with my &lt;a href="https://fedoraproject.org/coreos/"&gt;CoreOS&lt;/a&gt; deployment is that I don&amp;rsquo;t have inbound network access to handle the certificate verification process.&#10;Most automated certificate vendors need to reach your web server to verify that you have control over your domain.&lt;/p&gt;&#10;&lt;p&gt;This post talks about how to work around this problem with domains registered at &lt;a href="https://porkbun.com/"&gt;Porkbun&lt;/a&gt;.&lt;/p&gt;&#10;&lt;h2 id="dns-validation"&gt;DNS validation&lt;/h2&gt;&#10;&lt;p&gt;Certificate providers usually default to verifying domains by making a request to your server and retrieving a validation code.&#10;If your systems are all behind a firewall without inbound access from the internet, you can use DNS validation instead.&lt;/p&gt;&#10;&lt;p&gt;The process looks something like this:&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;You tell the certificate provider the domain names you want on your certificate&lt;/li&gt;&#10;&lt;li&gt;The certificate provider gives you some DNS records to add wherever you host your DNS records&lt;/li&gt;&#10;&lt;li&gt;You add the DNS records&lt;/li&gt;&#10;&lt;li&gt;You get your certificates once the certificate provider verifies the records.&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p&gt;You can do this manually with something like &lt;a href="https://github.com/acmesh-official/acme.sh"&gt;acme.sh&lt;/a&gt; today, but it&amp;rsquo;s &lt;strong&gt;painful&lt;/strong&gt;:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Make the initial certificate request&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;acme.sh --issue --dns -d example.com &lt;span class="se"&gt;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --yes-I-know-dns-manual-mode-enough-go-ahead-please&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Add your DNS records manually.&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Verify the DNS records and issue the certificates.&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;acme.sh --issue --dns -d example.com &lt;span class="se"&gt;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --yes-I-know-dns-manual-mode-enough-go-ahead-please --renew&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Copy the keys/certificates and configure your webserver.&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;We don&amp;rsquo;t want to live this way.&lt;/p&gt;&#10;&lt;p&gt;&lt;img src="do-not-want.gif" alt="do-not-want.gif"&gt;&lt;/p&gt;&#10;&lt;p&gt;Let&amp;rsquo;s talk about how Caddy can help.&lt;/p&gt;&#10;&lt;h2 id="adding-porkbun-support-to-caddy"&gt;Adding Porkbun support to Caddy&lt;/h2&gt;&#10;&lt;p&gt;Caddy is a minimal webserver and &lt;a href="https://github.com/caddy-dns/porkbun"&gt;Porkbun support&lt;/a&gt; doesn&amp;rsquo;t get included by default.&#10;However, we can quickly add it via a simple container build:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-Dockerfile" data-lang="Dockerfile"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;caddy:2.7.6-builder&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;builder&lt;/span&gt;&lt;span class="err"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;RUN&lt;/span&gt; xcaddy build &lt;span class="se"&gt;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --with github.com/caddy-dns/porkbun&lt;span class="err"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;caddy:2.7.6&lt;/span&gt;&lt;span class="err"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;COPY&lt;/span&gt; --from&lt;span class="o"&gt;=&lt;/span&gt;builder /usr/bin/caddy /usr/bin/caddy&lt;span class="err"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This is a two stage container build where we compile the Porkbun support and then use that new &lt;code&gt;caddy&lt;/code&gt; binary in the final container.&lt;/p&gt;&#10;&lt;p&gt;We&amp;rsquo;re not done yet!&lt;/p&gt;&#10;&lt;h2 id="automated-caddy-builds-with-updates"&gt;Automated Caddy builds with updates&lt;/h2&gt;&#10;&lt;p&gt;I created a &lt;a href="https://github.com/major/caddy"&gt;GitHub repository&lt;/a&gt; that builds the Caddy container for me and keeps it updated.&#10;There&amp;rsquo;s a &lt;a href="https://github.com/major/caddy/blob/main/.github/workflows/docker-publish.yml"&gt;workflow to publish a container&lt;/a&gt; to GitHub&amp;rsquo;s container repository and I can pull containers from there on my various CoreOS machines.&lt;/p&gt;&#10;&lt;p&gt;In addition, I use &lt;a href="https://github.com/apps/renovate"&gt;Renovate&lt;/a&gt; to watch for Caddy updates.&#10;New updates come through a &lt;a href="https://github.com/major/caddy/pull/10"&gt;regular pull request&lt;/a&gt; and I can apply them whenever I want.&lt;/p&gt;&#10;&lt;p&gt;&lt;img src="pr.png" alt="Renovate pull request" title="Example pull request from Renovate"&gt;&lt;/p&gt;&#10;&lt;h2 id="connecting-to-porkbun"&gt;Connecting to Porkbun&lt;/h2&gt;&#10;&lt;p&gt;We start here by getting an API key to manage the domain at Porkbun.&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Log into your &lt;a href="https://porkbun.com/account/domainsSpeedy"&gt;Porkbun dashboard&lt;/a&gt;.&lt;/li&gt;&#10;&lt;li&gt;Click &lt;strong&gt;Details&lt;/strong&gt; to the right of the domain you want to manage.&lt;/li&gt;&#10;&lt;li&gt;Look for &lt;strong&gt;API Access&lt;/strong&gt; in the leftmost column and turn it on.&lt;/li&gt;&#10;&lt;li&gt;At the top right of the dashboard, click &lt;strong&gt;Account&lt;/strong&gt; and then &lt;strong&gt;API Access&lt;/strong&gt;.&lt;/li&gt;&#10;&lt;li&gt;Add a title for your new API key, such as &lt;em&gt;Caddy&lt;/em&gt;, and click &lt;strong&gt;Create API Key&lt;/strong&gt;.&lt;/li&gt;&#10;&lt;li&gt;Save the API key and secrey key that are displayed.&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p&gt;Open up your Caddy configuration file (the &lt;em&gt;Caddyfile&lt;/em&gt;) and add some configuration:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-caddyfile" data-lang="caddyfile"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;email&lt;/span&gt; &lt;span class="s"&gt;me@example.com&lt;/span&gt;&lt;span class="c1"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt; # Uncomment this next line if you want to get&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt; # some test certificates first.&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt; # acme_ca https://acme-staging-v02.api.letsencrypt.org/directory&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;acme_dns&lt;/span&gt; &lt;span class="s"&gt;porkbun&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;api_key&lt;/span&gt; &lt;span class="s"&gt;pk1_******&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;api_secret_key&lt;/span&gt; &lt;span class="s"&gt;sk1_******&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gh"&gt;example.com&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;handle&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;respond&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Hello world!&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Save the Caddyfile and restart your Caddy server or container.&#10;Caddy will immediately begin requesting your TLS certificates and managing your DNS records for those certificates.&#10;This normally finishes in less than 30 seconds or so during the first run.&lt;/p&gt;&#10;&lt;p&gt;If you don&amp;rsquo;t see the HTTPS endpoint working within a minute or two, be sure to check the Caddy logs.&#10;You might have a typo in a Porkbun API key or the domain you&amp;rsquo;re trying to modify doesn&amp;rsquo;t have the &lt;strong&gt;API Access&lt;/strong&gt; switch enabled.&lt;/p&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;[!NOTE]&#10;Remember that Porkbun requires you to enable API access for each domain.&#10;API access is disabled at Porkbun by default.&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;&lt;strong&gt;That&amp;rsquo;s it!&lt;/strong&gt; 🎉&lt;/p&gt;&#10;&lt;h2 id="renewals"&gt;Renewals&lt;/h2&gt;&#10;&lt;p&gt;Caddy will keep watch over the certificates and begin the renewal process as the expiration approaches.&#10;It has a very careful retry mechanism that ensures your certificates are updated without tripping any rate limits at the certificate provider.&lt;/p&gt;&#10;&lt;h2 id="further-reading"&gt;Further reading&lt;/h2&gt;&#10;&lt;p&gt;Caddy&amp;rsquo;s detailed documentation about &lt;a href="https://caddyserver.com/docs/automatic-https"&gt;Automatic HTTPS&lt;/a&gt; and the &lt;a href="https://caddyserver.com/docs/caddyfile/directives/tls"&gt;tls configuration directive&lt;/a&gt; should answer most questions about how the process works.&lt;/p&gt;&#10;</description></item><item><title>How I learned to stop worrying and love the CoreOS</title><link>https://major.io/p/why-coreos/</link><pubDate>Fri, 13 Oct 2023 00:00:00 +0000</pubDate><author>major@mhtx.net (Major Hayden)</author><guid>https://major.io/p/why-coreos/</guid><description>&lt;hr&gt;&#10;&lt;p&gt;It&amp;rsquo;s quite clear that I&amp;rsquo;ve been on a &lt;a href="https://major.io/tags/coreos/"&gt;CoreOS&lt;/a&gt; blogging streak lately.&#10;I keep getting asked by people inside and outside my company about what makes CoreOS special and why I&amp;rsquo;ve switched over so many workloads to it.&lt;/p&gt;&#10;&lt;p&gt;The answer is pretty basic.&#10;&lt;strong&gt;It makes my life easier.&lt;/strong&gt;&lt;/p&gt;&#10;&lt;p&gt;I&amp;rsquo;m a Dad.&#10;I&amp;rsquo;m on the PTC (Parent Teacher Club) at one of my children&amp;rsquo;s schools.&#10;I volunteer as an IT person for a non-profit.&#10;I write software.&#10;I have other time consuming hobbies, such as ham radio, reading, and becoming a longer distance runner&lt;sup id="fnref:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;&#10;&lt;p&gt;My available time for my own IT projects is &lt;strong&gt;extremely limited&lt;/strong&gt; and CoreOS plays a part in keeping that part of my life as efficient as possible.&lt;/p&gt;&#10;&lt;p&gt;That&amp;rsquo;s what this blog post is about!&lt;/p&gt;&#10;&lt;h1 id="updates"&gt;Updates&lt;/h1&gt;&#10;&lt;p&gt;First and foremost, I love how CoreOS does updates.&#10;I encourage you to &lt;a href="https://docs.fedoraproject.org/en-US/fedora-coreos/auto-updates/"&gt;read the docs&lt;/a&gt; on this topic, but here&amp;rsquo;s a short explanation:&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Updates are automatically retrieved and they&amp;rsquo;re loaded into a slot.&lt;/li&gt;&#10;&lt;li&gt;Your system reboots into the new update but your original OS tree remains in place.&lt;/li&gt;&#10;&lt;li&gt;Did the update boot? Awesome. You&amp;rsquo;re good to go.&lt;/li&gt;&#10;&lt;li&gt;Did something break? The system reverts back to the known good tree.&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p&gt;In this way, it&amp;rsquo;s a lot like your smartphone.&lt;/p&gt;&#10;&lt;p&gt;You have full control over when a node looks for an update and how often it checks for them.&#10;Check out the &lt;a href="https://coreos.github.io/zincati/usage/updates-strategy/"&gt;Zincati docs&lt;/a&gt; for tons of controls over updates and reboots.&lt;/p&gt;&#10;&lt;p&gt;Some of mine are timed so well that I set maintenance windows with my monitoring provider when I know an update might take place.&#10;The updates come through, monitoring shuts off, the node reboots, and monitoring comes back.&#10;The nodes almost always come back before the monitoring even alerts me.&lt;/p&gt;&#10;&lt;p&gt;It also removes the reminders I would set for myself to update packages and run reboots.&#10;I know that my CoreOS nodes will do this automatically, so I don&amp;rsquo;t need to think about it.&lt;/p&gt;&#10;&lt;p&gt;Also, updates are rarely ever impactful to my workloads since all of them are running inside containers.&#10;My containers come right back up as soon as the node finishes its reboot.&lt;/p&gt;&#10;&lt;h1 id="toolbox"&gt;Toolbox&lt;/h1&gt;&#10;&lt;p&gt;To be fair, you can get toolbox running on lots of different Linux distributions outside of CoreOS, but that&amp;rsquo;s the first place I ever used it.&#10;Toolbox, also called &lt;a href="https://containertoolbx.org/"&gt;toolbx&lt;/a&gt;, gives you a utility container on your CoreOS node for all kinds of adminsitrative and diagnostic capabilities.&lt;/p&gt;&#10;&lt;p&gt;You might need a certain package for diagnosting a hardware issue or you might want to install some helpful utilities for the command line.&#10;Do that in a toolbox container.&#10;Just run &lt;code&gt;toolbox enter&lt;/code&gt; and if you&amp;rsquo;ve never created a toolbox container before, you&amp;rsquo;ll get a Fedora container that matches your CoreOS release.&lt;/p&gt;&#10;&lt;p&gt;But it gets better.&lt;/p&gt;&#10;&lt;p&gt;Toolbox automatically saves your container when you&amp;rsquo;re done with it so all of your installed packages stay there for next time.&#10;Also, these containers have seamless access to anything you have in your home directory, including sockets.&#10;You&amp;rsquo;re running inside a container, but it&amp;rsquo;s almost like you&amp;rsquo;re running on the host itself inside your home directory.&#10;You get the best of both worlds.&lt;/p&gt;&#10;&lt;p&gt;Don&amp;rsquo;t want Fedora?&#10;You have lots of distribution options through toolbox.&#10;Read the details on &lt;a href="https://containertoolbx.org/install/"&gt;custom images&lt;/a&gt; to create your own!&lt;/p&gt;&#10;&lt;h1 id="layering"&gt;Layering&lt;/h1&gt;&#10;&lt;p&gt;Okay, there are those situations where you really want a package on CoreOS and toolbox might not be sufficient.&#10;My muscle memory for &lt;code&gt;vim&lt;/code&gt; is so strong and CoreOS only comes with &lt;code&gt;vi&lt;/code&gt;.&lt;/p&gt;&#10;&lt;p&gt;You have a couple of options here:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Run &lt;code&gt;sudo rpm-ostree install vim&lt;/code&gt;, reboot, and you have &lt;code&gt;vim&lt;/code&gt;&lt;/li&gt;&#10;&lt;li&gt;Run &lt;code&gt;sudo rpm-ostree install --apply-live vim&lt;/code&gt; and you have &lt;code&gt;vim&lt;/code&gt; right now!&#10;&lt;em&gt;(And it&amp;rsquo;s there after a reboot as well.)&lt;/em&gt;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;When a new update comes down for the base OS from CoreOS, any packages you&amp;rsquo;ve added will be layered on the base image and available after a reboot.&#10;Layering is generally chosen as a last resort option for adding packages to the system but you shouldn&amp;rsquo;t run into issues if you&amp;rsquo;re installing small utilities or command line tools.&lt;/p&gt;&#10;&lt;h1 id="declarative-provisioning"&gt;Declarative provisioning&lt;/h1&gt;&#10;&lt;p&gt;If you&amp;rsquo;ve provisioned Linux distributions on cloud instances in the past, you&amp;rsquo;ve likely provided metadata that cloud-init uses to provision your system.&#10;CoreOS has something that acts a lot earlier in the boot process and has more power to get things done: &lt;a href="https://coreos.github.io/ignition/"&gt;ignition&lt;/a&gt;.&lt;/p&gt;&#10;&lt;p&gt;There&amp;rsquo;s a handy &lt;a href="https://coreos.github.io/butane/"&gt;butane&lt;/a&gt; file forma that you use for writing your configuration.&#10;You use the &lt;code&gt;butane&lt;/code&gt; utility to get it into ignition format.&#10;The ignition format is highly compressed to ensure you can fit your configuration into most cloud providers&amp;rsquo; metadata fields.&lt;/p&gt;&#10;&lt;p&gt;For a real example of what you can do with ignition, check out my &lt;a href="https://major.io/p/quadlets-replace-docker-compose/"&gt;quadlets post&lt;/a&gt; where I provisioned an entire Wordpress container stack using a single ignition file.&lt;/p&gt;&#10;&lt;p&gt;There&amp;rsquo;s lots of documentation for &lt;a href="https://docs.fedoraproject.org/en-US/fedora-coreos/producing-ign/"&gt;writing butane configuration&lt;/a&gt; files for common situations.&#10;It&amp;rsquo;s easy to add files, configure Wireguard, set up users, and launch containers immediately on the first boot.&lt;/p&gt;&#10;&lt;h1 id="pets-and-cattle"&gt;Pets and cattle&lt;/h1&gt;&#10;&lt;p&gt;CoreOS works well for systems that I only need online for a short time.&#10;These might be situations where I need to test a few containers and throw it away.&#10;There&amp;rsquo;s no OS to mess with and no updates to worry about.&lt;/p&gt;&#10;&lt;p&gt;It also works well for systems that I keep online for a long time.&#10;I have a few physical systems at home that run CoreOS and they&amp;rsquo;ve been extremely stable.&#10;I also have cloud instances on Hetzner, VULTR, and Digital Ocean that have run CoreOS for months without issues.&lt;/p&gt;&#10;&lt;h1 id="more-questions"&gt;More questions?&lt;/h1&gt;&#10;&lt;p&gt;Feel free to &lt;a href="mailto:major+coreos@mhtx.net"&gt;send me an email&lt;/a&gt;.&#10;I&amp;rsquo;ll update this post if I get some good ones!&lt;/p&gt;&#10;&lt;div class="footnotes" role="doc-endnotes"&gt;&#10;&lt;hr&gt;&#10;&lt;ol&gt;&#10;&lt;li id="fn:1"&gt;&#10;&lt;p&gt;Completing a half marathon without keeling over is the current goal! 👟&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;/div&gt;&#10;</description></item><item><title>Quadlets might make me finally stop using docker-compose</title><link>https://major.io/p/quadlets-replace-docker-compose/</link><pubDate>Mon, 25 Sep 2023 00:00:00 +0000</pubDate><author>major@mhtx.net (Major Hayden)</author><guid>https://major.io/p/quadlets-replace-docker-compose/</guid><description>&lt;hr&gt;&#10;&lt;p&gt;I&amp;rsquo;ve &lt;a href="https://major.io/tags/containers/"&gt;written a lot about containers&lt;/a&gt; on this blog.&#10;Why do I love containers so much?&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;They start quickly&lt;/li&gt;&#10;&lt;li&gt;They make your workloads portable&lt;/li&gt;&#10;&lt;li&gt;They disconnect your application stack from the OS that runs underneath&lt;/li&gt;&#10;&lt;li&gt;You can send your application through CI as a single container image&lt;/li&gt;&#10;&lt;li&gt;You can isolate workloads on the network and limit their resource usage much like a VM&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;However, I&amp;rsquo;m still addicted to &lt;a href="https://docs.docker.com/compose/"&gt;docker-compose&lt;/a&gt;.&#10;Can podman&amp;rsquo;s &lt;a href="https://www.redhat.com/sysadmin/quadlet-podman"&gt;quadlets&lt;/a&gt; change that?&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;Yes, I think they can.&lt;/strong&gt;&lt;/p&gt;&#10;&lt;h1 id="whats-a-quadlet"&gt;What&amp;rsquo;s a quadlet?&lt;/h1&gt;&#10;&lt;p&gt;Podman introduced support for quadlets in version 4.4 and it&amp;rsquo;s a simpler way of letting systemd manage your containers.&#10;There was an option in the past to have podman generate systemd unit files, but those were unwieldy and full of podman command line options inside a unit file.&#10;These unit files weren&amp;rsquo;t easy to edit or even parse with eyeballs.&lt;/p&gt;&#10;&lt;p&gt;Quadlets make this easier by giving you a simple ini-style file that you can easily read and edit.&#10;This blog post will include some quadlets later, but here&amp;rsquo;s an example one for Wordpress:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-ini" data-lang="ini"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;[Unit]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt;Description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;Wordpress Quadlet&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;[Container]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt;Image&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;docker.io/library/wordpress:fpm&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt;ContainerName&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;wordpress&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt;AutoUpdate&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;registry&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt;EnvironmentFile&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;/home/core/.config/containers/containers-environment&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt;Volume&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;wordpress.volume:/var/www/html&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt;Network&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;wordpress.network&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;[Service]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt;Restart&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;always&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt;TimeoutStartSec&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;900&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;[Install]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt;WantedBy&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;caddy.service multi-user.target default.target&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Lots of the lines under &lt;code&gt;[Container]&lt;/code&gt; should look familiar to most readers who have worked with containers before.&#10;However, there&amp;rsquo;s something new here.&lt;/p&gt;&#10;&lt;p&gt;Check out the &lt;code&gt;AutoUpdate=registry&lt;/code&gt; line.&#10;This tells podman to keep your container updated on a regular basis with the upstream container registry.&#10;I&amp;rsquo;ve used &lt;a href="https://major.io/p/podman-quadlet-watchtower/"&gt;watchtower&lt;/a&gt; in the past for this, but it requires a privileged container and it&amp;rsquo;s yet another external dependency.&lt;/p&gt;&#10;&lt;p&gt;Also, at the very end, you&amp;rsquo;ll see a &lt;code&gt;WantedBy&lt;/code&gt; line.&#10;This is a great place to set up container dependencies.&#10;In this example, the container that runs &lt;code&gt;caddy&lt;/code&gt; (a web server) can&amp;rsquo;t start until Wordpress is up and running.&lt;/p&gt;&#10;&lt;h1 id="so-why-not-stick-with-docker-compose"&gt;So why not stick with docker-compose?&lt;/h1&gt;&#10;&lt;p&gt;There&amp;rsquo;s no denying that docker-compose is an awesome tool.&#10;You specify the desired outcome, tell it to bring up containers, and it gets containers into the state you specified.&#10;It handles volumes, networks, and complicated configuration without a lot of legwork.&#10;The YAML files are pretty easy to read, too.&lt;/p&gt;&#10;&lt;p&gt;However, as with watchtower, that&amp;rsquo;s another external dependency.&lt;/p&gt;&#10;&lt;p&gt;My container deployments are often done at instance boot time and I don&amp;rsquo;t make too many changes afterwards.&#10;I found myself using docker-compose for the initial deployment and then I didn&amp;rsquo;t really use it again.&lt;/p&gt;&#10;&lt;p&gt;Why not remove it entirely and use what&amp;rsquo;s built into CoreOS already?&lt;/p&gt;&#10;&lt;h1 id="quaint-quadlets-quickly"&gt;Quaint quadlets quickly!&lt;/h1&gt;&#10;&lt;p&gt;Before we start, we&amp;rsquo;re going to need a few things:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;An easy to read &lt;a href="https://coreos.github.io/butane/"&gt;butane&lt;/a&gt; configuration which gets transformed into a tiny &lt;a href="https://coreos.github.io/ignition/"&gt;ignition&lt;/a&gt; configuration for CoreOS&lt;/li&gt;&#10;&lt;li&gt;Some quadlets&lt;/li&gt;&#10;&lt;li&gt;Extra system configuration&lt;/li&gt;&#10;&lt;li&gt;A cloud provider with CoreOS images &lt;em&gt;(using &lt;a href="https://www.vultr.com/?ref=9544589-8H"&gt;VULTR&lt;/a&gt; for this)&lt;/em&gt;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;I&amp;rsquo;ve packed all of these items into my &lt;a href="https://github.com/major/quadlets-wordpress"&gt;quadlets-wordpress&lt;/a&gt; repository to make it easy.&#10;Start by looking at the &lt;a href="https://github.com/major/quadlets-wordpress/blob/main/config.butane"&gt;config.butane&lt;/a&gt; file.&lt;/p&gt;&#10;&lt;p&gt;Let&amp;rsquo;s break it down here.&#10;First up, we add an ssh key for the default &lt;code&gt;core&lt;/code&gt; user.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;variant&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;fcos&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1.5.0&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;passwd&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;users&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;core&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;ssh_authorized_keys&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDyoH6gU4lgEiSiwihyD0Rxk/o5xYIfA3stVDgOGM9N0&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Next up, we enable the &lt;code&gt;podman-auto-update.timer&lt;/code&gt; so we get container updates automatically:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;links&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;/home/core/.config/systemd/user/timers.target.wants/podman-auto-update.timer&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;/usr/lib/systemd/user/podman-auto-update.timer&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;core&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;group&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;core&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Next is the long &lt;code&gt;files&lt;/code&gt; section:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;files&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# Ensure the `core` user can keep processes running after they&amp;#39;re logged out.&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;/var/lib/systemd/linger/core&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;mode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0644&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# Allow caddy to listen on 80 and 443.&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# Allow it to ask for bigger network buffers, too.&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;/etc/sysctl.d/90-caddy.conf&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;contents&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;inline&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="sd"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; net.ipv4.ip_unprivileged_port_start = 80&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; net.core.rmem_max=2500000&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; net.core.wmem_max=2500000&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# Set up an an environment file that containers can read to configure themselves.&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;/home/core/.config/containers/containers-environment&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;contents&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;inline&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="sd"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; MYSQL_DATABASE=wordpress&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; MYSQL_USER=wordpress&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; MYSQL_ROOT_PASSWORD=mariadb-needs-a-secure-password&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; MYSQL_PASSWORD=wordpress-needs-a-secure-password&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; WORDPRESS_DB_HOST=mariadb&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; WORDPRESS_DB_USER=wordpress&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; WORDPRESS_DB_PASSWORD=wordpress-needs-a-secure-password&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; WORDPRESS_DB_NAME=wordpress&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;mode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0644&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# Deploy the caddy configuration file from the repository.&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;/home/core/.config/caddy/Caddyfile&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;contents&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;local&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;caddy/Caddyfile&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;mode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0644&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;core&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;group&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;core&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# Add some named volumes for caddy and wordpress.&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;/home/core/.config/containers/systemd/caddy-config.volume&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;contents&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;inline&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="sd"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; [Volume]&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;core&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;group&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;core&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;/home/core/.config/containers/systemd/caddy-data.volume&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;contents&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;inline&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="sd"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; [Volume]&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;core&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;group&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;core&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;/home/core/.config/containers/systemd/wordpress.volume&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;contents&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;inline&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="sd"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; [Volume]&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;core&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;group&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;core&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# Create a network for all the containers to use and enable the&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# DNS plugin. This allows containers to find each other using&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# the container names.&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;/home/core/.config/containers/systemd/wordpress.network&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;contents&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;inline&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="sd"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; [Network]&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; DisableDNS=false&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; Internal=false&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;core&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;group&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;core&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# Add the wordpress container.&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;/home/core/.config/containers/systemd/wordpress.container&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;contents&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;local&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;quadlets/wordpress.container&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;mode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0644&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;core&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;group&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;core&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# Add the MariaDB container.&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;/home/core/.config/containers/systemd/mariadb.container&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;contents&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;local&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;quadlets/mariadb.container&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;mode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0644&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;core&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;group&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;core&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# Add the caddy container.&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;/home/core/.config/containers/systemd/caddy.container&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;contents&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;local&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;quadlets/caddy.container&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;mode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0644&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;core&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;group&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;core&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The &lt;a href="https://github.com/major/quadlets-wordpress/blob/main/caddy/Caddyfile"&gt;Caddyfile&lt;/a&gt; is also in the repository and will be deployed by the butane configuration shown above.&lt;/p&gt;&#10;&lt;p&gt;We can go through each quadlet in detail.&#10;First up is MariaDB.&#10;We tell systemd that the wordpress container will want to have this one started first.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-ini" data-lang="ini"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;[Unit]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt;Description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;MariaDB Quadlet&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;[Container]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt;Image&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;docker.io/library/mariadb:11&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt;ContainerName&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;mariadb&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt;AutoUpdate&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;registry&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt;EnvironmentFile&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;/home/core/.config/containers/containers-environment&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt;Volume&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;mariadb.volume:/var/lib/mysql&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt;Network&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;wordpress.network&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;[Service]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt;Restart&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;always&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt;TimeoutStartSec&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;900&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;[Install]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt;WantedBy&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;wordpress.service multi-user.target default.target&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The wordpress quadlet is much the same as the MariaDB one, but we tell systemd that caddy will want wordpress started first.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-ini" data-lang="ini"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;[Unit]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt;Description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;Wordpress Quadlet&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;[Container]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt;Image&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;docker.io/library/wordpress:fpm&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt;ContainerName&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;wordpress&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt;AutoUpdate&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;registry&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt;EnvironmentFile&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;/home/core/.config/containers/containers-environment&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt;Volume&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;wordpress.volume:/var/www/html&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt;Network&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;wordpress.network&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;[Service]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt;Restart&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;always&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt;TimeoutStartSec&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;900&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;[Install]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt;WantedBy&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;caddy.service multi-user.target default.target&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Finally, the caddy quadlet contains four volumes and some published ports.&#10;These ports will be published to the container host.&#10;Also, you&amp;rsquo;ll note that the wordpress volume is mounted here, too.&#10;This is because caddy can serve static files &lt;em&gt;much faster&lt;/em&gt; than wordpress can.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-ini" data-lang="ini"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;[Unit]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt;Description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;Caddy Quadlet&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;[Container]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt;Image&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;docker.io/library/caddy:latest&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt;ContainerName&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;caddy&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt;AutoUpdate&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;registry&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt;EnvironmentFile&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;/home/core/.config/containers/containers-environment&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt;Volume&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;caddy-data.volume:/data&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt;Volume&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;caddy-config.volume:/config&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt;Volume&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;/home/core/.config/caddy/Caddyfile:/etc/caddy/Caddyfile:Z&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt;Volume&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;wordpress.volume:/var/www/html&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt;PublishPort&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;80:80&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt;PublishPort&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;443:443&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt;Network&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;wordpress.network&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;[Service]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt;Restart&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;always&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt;TimeoutStartSec&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;900&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;[Install]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt;WantedBy&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;multi-user.target default.target&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h1 id="launch-the-quadlets"&gt;Launch the quadlets&lt;/h1&gt;&#10;&lt;p&gt;There&amp;rsquo;s a &lt;a href="https://github.com/major/quadlets-wordpress/blob/main/launch-instance"&gt;launch script&lt;/a&gt; that ships this configuration to VULTR and launches a CoreOS instance:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;#!/bin/bash&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# This command starts up a CoreOS instance on Vultr using the vultr-cli&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;vultr-cli instance create &lt;span class="se"&gt;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --os &lt;span class="m"&gt;391&lt;/span&gt; &lt;span class="se"&gt;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --plan vhp-1c-1gb-amd &lt;span class="se"&gt;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --region dfw &lt;span class="se"&gt;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --notify &lt;span class="nb"&gt;true&lt;/span&gt; &lt;span class="se"&gt;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --ipv6 &lt;span class="nb"&gt;true&lt;/span&gt; &lt;span class="se"&gt;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -u &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;butane --files-dir . config.butane&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="se"&gt;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -l &lt;span class="s2"&gt;&amp;#34;coreos-&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;date &lt;span class="s2"&gt;&amp;#34;+%s&amp;#34;&lt;/span&gt;&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;To launch an instance, get your &lt;a href="https://my.vultr.com/settings/#settingsapi"&gt;VULTR API key&lt;/a&gt; first.&#10;Then install vultr-cli and butane:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; sudo dnf -y install butane vultr-cli&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;After launch, check to see what your containers are doing:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;[core@vultr ~]$&lt;/span&gt; podman ps&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;afa2d6501593 docker.io/library/caddy:latest caddy run --confi... 54 seconds ago Up 53 seconds 0.0.0.0:80-&amp;gt;80/tcp, 0.0.0.0:443-&amp;gt;443/tcp caddy&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;460426f39e6c docker.io/library/mariadb:11 mariadbd 35 seconds ago Up 35 seconds mariadb&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;92ece6538d5a docker.io/library/wordpress:fpm php-fpm 28 seconds ago Up 29 seconds wordpress&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;We should be able to talk to wordpress through caddy on port 80:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;[core@vultr ~]$&lt;/span&gt; curl -si http://localhost/wp-admin/install.php &lt;span class="p"&gt;|&lt;/span&gt; head -n &lt;span class="m"&gt;25&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;HTTP/1.1 200 OK&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Cache-Control: no-cache, must-revalidate, max-age=0&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Content-Type: text/html; charset=utf-8&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Expires: Wed, 11 Jan 1984 05:00:00 GMT&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Server: Caddy&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;X-Powered-By: PHP/8.0.30&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Date: Mon, 25 Sep 2023 21:43:40 GMT&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Transfer-Encoding: chunked&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;&amp;lt;!DOCTYPE html&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;&amp;lt;html lang=&amp;#34;en-US&amp;#34; xml:lang=&amp;#34;en-US&amp;#34;&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;&amp;lt;head&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;&#9;&amp;lt;meta name=&amp;#34;viewport&amp;#34; content=&amp;#34;width=device-width&amp;#34; /&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;&#9;&amp;lt;meta http-equiv=&amp;#34;Content-Type&amp;#34; content=&amp;#34;text/html; charset=utf-8&amp;#34; /&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;&#9;&amp;lt;meta name=&amp;#34;robots&amp;#34; content=&amp;#34;noindex,nofollow&amp;#34; /&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;&#9;&amp;lt;title&amp;gt;WordPress &amp;amp;rsaquo; Installation&amp;lt;/title&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;&#9;&amp;lt;link rel=&amp;#39;stylesheet&amp;#39; id=&amp;#39;dashicons-css&amp;#39; href=&amp;#39;http://localhost/wp-includes/css/dashicons.min.css?ver=6.3.1&amp;#39; type=&amp;#39;text/css&amp;#39; media=&amp;#39;all&amp;#39; /&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;&amp;lt;link rel=&amp;#39;stylesheet&amp;#39; id=&amp;#39;buttons-css&amp;#39; href=&amp;#39;http://localhost/wp-includes/css/buttons.min.css?ver=6.3.1&amp;#39; type=&amp;#39;text/css&amp;#39; media=&amp;#39;all&amp;#39; /&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;&amp;lt;link rel=&amp;#39;stylesheet&amp;#39; id=&amp;#39;forms-css&amp;#39; href=&amp;#39;http://localhost/wp-admin/css/forms.min.css?ver=6.3.1&amp;#39; type=&amp;#39;text/css&amp;#39; media=&amp;#39;all&amp;#39; /&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;&amp;lt;link rel=&amp;#39;stylesheet&amp;#39; id=&amp;#39;l10n-css&amp;#39; href=&amp;#39;http://localhost/wp-admin/css/l10n.min.css?ver=6.3.1&amp;#39; type=&amp;#39;text/css&amp;#39; media=&amp;#39;all&amp;#39; /&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;&amp;lt;link rel=&amp;#39;stylesheet&amp;#39; id=&amp;#39;install-css&amp;#39; href=&amp;#39;http://localhost/wp-admin/css/install.min.css?ver=6.3.1&amp;#39; type=&amp;#39;text/css&amp;#39; media=&amp;#39;all&amp;#39; /&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;&amp;lt;/head&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;&amp;lt;body class=&amp;#34;wp-core-ui language-chooser&amp;#34;&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;&amp;lt;p id=&amp;#34;logo&amp;#34;&amp;gt;WordPress&amp;lt;/p&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Awesome! 🎉&lt;/p&gt;&#10;&lt;h1 id="managing-containers"&gt;Managing containers&lt;/h1&gt;&#10;&lt;p&gt;Containers will automatically update on a schedule and you can check the timer:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;[core@vultr ~]$&lt;/span&gt; systemctl status --user podman-auto-update.timer&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;● podman-auto-update.timer - Podman auto-update timer&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; Loaded: loaded (/usr/lib/systemd/user/podman-auto-update.timer; enabled; preset: disabled)&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; Active: active (waiting) since Mon 2023-09-25 21:41:31 UTC; 3min 14s ago&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; Trigger: Tue 2023-09-26 00:04:46 UTC; 2h 20min left&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; Triggers: ● podman-auto-update.service&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Sep 25 21:41:31 vultr.guest systemd[1786]: Started podman-auto-update.timer - Podman auto-update timer.&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Quadlets are just regular systemd units:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;[core@vultr ~]$&lt;/span&gt; systemctl list-units --user &lt;span class="p"&gt;|&lt;/span&gt; grep -i Quadlet&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; caddy.service loaded active running Caddy Quadlet&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; mariadb.service loaded active running MariaDB Quadlet&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; wordpress.service loaded active running Wordpress Quadlet&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;As an example, you can make changes to caddy&amp;rsquo;s config file and restart it easily:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;[core@vultr ~]$&lt;/span&gt; systemctl restart --user caddy&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;[core@vultr ~]$&lt;/span&gt; systemctl status --user caddy&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;● caddy.service - Caddy Quadlet&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; Loaded: loaded (/var/home/core/.config/containers/systemd/caddy.container; generated)&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; Drop-In: /usr/lib/systemd/user/service.d&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; └─10-timeout-abort.conf&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; Active: active (running) since Mon 2023-09-25 21:46:28 UTC; 5s ago&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; Main PID: 2652 (conmon)&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; Tasks: 18 (limit: 1023)&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; Memory: 15.1M&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; CPU: 207ms&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;If you need to change a quadlet&amp;rsquo;s configuration, just open up the configuration file in your favorite editor under &lt;code&gt;~/.config/containers/systemd&lt;/code&gt;, reload systemd, and restart the container:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; vi ~/.config/containers/systemd/caddy.container&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;--- make your edits and save the quadlet configuration ---&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; systemctl daemon-reload --user&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; systemctl restart --user caddy&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Enjoy!&lt;/p&gt;&#10;</description></item><item><title>Fixing a ghost database migration failure</title><link>https://major.io/p/ghost-db-migration-failure/</link><pubDate>Thu, 31 Aug 2023 00:00:00 +0000</pubDate><author>major@mhtx.net (Major Hayden)</author><guid>https://major.io/p/ghost-db-migration-failure/</guid><description>&lt;p&gt;I love learning about the &lt;em&gt;behind the scenes&lt;/em&gt; aspects of just about everything.&#10;I do &lt;a href="https://w5wut.com"&gt;ham radio&lt;/a&gt;, I self-host lots of my personal infrastructure, and I&amp;rsquo;ve been learning more about the math behind the stock market for the last year or two.&lt;/p&gt;&#10;&lt;p&gt;That led me to start a blog on &lt;a href="https://unsplash.com/photos/l6mLi-iKUW0"&gt;Ghost&lt;/a&gt; to share my findings with others.&#10;I started &lt;a href="https://thetanerd.com"&gt;Theta Nerd&lt;/a&gt;&lt;sup id="fnref:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt; earlier this summer.&lt;/p&gt;&#10;&lt;p&gt;My deployment looked great when I started!&#10;Everything was automatically updated with &lt;a href="https://major.io/p/watchtower/"&gt;watchtower&lt;/a&gt; and running with &lt;a href="https://major.io/p/docker-compose-on-coreos/"&gt;docker-compose on Fedora CoreOS&lt;/a&gt;. &lt;em&gt;(Click these links to read the posts on both topics!)&lt;/em&gt;&lt;/p&gt;&#10;&lt;p&gt;However, I woke up one morning to my monitoring going off and my site was down. 😱&lt;/p&gt;&#10;&lt;h1 id="why-is-the-site-down"&gt;Why is the site down?&lt;/h1&gt;&#10;&lt;p&gt;Anyone who has worked in IT knows this sinking feeling.&#10;Something is down, you don&amp;rsquo;t know why, and you suspect the worst possible scenarios.&lt;/p&gt;&#10;&lt;p&gt;The instance hosting the blog was online and responsive, so I started digging into the logs with &lt;code&gt;docker-compose logs&lt;/code&gt;.&#10;I suddenly found a wall of text in the logs for the Ghost container:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;[2023-08-03 11:10:16] INFO Adding members.email_disabled column&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;[2023-08-03 11:10:16] INFO Setting email_disabled to true for all members that have their email on the suppression list&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;[2023-08-03 11:10:16] INFO Setting nullable: stripe_products.product_id&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;[2023-08-03 11:10:16] INFO Adding table: donation_payment_events&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;[2023-08-03 11:10:16] INFO Rolling back: alter table `donation_payment_events` add constraint `donation_payment_events_member_id_foreign` foreign key (`member_id`) references `members` (`id`) on delete SET NULL - Referencing column &amp;#39;member_id&amp;#39; and referenced column &amp;#39;id&amp;#39; in foreign key constraint &amp;#39;donation_payment_events_member_id_foreign&amp;#39; are incompatible..&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;[2023-08-03 11:10:16] INFO Dropping table: donation_payment_events&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;[2023-08-03 11:10:16] INFO Dropping nullable: stripe_products.product_id with foreign keys disabled&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;[2023-08-03 11:10:16] INFO Setting email_disabled to false for all members&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;[2023-08-03 11:10:16] INFO Removing members.email_disabled column&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;[2023-08-03 11:10:16] INFO Rollback was successful.&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;[2023-08-03 11:10:16] ERROR alter table `donation_payment_events` add constraint `donation_payment_events_member_id_foreign` foreign key (`member_id`) references `members` (`id`) on delete SET NULL - Referencing column &amp;#39;member_id&amp;#39; and referenced column &amp;#39;id&amp;#39; in foreign key constraint &amp;#39;donation_payment_events_member_id_foreign&amp;#39; are incompatible.&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;alter table `donation_payment_events` add constraint `donation_payment_events_member_id_foreign` foreign key (`member_id`) references `members` (`id`) on delete SET NULL - Referencing column &amp;#39;member_id&amp;#39; and referenced column &amp;#39;id&amp;#39; in foreign key constraint &amp;#39;donation_payment_events_member_id_foreign&amp;#39; are incompatible.&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;{&amp;#34;config&amp;#34;:{&amp;#34;transaction&amp;#34;:false},&amp;#34;name&amp;#34;:&amp;#34;2023-07-27-11-47-49-create-donation-events.js&amp;#34;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&amp;#34;Error occurred while executing the following migration: 2023-07-27-11-47-49-create-donation-events.js&amp;#34;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Error ID:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 300&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Error Code: &#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ER_FK_INCOMPATIBLE_COLUMNS&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;----------------------------------------&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Error: alter table `donation_payment_events` add constraint `donation_payment_events_member_id_foreign` foreign key (`member_id`) references `members` (`id`) on delete SET NULL - Referencing column &amp;#39;member_id&amp;#39; and referenced column &amp;#39;id&amp;#39; in foreign key constraint &amp;#39;donation_payment_events_member_id_foreign&amp;#39; are incompatible.&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; at /var/lib/ghost/versions/5.57.2/node_modules/knex-migrator/lib/index.js:1032:19&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; at Packet.asError (/var/lib/ghost/versions/5.57.2/node_modules/mysql2/lib/packets/packet.js:728:17)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; at Query.execute (/var/lib/ghost/versions/5.57.2/node_modules/mysql2/lib/commands/command.js:29:26)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; at Connection.handlePacket (/var/lib/ghost/versions/5.57.2/node_modules/mysql2/lib/connection.js:478:34)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; at PacketParser.onPacket (/var/lib/ghost/versions/5.57.2/node_modules/mysql2/lib/connection.js:97:12)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; at PacketParser.executeStart (/var/lib/ghost/versions/5.57.2/node_modules/mysql2/lib/packet_parser.js:75:16)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; at Socket.&amp;lt;anonymous&amp;gt; (/var/lib/ghost/versions/5.57.2/node_modules/mysql2/lib/connection.js:104:25)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; at Socket.emit (node:events:513:28)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; at addChunk (node:internal/streams/readable:315:12)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; at readableAddChunk (node:internal/streams/readable:289:9)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; at Socket.Readable.push (node:internal/streams/readable:228:10)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; at TCP.onStreamRead (node:internal/stream_base_commons:190:23)&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Ah, so a failed database migration in the upgrade to 5.57.2 is the culprit! 👏&lt;/p&gt;&#10;&lt;p&gt;I brought the site back online quickly by changing the container version for Ghost back to the previous version (5.55.2).&lt;/p&gt;&#10;&lt;h1 id="why-did-the-database-migration-fail"&gt;Why did the database migration fail?&lt;/h1&gt;&#10;&lt;p&gt;The error message from above boils down to this:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Error: alter table `donation_payment_events` add constraint &#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;`donation_payment_events_member_id_foreign` foreign key (`member_id`)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;references `members` (`id`) on delete SET NULL - Referencing column&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&amp;#39;member_id&amp;#39; and referenced column &amp;#39;id&amp;#39; in foreign key constraint &#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&amp;#39;donation_payment_events_member_id_foreign&amp;#39; are incompatible.&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Adjusting the &lt;code&gt;donation_payment_events.member_id&lt;/code&gt; column to be a foreign key of &lt;code&gt;members.id&lt;/code&gt; is failing because they are incompatible types.&#10;However, as I examined both tables, both were regular &lt;code&gt;varchar(24)&lt;/code&gt; columns without anything special attached to them:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;mysql&amp;gt; describe members;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;+------------------------------+---------------+------+-----+---------+-------+&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;| Field | Type | Null | Key | Default | Extra |&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;+------------------------------+---------------+------+-----+---------+-------+&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;| id | varchar(24) | NO | PRI | NULL | |&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;| uuid | varchar(36) | YES | UNI | NULL | |&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;| email | varchar(191) | NO | UNI | NULL | |&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;| status | varchar(50) | NO | | free | |&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;| name | varchar(191) | YES | | NULL | |&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;| expertise | varchar(191) | YES | | NULL | |&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;| note | varchar(2000) | YES | | NULL | |&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;| geolocation | varchar(2000) | YES | | NULL | |&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;| enable_comment_notifications | tinyint(1) | NO | | 1 | |&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;| email_count | int unsigned | NO | | 0 | |&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;| email_opened_count | int unsigned | NO | | 0 | |&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;| email_open_rate | int unsigned | YES | MUL | NULL | |&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;| last_seen_at | datetime | YES | | NULL | |&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;| last_commented_at | datetime | YES | | NULL | |&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;| created_at | datetime | NO | | NULL | |&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;| created_by | varchar(24) | NO | | NULL | |&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;| updated_at | datetime | YES | | NULL | |&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;| updated_by | varchar(24) | YES | | NULL | |&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;+------------------------------+---------------+------+-----+---------+-------+&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;18 rows in set (0.00 sec)&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h1 id="going-upstream"&gt;Going upstream&lt;/h1&gt;&#10;&lt;p&gt;I went to Ghost&amp;rsquo;s GitHub repository and &lt;a href="https://github.com/TryGhost/Ghost/issues/17584"&gt;opened an issue&lt;/a&gt; with as much data as I can find.&lt;/p&gt;&#10;&lt;p&gt;One of the &lt;a href="https://github.com/TryGhost/Ghost/issues/17584#issuecomment-1671134556"&gt;first replies&lt;/a&gt; mentioned something about database collations.&#10;Long story short, collations describe how databases handle sorting and comparing data for different languages.&#10;Comparing some languages to other languages can be particularly challenging and this can lead to problems.&lt;/p&gt;&#10;&lt;p&gt;I made a switch from MariaDB to MySQL recently for the blog.&#10;Could that be related?&lt;/p&gt;&#10;&lt;h1 id="more-searching"&gt;More searching&lt;/h1&gt;&#10;&lt;p&gt;I figured that I wasn&amp;rsquo;t the first one to stumble into this problem, and sure enough &amp;ndash; I wasn&amp;rsquo;t!&#10;There&amp;rsquo;s a &lt;a href="https://dnsmichi.at/2022/06/01/ghost-v5-upgrade-with-mysql-8-collation-migration-in-docker-compose/"&gt;great blog post&lt;/a&gt; about a broken migration from MySQL 5 to 8 with Ghost.&lt;/p&gt;&#10;&lt;p&gt;In short, it required several steps to fix it:&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Stop the Ghost container&lt;/li&gt;&#10;&lt;li&gt;Back up the database first (always a good idea)&lt;/li&gt;&#10;&lt;li&gt;Do a quick find/replace on the dumped database to change the collations&lt;/li&gt;&#10;&lt;li&gt;Drop the &lt;code&gt;ghost&lt;/code&gt; database from the database 😱&lt;/li&gt;&#10;&lt;li&gt;Import the database back into MySQL&lt;/li&gt;&#10;&lt;li&gt;Start Ghost again&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p&gt;Dropping databases always makes me pause, but that&amp;rsquo;s what backups are for! 😉&lt;/p&gt;&#10;&lt;h1 id="how-i-fixed-it"&gt;How I fixed it&lt;/h1&gt;&#10;&lt;p&gt;In my case, my MySQL container is called &lt;code&gt;ghostmysql&lt;/code&gt; and my Ghost database is &lt;code&gt;ghostdb&lt;/code&gt;.&#10;Then I made a backup of the database using &lt;code&gt;mysqldump&lt;/code&gt;:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;sudo docker-compose exec ghostmysql mysqldump \&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; -u root -psuper-secret-password ghostdb &amp;gt; backup-ghost-db.sql&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Next, I copied the SQL file to another directory &lt;em&gt;just in case&lt;/em&gt; I accidentally deleted this backup with an errant command.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;cp backup-ghost-db.sql ../&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Then I made a copy of the SQL file in the current directory and ran the find and replace on that copy.&#10;This changes the collations from the wrong one, &lt;code&gt;utf8mb4_general_ci&lt;/code&gt;, to the right one, &lt;code&gt;utf8mb4_0900_ai_ci&lt;/code&gt;&lt;sup id="fnref:2"&gt;&lt;a href="#fn:2" class="footnote-ref" role="doc-noteref"&gt;2&lt;/a&gt;&lt;/sup&gt;:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;cp backup-ghost-db.sql backup-ghost-db-new.sql&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;sed -i &amp;#39;s/utf8mb4_general_ci/utf8mb4_0900_ai_ci/g&amp;#39; \&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; backup-ghost-db-new.sql&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now I have the collations right for importing the database back into MySQL.&#10;But first, I have to drop the existing database.&#10;&lt;strong&gt;This is a good time to double check your backups!&lt;/strong&gt;&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;sudo docker-compose exec ghostmysql mysql -u root \&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; -psuper-secret-password&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;mysql&amp;gt; DROP DATABASE ghostdb;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now we can import the modified backup:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;cat backup-ghost-db-new.sql | sudo docker-compose exec -T \&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; ghostmysql mysql -u root -psuper-secret-password ghostdb&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Start all the containers:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;sudo docker-compose up -d&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Ghost was back online with the older version and everything looked good!&#10;I updated my &lt;code&gt;docker-compose.yaml&lt;/code&gt; back to use &lt;code&gt;latest&lt;/code&gt; for the Ghost version and ran &lt;code&gt;sudo docker-compose up -d&lt;/code&gt; once more.&lt;/p&gt;&#10;&lt;p&gt;Within seconds, the new container image was in place and the container was running!&#10;Both migrations completed in seconds and the blog was back online with the newest version. 🎉&lt;/p&gt;&#10;&lt;div class="footnotes" role="doc-endnotes"&gt;&#10;&lt;hr&gt;&#10;&lt;ol&gt;&#10;&lt;li id="fn:1"&gt;&#10;&lt;p&gt;Theta is one of many &lt;a href="https://en.wikipedia.org/wiki/Greeks_(finance)"&gt;financial Greeks&lt;/a&gt; that measure certain aspects of options contracts in the market.&#10;It&amp;rsquo;s also a &lt;a href="https://en.wikipedia.org/wiki/Theta"&gt;letter in the Greek alphabet&lt;/a&gt;.&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li id="fn:2"&gt;&#10;&lt;p&gt;The default collation in MySQL 8 is &lt;code&gt;utf8mb4_0900_ai_ci&lt;/code&gt;.&amp;#160;&lt;a href="#fnref:2" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;/div&gt;&#10;</description></item><item><title>Deploy a containerized Ghost blog 👻</title><link>https://major.io/p/deploy-ghost/</link><pubDate>Tue, 27 Jun 2023 00:00:00 +0000</pubDate><author>major@mhtx.net (Major Hayden)</author><guid>https://major.io/p/deploy-ghost/</guid><description>&lt;p&gt;There&amp;rsquo;s no shortage of options for starting a self-hosted blog.&#10;Wordpress might be chosen most often, but I stumbled upon &lt;a href="https://ghost.org/"&gt;Ghost&lt;/a&gt; recently and their &lt;a href="https://ghost.org/vs/wordpress/"&gt;performance numbers&lt;/a&gt; really got my attention.&lt;/p&gt;&#10;&lt;p&gt;I prefer deploying most things in containers these days with &lt;a href="https://fedoraproject.org/coreos/"&gt;Fedora CoreOS&lt;/a&gt;.&#10;Luckily, the Ghost stack doesn&amp;rsquo;t demand a lot of infrastructure:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Ghost itself&lt;/li&gt;&#10;&lt;li&gt;MySQL 8+ &lt;em&gt;(I went with MariaDB 11.x)&lt;/em&gt;&lt;/li&gt;&#10;&lt;li&gt;A web server out front&lt;/li&gt;&#10;&lt;li&gt;TLS certificate&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;[!NOTE]&#10;Although I chose MariaDB for the database here, &lt;strong&gt;Ghost recommends MySQL&lt;/strong&gt; and will throw a warning in the admin panel if you&amp;rsquo;re using something else.&#10;I haven&amp;rsquo;t had any issues so far, but &lt;strong&gt;you&amp;rsquo;ve been warned&lt;/strong&gt;. 💣&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;I picked Caddy for the webserver since it&amp;rsquo;s so small and the configuration is tremendously simple.&lt;/p&gt;&#10;&lt;h1 id="launch-coreos"&gt;Launch CoreOS&lt;/h1&gt;&#10;&lt;p&gt;Fedora CoreOS offers lots of &lt;a href="https://fedoraproject.org/coreos/download/?stream=stable"&gt;cloud options&lt;/a&gt; for launching it immediately.&#10;Many public clouds already have CoreOS images available, but I love Hetzner&amp;rsquo;s US locations and I already had a CoreOS image loaded up in my account.&lt;/p&gt;&#10;&lt;p&gt;🇩🇪 Want CoreOS at Hetzner?&#10;There&amp;rsquo;s a &lt;a href="https://major.io/p/deploy-fedora-coreos-in-hetzner-cloud/"&gt;blog post&lt;/a&gt; for that!&lt;/p&gt;&#10;&lt;p&gt;Once your CoreOS instance is running, connect to the instance over ssh and ensure the &lt;code&gt;docker.service&lt;/code&gt; starts on each boot:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sudo systemctl &lt;span class="nb"&gt;enable&lt;/span&gt; --now docker.service&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This ensures that containers come up on each reboot.&#10;CoreOS has a podman socket that listens for docker-compatible connections, but that doesn&amp;rsquo;t help with reboots.&lt;/p&gt;&#10;&lt;p&gt;Perhaps I&amp;rsquo;m old fashioned, but I still enjoy using &lt;a href="https://github.com/docker/compose"&gt;docker-compose&lt;/a&gt; for container management.&#10;I like how I can declare what I want and let &lt;code&gt;docker-compose&lt;/code&gt; sort out the rest.&lt;/p&gt;&#10;&lt;p&gt;Let&amp;rsquo;s install &lt;code&gt;docker-compose&lt;/code&gt; on the CoreOS instance now:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Check the latest version in the GitHub repo before starting!&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# https://github.com/docker/compose&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;curl -LO https://github.com/docker/compose/releases/download/v2.19.0/docker-compose-linux-x86_64&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Install docker-compose and make it executable.&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sudo mv docker-compose-linux-x86_64 /usr/local/bin/docker-compose&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sudo chown +x /usr/local/bin/docker-compose&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Verify that &lt;code&gt;docker-compose&lt;/code&gt; is ready to go:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; docker-compose --version&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Docker Compose version v2.19.0&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h1 id="preparing-caddy"&gt;Preparing Caddy&lt;/h1&gt;&#10;&lt;p&gt;Caddy uses a configuration file called a &lt;em&gt;Caddyfile&lt;/em&gt; and we need that in place before we deploy the other containers.&#10;Within my home directory, I created a directory called &lt;em&gt;caddy&lt;/em&gt;:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;mkdir caddy&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Then I added the &lt;em&gt;Caddyfile&lt;/em&gt; inside the directory:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-caddy" data-lang="caddy"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="c1"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt; # Your email for LetsEncrypt warnings/notices.&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;email&lt;/span&gt; &lt;span class="s"&gt;youremail@domain.com&lt;/span&gt;&lt;span class="c1"&gt; &#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt; # Staging LetsEncrypt server to use while testing.&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt; # Uncomment this before going to production!&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;acme_ca&lt;/span&gt; &lt;span class="s"&gt;https://acme-staging-v02.api.letsencrypt.org/directory&lt;/span&gt;&#10;&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 class="c1"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Basic virtual host definition to feed traffic into the&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Ghost container when it arrives.&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gh"&gt;example.com&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;reverse_proxy&lt;/span&gt; &lt;span class="n"&gt;ghost&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;2368&lt;/span&gt;&#10;&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 class="c1"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# OPTIONAL: Redirect traffic to &amp;#39;www&amp;#39; to the bare domain.&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gh"&gt;www.example.com&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;redir&lt;/span&gt; &lt;span class="s"&gt;https://example.com&lt;/span&gt;&lt;span class="se"&gt;{uri}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This configuration sets up LetsEncrypt certificates automatically from the staging server for now.&#10;Once we know our configuration is working well, we can comment out the &lt;code&gt;acme_ca&lt;/code&gt; line above and get production TLS certificates.&lt;/p&gt;&#10;&lt;p&gt;At this point, you need a DNS record pointed to your server so you can get a certificate.&#10;You have some options:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;&lt;strong&gt;If the site is entirely new,&lt;/strong&gt; just point the root domain name to your CoreOS instance.&#10;Use that domain in the configuration above and later in the deployment.&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;&lt;strong&gt;If you&amp;rsquo;re migrating from an existing site,&lt;/strong&gt; choose a subdomain off your main domain to use.&#10;If your website is &lt;em&gt;example.com&lt;/em&gt;, use something like &lt;em&gt;test.example.com&lt;/em&gt; or &lt;em&gt;new.example.com&lt;/em&gt; to get Ghost up and running.&#10;It&amp;rsquo;s really easy to change this later.&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;Now we&amp;rsquo;re ready for the rest of the deployment.&lt;/p&gt;&#10;&lt;h1 id="deploying-containers"&gt;Deploying containers&lt;/h1&gt;&#10;&lt;p&gt;Here&amp;rsquo;s the &lt;code&gt;docker-compose.yml&lt;/code&gt; file I&amp;rsquo;m using:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nn"&gt;---&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;3.8&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;services&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# OPTIONAL&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# Watchtower monitors all running containers and updates&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# them when the upstream container repo is updated.&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;watchtower&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;docker.io/containrrr/watchtower:latest&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;container_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;watchtower&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;restart&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;unless-stopped&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;hostname&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;coreos-ghost-deployment&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;WATCHTOWER_CLEANUP=true&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;WATCHTOWER_POLL_INTERVAL=3600&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;command&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- --&lt;span class="l"&gt;cleanup&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;volumes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;/var/run/docker.sock:/var/run/docker.sock&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;privileged&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# Caddy acts as our external-facing webserver and handles&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# getting TLS certs from LetsEncrypt.&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;caddy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;caddy:latest&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;container_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;caddy&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;depends_on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;ghost&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;ports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="m"&gt;80&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="m"&gt;80&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="m"&gt;443&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="m"&gt;443&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;restart&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;unless-stopped&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;volumes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;./caddy/Caddyfile:/etc/caddy/Caddyfile:Z&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;ghost:/var/www/html&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;caddy_data:/data&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;caddy_config:/config&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# The Ghost blog software itself&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;ghost&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;docker.io/library/ghost:5&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;container_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;ghost&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;restart&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;always&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;depends_on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;ghostdb&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;https://example.com&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;database__client&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;mysql&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;database__connection__host&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;ghostdb&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;database__connection__user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;ghost&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;database__connection__password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;GHOST_PASSWORD_FOR_MARIADB&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;database__connection__database&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;ghostdb&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;volumes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;ghost:/var/lib/ghost/content&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# Our MariaDB database&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;ghostdb&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;docker.io/library/mariadb:11&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;container_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;ghostdb&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;restart&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;always&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;MYSQL_ROOT_PASSWORD&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;A_SECURE_ROOT_PASSWORD&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;MYSQL_USER&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;ghost&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;MYSQL_PASSWORD&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;GHOST_PASSWORD_FOR_MARIADB&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;MYSQL_DATABASE&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;ghostdb&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;volumes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;ghostdb:/var/lib/mysql&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;volumes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;caddy_config&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;caddy_data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;ghost&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;ghostdb&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;I love &lt;a href="https://major.io/p/watchtower/"&gt;watchtower&lt;/a&gt; but that step is completely optional.&#10;It does require some elevated privileges to talk to the podman socket, so &lt;strong&gt;keep that in mind if you choose to use it.&lt;/strong&gt;&lt;/p&gt;&#10;&lt;p&gt;Our &lt;code&gt;ghostdb&lt;/code&gt; container starts first, followed by &lt;code&gt;ghost&lt;/code&gt;, and then &lt;code&gt;caddy&lt;/code&gt;.&#10;That follows the &lt;code&gt;depends_on&lt;/code&gt; configuration keys shown above.&lt;/p&gt;&#10;&lt;p&gt;There are two steps to take now:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Replace &lt;code&gt;GHOST_PASSWORD_FOR_MARIADB&lt;/code&gt; and &lt;code&gt;A_SECURE_ROOT_PASSWORD&lt;/code&gt; above with better passwords. 😉&lt;/li&gt;&#10;&lt;li&gt;Also, set the &lt;code&gt;url&lt;/code&gt; parameter for the &lt;code&gt;ghost&lt;/code&gt; container to your blog&amp;rsquo;s domain name.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;Once all of that is done, let&amp;rsquo;s let &lt;code&gt;docker-compose&lt;/code&gt; do the heavy lifting:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;sudo docker-compose up -d&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Let&amp;rsquo;s verify that our containers are running:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; sudo docker-compose ps&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;NAME IMAGE COMMAND SERVICE &#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;caddy caddy:latest &amp;#34;caddy run --config …&amp;#34; caddy &#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;ghost docker.io/library/ghost:5 &amp;#34;docker-entrypoint.s…&amp;#34; ghost &#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;ghostdb docker.io/library/mariadb:11 &amp;#34;docker-entrypoint.s…&amp;#34; ghostdb &#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;watchtower docker.io/containrrr/watchtower:latest &amp;#34;/watchtower --clean…&amp;#34; watchtower &#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Awesome! 👏&lt;/p&gt;&#10;&lt;h1 id="ghost-initial-setup"&gt;Ghost initial setup&lt;/h1&gt;&#10;&lt;p&gt;With all of your containers running, browse to &lt;code&gt;https://example.com/ghost/&lt;/code&gt;&#10;Just add &lt;code&gt;/ghost/&lt;/code&gt; to the end of your domain name to reach the admin panel.&#10;Create your admin account there with a good password.&lt;/p&gt;&#10;&lt;p&gt;If everything looks good, run back to your &lt;em&gt;Caddyfile&lt;/em&gt; and comment out the &lt;code&gt;acme_ca&lt;/code&gt; line:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-caddy" data-lang="caddy"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="c1"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt; # Your email for LetsEncrypt warnings/notices.&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;email&lt;/span&gt; &lt;span class="s"&gt;youremail@domain.com&lt;/span&gt;&lt;span class="c1"&gt; &#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt; # Staging LetsEncrypt server to use while testing.&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt; # Uncomment this before going to production!&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt; # acme_ca https://acme-staging-v02.api.letsencrypt.org/directory&#10;&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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Restart the caddy container to get a production LetsEncrypt certificate on the site:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;sudo docker-compose restart caddy&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h1 id="customizing-ghost"&gt;Customizing Ghost&lt;/h1&gt;&#10;&lt;p&gt;Ghost &lt;a href="https://ghost.org/docs/config/#custom-configuration-files"&gt;looks for lots of environment variables&lt;/a&gt; to determine its configuration and you can set these in your &lt;code&gt;docker-compose.yml&lt;/code&gt; file.&#10;Although some configuration items are easy, like &lt;code&gt;url&lt;/code&gt;, some are nested and get more complicated.&#10;For these, you can use double underscores &lt;code&gt;__&lt;/code&gt; to handle the nesting.&lt;/p&gt;&#10;&lt;p&gt;As an example, we already used &lt;code&gt;database__connection__host&lt;/code&gt; in the &lt;code&gt;docker-compose.yaml&lt;/code&gt;, and that&amp;rsquo;s the equivalent to this nested configuration:&lt;/p&gt;&#10;&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="s2"&gt;&amp;#34;database&amp;#34;&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;connection&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;host&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;...&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;If you&amp;rsquo;re deploying in containers, it&amp;rsquo;s a good idea to configure Ghost via environment variables.&#10;This ensures that your &lt;code&gt;docker-compose.yml&lt;/code&gt; is authoritative for the Ghost deployment.&#10;You &lt;em&gt;can&lt;/em&gt; &lt;code&gt;exec&lt;/code&gt; into the container, adjust the config file on disk, and restart Ghost, but then you have to remember where you configured each item. 🥵&lt;/p&gt;&#10;&lt;h1 id="switching-to-production-domain"&gt;Switching to production domain&lt;/h1&gt;&#10;&lt;p&gt;If you used a temporary domain to get everything configured and you&amp;rsquo;re ready to use your production domain, follow these steps:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Open your &lt;em&gt;Caddyfile&lt;/em&gt; and replace all instances of the testing domain with the production domain&lt;/li&gt;&#10;&lt;li&gt;Restart caddy: &lt;code&gt;sudo docker-compose restart caddy&lt;/code&gt;&lt;/li&gt;&#10;&lt;li&gt;Edit the &lt;code&gt;docker-compose.yml&lt;/code&gt; and change the &lt;code&gt;url&lt;/code&gt; key in the &lt;code&gt;ghost&lt;/code&gt; container to the production domain&lt;/li&gt;&#10;&lt;li&gt;Apply the configuration with &lt;code&gt;sudo docker-compose up -d&lt;/code&gt;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;Enjoy your new automatically-updating Ghost blog deployment! 👻&lt;/p&gt;&#10;</description></item><item><title>Launch a watchtower container via podman quadlets</title><link>https://major.io/p/podman-quadlet-watchtower/</link><pubDate>Wed, 31 May 2023 00:00:00 +0000</pubDate><author>major@mhtx.net (Major Hayden)</author><guid>https://major.io/p/podman-quadlet-watchtower/</guid><description>&lt;p&gt;Most of my container workloads run on independent CoreOS cloud instances that I &lt;a href="https://major.io/p/coreos-as-pet/"&gt;treat like pets&lt;/a&gt;.&#10;Keeping containers update remains a constant battle, but it&amp;rsquo;s still easier than running kubernetes.&lt;/p&gt;&#10;&lt;p&gt;I wrote about &lt;a href="https://major.io/p/watchtower/"&gt;using watchtower&lt;/a&gt; in the past to keep containers updated.&#10;It&amp;rsquo;s a simple container that does a few important things:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;It monitors (via docker/podman socket) the running containers on the host&lt;/li&gt;&#10;&lt;li&gt;It tracks the versions/tags of each container image&lt;/li&gt;&#10;&lt;li&gt;It looks for updated versions of the container image in their upstream repositories&lt;/li&gt;&#10;&lt;li&gt;Based on a configurable schedule, it pulls a new container image and restarts the container for updates&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;I encourage you to &lt;a href="https://github.com/containrrr/watchtower"&gt;read more about watchtower on GitHub&lt;/a&gt;.&#10;There&amp;rsquo;s plenty you can configure, including update intervals, how updates are handled, and how you can get notifications when an update happens.&lt;/p&gt;&#10;&lt;p&gt;My new deployments always need watchtower running.&#10;Luckily, we can combine Fedora CoreOS&amp;rsquo; initial provisioning system, called &lt;a href="https://coreos.github.io/ignition/"&gt;ignition&lt;/a&gt;, with podman&amp;rsquo;s new &lt;a href="https://www.redhat.com/sysadmin/quadlet-podman"&gt;quadlet&lt;/a&gt; feature and launch watchtower automatically on the first boot.&lt;/p&gt;&#10;&lt;h1 id="quadlets"&gt;Quadlets&lt;/h1&gt;&#10;&lt;p&gt;So what&amp;rsquo;s a quadlet?&lt;/p&gt;&#10;&lt;p&gt;The &lt;a href="https://www.redhat.com/sysadmin/quadlet-podman"&gt;blog post&lt;/a&gt; explains it well by making containers more declarative via a familiar systemd syntax.&#10;Here&amp;rsquo;s an example &lt;code&gt;.container&lt;/code&gt; file from the post:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-ini" data-lang="ini"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;[Unit]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt;Description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;The sleep container&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt;After&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;local-fs.target&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;[Container]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt;Image&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;registry.access.redhat.com/ubi9-minimal:latest&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt;Exec&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;sleep 1000&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;[Install]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Start by default on boot&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt;WantedBy&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;multi-user.target default.target&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;You can toss this into &lt;code&gt;$HOME/.config/containers/systemd/mysleep.container&lt;/code&gt; for rootless user containers or in &lt;code&gt;/etc/containers/systemd/mysleep.container&lt;/code&gt; for a container running as root.&lt;/p&gt;&#10;&lt;h1 id="configure-a-quadlet-on-boot"&gt;Configure a quadlet on boot&lt;/h1&gt;&#10;&lt;p&gt;As I mentioned earlier, I want a watchtower container running on my CoreOS nodes at first boot.&#10;Let&amp;rsquo;s start with a fairly basic &lt;a href="https://coreos.github.io/butane/"&gt;butane&lt;/a&gt; file:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;variant&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;fcos&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1.4.0&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;passwd&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;users&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;major&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;groups&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;wheel&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;sudo&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;ssh_authorized_keys&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDyoH6gU4lgEiSiwihyD0Rxk/o5xYIfA3stVDgOGM9N0&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;files&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;/etc/containers/systemd/watchtower.container&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;contents&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;inline&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="sd"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; [Unit]&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; Description=Watchtower container updater&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; Wants=network-online.target&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; After=network-online.target&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; [Container]&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; ContainerName=watchtower&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; Image=ghcr.io/containrrr/watchtower:1.5.3@sha256:a924a9aaef50016b7e69c7f618c7eb81ba02f06711558af57da0f494a76e7aca&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; Environment=WATCHTOWER_CLEANUP=true&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; Environment=WATCHTOWER_POLL_INTERVAL=3600&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; Volume=/var/run/docker.sock:/var/run/docker.sock&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; SecurityLabelDisable=true&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; [Install]&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; WantedBy=multi-user.target default.target&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Let&amp;rsquo;s break this file down:&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;I start by adding a user named &lt;code&gt;major&lt;/code&gt; that has administrative privileges an an ssh key&#10;&lt;em&gt;(this is optional, but I like using my own username rather than &lt;code&gt;core&lt;/code&gt;)&lt;/em&gt;&lt;/li&gt;&#10;&lt;li&gt;The quadlet unit file lands in &lt;code&gt;/etc/containers/systemd/watchtower.container&lt;/code&gt; and starts at boot time&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p&gt;The quadlet file has some important configurations:&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;I added environment variables to clean up outdated container images and check for updates once an hour&lt;/li&gt;&#10;&lt;li&gt;The podman socket is mounted inside the watchtower container&lt;/li&gt;&#10;&lt;li&gt;Security labels are disabled to allow for communication with the podman socket&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;[!NOTE]&#10;&lt;strong&gt;Mounting the podman socket and disabling security labels is not an ideal security approach.&lt;/strong&gt;&#10;However, I&amp;rsquo;ve found that watchtower&amp;rsquo;s configuration and automation fits my needs really well and I retreive the image from a trusted source.&#10;If this won&amp;rsquo;t work for you, you can use &lt;a href="https://docs.podman.io/en/latest/markdown/podman-auto-update.1.html"&gt;podman&amp;rsquo;s built-in auto-update&lt;/a&gt; feature instead.&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;From here, we convert the butane configuration into an ignition configuration.&#10;I&amp;rsquo;m launching this CoreOS node on &lt;a href="https://www.vultr.com/?ref=6941438"&gt;VULTR&lt;/a&gt;, so I&amp;rsquo;ve named my files accordingly:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; butane vultr-coreos.butane &amp;gt; vultr-coreos.ign&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h1 id="lets-go-"&gt;Let&amp;rsquo;s go 🚀&lt;/h1&gt;&#10;&lt;p&gt;I&amp;rsquo;m using VULTR&amp;rsquo;s CLI here in Fedora, but you can do the same steps via VULTR&amp;rsquo;s portal if needed.&#10;Just paste in the ignition configuration into the large text box before launch.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Install vultr-cli in Fedora&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sudo dnf install vultr-cli&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Launch the instance&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;vultr-cli instance create --region dfw --plan vhp-2c-2gb-amd &lt;span class="se"&gt;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --os &lt;span class="m"&gt;391&lt;/span&gt; --label coreos-dfw-1 --host coreos-dfw-1 &lt;span class="se"&gt;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --userdata &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;cat vultr-coreos.ign&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Let&amp;rsquo;s see how the container is doing:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; ssh major@COREOS_HOST&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Fedora CoreOS 38.20230430.3.1&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Tracker: https://github.com/coreos/fedora-coreos-tracker&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Discuss: https://discussion.fedoraproject.org/tag/coreos&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;[major@coreos-dfw-1 ~]$&lt;/span&gt; sudo podman ps&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;a0024712c95d ghcr.io/containrrr/watchtower@sha256:a924a9aaef50016b7e69c7f618c7eb81ba02f06711558af57da0f494a76e7aca About a minute ago Up About a minute watchtower&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;[major@coreos-dfw-1 ~]$&lt;/span&gt; sudo podman logs watchtower&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;time=&amp;#34;2023-05-31T14:01:12Z&amp;#34; level=info msg=&amp;#34;Watchtower 1.5.3&amp;#34;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;time=&amp;#34;2023-05-31T14:01:12Z&amp;#34; level=info msg=&amp;#34;Using no notifications&amp;#34;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;time=&amp;#34;2023-05-31T14:01:12Z&amp;#34; level=info msg=&amp;#34;Checking all containers (except explicitly disabled with label)&amp;#34;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;time=&amp;#34;2023-05-31T14:01:12Z&amp;#34; level=info msg=&amp;#34;Scheduling first run: 2023-05-31 15:01:12 +0000 UTC&amp;#34;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;time=&amp;#34;2023-05-31T14:01:12Z&amp;#34; level=info msg=&amp;#34;Note that the first check will be performed in 59 minutes, 59 seconds&amp;#34;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;Awesome!&lt;/strong&gt; 🥳&lt;/p&gt;&#10;&lt;p&gt;My system rebooted for an ostree update shortly after provisioning and the container came up automatically both times.&lt;/p&gt;&#10;</description></item><item><title>CoreOS as a pet</title><link>https://major.io/p/coreos-as-pet/</link><pubDate>Thu, 25 May 2023 00:00:00 +0000</pubDate><author>major@mhtx.net (Major Hayden)</author><guid>https://major.io/p/coreos-as-pet/</guid><description>&lt;p&gt;Anyone working with containers has likely heard of &lt;a href="https://fedoraproject.org/coreos/"&gt;CoreOS&lt;/a&gt; by this point&#10;Haven&amp;rsquo;t heard about it?&#10;Don&amp;rsquo;t despair.&#10;I&amp;rsquo;ll catch you up on what you missed.&lt;/p&gt;&#10;&lt;p&gt;Fedora CoreOS offers a really fast pathway to running containers on hardware, in virtual machines, or in clouds.&#10;It delivers a lightweight operating system with all of the container technology that you need for running simple containers or launching a kubernetes deployment.&lt;/p&gt;&#10;&lt;p&gt;But that&amp;rsquo;s not the best part.&lt;/p&gt;&#10;&lt;p&gt;CoreOS really shines due to its &lt;strong&gt;immutable OS layer&lt;/strong&gt;&lt;sup id="fnref:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt;.&#10;The OS underneath your containers ships as a single unit and it automatically updates itself much like your mobile phone.&#10;An update rolls down, CoreOS sets it up as a secondary OS, reboots into that new update, and rolls back to the original one if there were any issues.&lt;/p&gt;&#10;&lt;p&gt;Many people use CoreOS as the workhorse underneath kubernetes.&#10;Red Hat uses it underneath &lt;a href="https://docs.openshift.com/container-platform/4.13/installing/installing_bare_metal/installing-bare-metal.html#creating-machines-bare-metal_installing-bare-metal"&gt;OpenShift&lt;/a&gt; as well.&#10;It&amp;rsquo;s even supported by the super light weight kubernetes distribution &lt;a href="https://k3s.io/"&gt;k3s&lt;/a&gt;.&lt;/p&gt;&#10;&lt;p&gt;But can you use CoreOS as a &lt;em&gt;pet&lt;/em&gt; type instance that you use and maintain for long periods of time just like any other server?&#10;&lt;strong&gt;Absolutely!&lt;/strong&gt;&lt;/p&gt;&#10;&lt;h1 id="whats-this-pet-stuff-about"&gt;What&amp;rsquo;s this &lt;em&gt;pet&lt;/em&gt; stuff about?&lt;/h1&gt;&#10;&lt;p&gt;Whether you like it or not, there&amp;rsquo;s a cattle versus pets paradigm that took hold in the world of IT at some point.&#10;The basic ideas are these:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;When you take care of cattle, you take care of them as a group.&#10;Losing one or more of them would make you sad, but you know you have many others.&lt;/li&gt;&#10;&lt;li&gt;As for pets, you spend a lot of time taking care of them and playing with them.&#10;If you lost one, it would be devastating.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;A fleet of web servers could be treated like cattle.&#10;Keep lots of them online and replace any instances that have issues.&lt;/p&gt;&#10;&lt;p&gt;On the other hand, databases or tier zero systems (everyone feels if it they went down) are like pets.&#10;You carefully build, maintain, and monitor these.&lt;/p&gt;&#10;&lt;h1 id="how-does-coreos-fit-in"&gt;How does CoreOS fit in?&lt;/h1&gt;&#10;&lt;p&gt;Many people do use CoreOS as a container hosting platform as part of a bigger system.&#10;It works really well for that.&#10;But it&amp;rsquo;s great as a regular cloud server, too.&lt;/p&gt;&#10;&lt;p&gt;You can run a single node CoreOS deployment and manage containers via the tools that you know and love.&#10;For example, &lt;a href="https://github.com/docker/compose"&gt;docker-compose&lt;/a&gt; works great on CoreOS.&#10;I even used it to host my own &lt;a href="https://major.io/p/self-hosted-mastodon-second-try/"&gt;Mastodon deployment&lt;/a&gt;.&lt;/p&gt;&#10;&lt;p&gt;You can also load up more user-friendly tools such as &lt;a href="https://www.portainer.io/"&gt;portainer&lt;/a&gt; to manage containers in a browser.&lt;/p&gt;&#10;&lt;h1 id="my-development-tools-are-missing"&gt;My development tools are missing!&lt;/h1&gt;&#10;&lt;p&gt;😱 No &lt;code&gt;vim&lt;/code&gt;?&#10;This is too minimal!&#10;&lt;strong&gt;What are we going to do?&lt;/strong&gt;&lt;/p&gt;&#10;&lt;p&gt;Luckily CoreOS comes with &lt;a href="https://github.com/containers/toolbox"&gt;toolbox&lt;/a&gt;. 🧰&lt;/p&gt;&#10;&lt;p&gt;Toolbox gives you the ability to run a utility container on the system with some handy benefits:&lt;/p&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;Toolbox environments have seamless access to the user&amp;rsquo;s home directory, the Wayland and X11 sockets, networking (including Avahi), removable devices (like USB sticks), systemd journal, SSH agent, D-Bus, ulimits, /dev and the udev database, etc..&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;This means that the toolbox feels like a second OS on the system and it has all of the elevated privileges that you need to do your work.&#10;Simply run &lt;code&gt;toolbox enter&lt;/code&gt;, follow the prompts, and you&amp;rsquo;ll end up with a Fedora toolbox that matches your CoreOS version.&#10;Need a different version, such as Fedora Rawhide?&#10;Just specify the Fedora release you want on the prompt:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; toolbox enter --release &lt;span class="m"&gt;39&lt;/span&gt; &#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;No toolbox containers found. Create now? [y/N] y&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Image required to create toolbox container.&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Download registry.fedoraproject.org/fedora-toolbox:39 (500MB)? [y/N]: y&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Welcome to the Toolbox; a container where you can install and run&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;all your tools.&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; - Use DNF in the usual manner to install command line tools.&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; - To create a new tools container, run &amp;#39;toolbox create&amp;#39;.&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;For more information, see the documentation.&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;⬢[major@toolbox ~]$ &#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Look at the &lt;code&gt;toolbox create --help&lt;/code&gt; output to see how to create lots of different toolbox containers with different names and releases.&#10;If you go overboard and need to delete some toolboxes, just list your toolboxes with &lt;code&gt;toolbox list&lt;/code&gt; and follow it up with &lt;code&gt;toolbox rm&lt;/code&gt;.&lt;/p&gt;&#10;&lt;h1 id="my-tool-wont-work-in-the-toolbox"&gt;My tool won&amp;rsquo;t work in the toolbox.&lt;/h1&gt;&#10;&lt;p&gt;Some applications have issues running inside a container, even one that has elevated privileges on the system.&#10;CoreOS offers an option for layering packages on top of the underlying immutable OS.&lt;/p&gt;&#10;&lt;p&gt;Simply run &lt;code&gt;rpm-ostree install PACKAGE&lt;/code&gt; to layer a package on top of the OS.&#10;When &lt;code&gt;rpm-ostree&lt;/code&gt; runs, it creates a new layer and sets that layer to be active on the next boot.&#10;That means that you need to reboot before you can use the package.&lt;/p&gt;&#10;&lt;p&gt;Don&amp;rsquo;t want to reboot?&#10;There&amp;rsquo;s another option, but I recommend against it if you can avoid it&lt;sup id="fnref:2"&gt;&lt;a href="#fn:2" class="footnote-ref" role="doc-noteref"&gt;2&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;&#10;&lt;p&gt;You can apply a package layer &lt;em&gt;live&lt;/em&gt; on the system without a reboot with the &lt;code&gt;--apply-live&lt;/code&gt; flag.&#10;Installing a package like &lt;a href="https://github.com/traviscross/mtr"&gt;mtr&lt;/a&gt; would look like this:&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;$ sudo rpm-ostree install --apply-live mtr&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;As soon as &lt;code&gt;rpm-ostree&lt;/code&gt; finishes its work, &lt;code&gt;mtr&lt;/code&gt; should be available on the system for you to use.&lt;/p&gt;&#10;&lt;h1 id="how-do-updates-work"&gt;How do updates work?&lt;/h1&gt;&#10;&lt;p&gt;There are two main technologies at work here.&lt;/p&gt;&#10;&lt;p&gt;First, &lt;a href="https://github.com/coreos/zincati"&gt;zincati&lt;/a&gt; checks for updates to your immutable OS tree.&#10;It runs on a &lt;a href="https://docs.fedoraproject.org/en-US/fedora-coreos/auto-updates/#_os_update_finalization"&gt;configurable schedule&lt;/a&gt; that you can adjust based on your preferences.&lt;/p&gt;&#10;&lt;p&gt;Second, &lt;code&gt;rpm-ostree&lt;/code&gt; handles the OS layers and switches between them at boot time.&#10;If you&amp;rsquo;re running off layer A and an update comes down (layer B), that layer is written to the disk and activated on the next boot.&#10;Should there be any issues booting up layer B later, &lt;code&gt;rpm-ostree&lt;/code&gt; switches the system back to layer A.&#10;In these situations, your downtime might be extended a bit due to two reboots.&#10;Your system will come back up with the original OS layer activated.&lt;/p&gt;&#10;&lt;p&gt;You also get a choice of &lt;a href="https://docs.fedoraproject.org/en-US/fedora-coreos/update-streams/"&gt;update streams&lt;/a&gt;.&#10;Want to live a bit more on the edge?&#10;Go for &lt;em&gt;next&lt;/em&gt; or &lt;em&gt;testing&lt;/em&gt;.&#10;You&amp;rsquo;re on the &lt;em&gt;stable&lt;/em&gt; stream by default.&lt;/p&gt;&#10;&lt;p&gt;Although I haven&amp;rsquo;t landed in this situation, it&amp;rsquo;s possible that the system boots into a new update where you notice a problem that doesn&amp;rsquo;t affect the boot.&#10;You can &lt;a href="https://docs.fedoraproject.org/en-US/fedora-coreos/auto-updates/#_manual_rollbacks"&gt;manually roll back&lt;/a&gt; to fix it.&lt;/p&gt;&#10;&lt;h1 id="i-have-more-questions"&gt;I have more questions.&lt;/h1&gt;&#10;&lt;p&gt;Your first stop should be the &lt;a href="https://docs.fedoraproject.org/en-US/fedora-coreos/"&gt;Fedora CoreOS docs&lt;/a&gt;.&#10;There are also lots of ways to &lt;a href="https://docs.fedoraproject.org/en-US/fedora-coreos/getting-started/#_getting_in_touch"&gt;contact the development team and talk with the community&lt;/a&gt;.&lt;/p&gt;&#10;&lt;p&gt;Love the idea of an immutable OS but you wish you had it for your desktop or laptop?&#10;Go check out &lt;a href="https://fedoraproject.org/silverblue/"&gt;Fedora Silverblue&lt;/a&gt;. 💻&lt;/p&gt;&#10;&lt;div class="footnotes" role="doc-endnotes"&gt;&#10;&lt;hr&gt;&#10;&lt;ol&gt;&#10;&lt;li id="fn:1"&gt;&#10;&lt;p&gt;Okay, so it&amp;rsquo;s &lt;em&gt;mostly&lt;/em&gt; immutable.&#10;You can edit configuration in &lt;code&gt;/etc&lt;/code&gt; and you can layer more packages on top of the base OS layer if you need them.&#10;However, CoreOS maintainers discourage adding layered packages if you can avoid it.&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li id="fn:2"&gt;&#10;&lt;p&gt;When you apply some packages and make them available immediately, you may lose track of which ones were applied live and which ones are available on the next reboot.&#10;Things can get a bit confusing if you suddenly change your mind about applying a package live or not.&amp;#160;&lt;a href="#fnref:2" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;/div&gt;&#10;</description></item><item><title>Automatic container updates with watchtower</title><link>https://major.io/p/watchtower/</link><pubDate>Wed, 04 Jan 2023 00:00:00 +0000</pubDate><author>major@mhtx.net (Major Hayden)</author><guid>https://major.io/p/watchtower/</guid><description>&lt;p&gt;Keeping things updated quickly becomes a monotonous task.&#10;I&amp;rsquo;m surrounded by devices that demand updates on different frequencies.&#10;Phones, computers, tables, cloud instances, containers, and even my car need constant attention for updates that improve security or fix bugs.&#10;&lt;em&gt;(Sometimes the updates cause bugs, but let&amp;rsquo;s forget about those for now)&lt;/em&gt; 😉&lt;/p&gt;&#10;&lt;p&gt;My container infrastructure runs on &lt;a href="https://getfedora.org/en/coreos"&gt;Fedora CoreOS&lt;/a&gt; and it updates itself.&#10;It has an immutable layer underneath my containers that updates using &lt;a href="https://ostreedev.github.io/ostree/"&gt;ostree&lt;/a&gt;.&lt;/p&gt;&#10;&lt;p&gt;However, keeping containers updated is a constant battle.&#10;Updating the containers themselves is fairly easy with a &lt;code&gt;podman pull&lt;/code&gt; or &lt;code&gt;docker pull&lt;/code&gt; followed by a stop and start.&#10;It&amp;rsquo;s a bit easier with &lt;code&gt;docker-compose&lt;/code&gt;, but it&amp;rsquo;s still a nuisance to remember to update.&lt;/p&gt;&#10;&lt;h1 id="enter-watchtower"&gt;Enter watchtower&lt;/h1&gt;&#10;&lt;p&gt;&lt;a href="https://containrrr.dev/watchtower/"&gt;Watchtower&lt;/a&gt; is incredibly handy and surprisingly simple to operate.&#10;At a high level, it reads the container tags from each running container and watches the upstream repositories for updates.&#10;When an updated container appears, watchtower springs into action, pulls the new container, and replaces the old container with the new one.&lt;/p&gt;&#10;&lt;p&gt;It accepts &lt;a href="https://containrrr.dev/watchtower/arguments/"&gt;arguments&lt;/a&gt; to configure all kinds of aspects of updating containers.&#10;You can exclude certain containers from updates, choose your update interval, and send notifications when updates occur.&lt;/p&gt;&#10;&lt;p&gt;Also, if manually updating containers is something you find fascinating, watchtower can notify you about updates.&#10;Then you get that great feeling of running lots of commands on your own.&#10;&lt;em&gt;(Wait, surely nobody likes that!)&lt;/em&gt;&lt;/p&gt;&#10;&lt;h1 id="deploy"&gt;Deploy&lt;/h1&gt;&#10;&lt;p&gt;There are plenty of configuration snippets in watchtower&amp;rsquo;s documentation, but you can start off with something as simple with this in your &lt;code&gt;docker-compose.yaml&lt;/code&gt;:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;services&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;watchtower&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;ghcr.io/containrrr/watchtower&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;volumes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;/var/run/docker.sock:/var/run/docker.sock&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;restart&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;always&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;privileged&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;My configuration on my &lt;a href="https://major.io/p/self-hosted-mastodon-second-try/"&gt;mastodon deployment&lt;/a&gt; looks something like this:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;services&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;watchtower&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;container_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;watchtower&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;ghcr.io/containrrr/watchtower&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;hostname&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;watchtower.tootchute.com&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;volumes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;/var/run/docker.sock:/var/run/docker.sock&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;WATCHTOWER_CLEANUP=true&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;WATCHTOWER_POLL_INTERVAL=86400&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;WATCHTOWER_NOTIFICATIONS=shoutrrr&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;WATCHTOWER_NOTIFICATION_URL=discord://DISCORD_WEBHOOK_KEY@DISCORD_CHANNEL_ID&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;restart&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;always&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;privileged&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This configuration sends me notifications via my Discord server whenever watchtower starts or when a container is updated.&lt;/p&gt;&#10;&lt;p&gt;The &lt;code&gt;docker.sock&lt;/code&gt; volume mount allows watchtower to interact with the container daemon underneath watchtower.&#10;This could easily be done with &lt;code&gt;docker&lt;/code&gt;, &lt;code&gt;moby-engine&lt;/code&gt;, or &lt;code&gt;podman&lt;/code&gt;.&lt;/p&gt;&#10;&lt;p&gt;I&amp;rsquo;d like to remove the &lt;code&gt;privileged&lt;/code&gt; setting at some point soon but I haven&amp;rsquo;t figured out a way to allow watchtower to talk to the &lt;code&gt;docker.sock&lt;/code&gt; without it. 🤔&lt;/p&gt;&#10;&lt;h1 id="further-considerations"&gt;Further considerations&lt;/h1&gt;&#10;&lt;p&gt;If you don&amp;rsquo;t trust the upstream where you download your containers, be careful using watchtower.&#10;A malicious container could be uploaded to a particular container image repository and your system might update itself to the malicious container before the malicious container is found.&#10;&lt;em&gt;Then again, if you don&amp;rsquo;t trust the upstream where you download your containers, you should be building these containers yourself.&lt;/em&gt; 😉&lt;/p&gt;&#10;&lt;p&gt;For more complex services that might need some extra care around updates, such as database services, you may want to exclude them from automatic updates.&#10;You can run &lt;a href="https://containrrr.dev/watchtower/running-multiple-instances/"&gt;multiple instances&lt;/a&gt; of watchtower with customized configurations for different sets of containers.&lt;/p&gt;&#10;</description></item><item><title>Second try at self-hosting Mastodon</title><link>https://major.io/p/self-hosted-mastodon-second-try/</link><pubDate>Mon, 02 Jan 2023 00:00:00 +0000</pubDate><author>major@mhtx.net (Major Hayden)</author><guid>https://major.io/p/self-hosted-mastodon-second-try/</guid><description>&lt;p&gt;&lt;a href="https://joinmastodon.org/"&gt;Mastodon&lt;/a&gt; caught my attention at the end of 2022 in the wake of all the Twitter shenanigans.&#10;At a high level, Mastodon is an implementation of &lt;a href="https://en.wikipedia.org/wiki/ActivityPub"&gt;ActivityPub&lt;/a&gt; and you can use it for &amp;ldquo;micro-blogging&amp;rdquo; much like you would use Twitter.&#10;&lt;em&gt;(This is a really quick, high-level explanation and I skipped over plenty of detail.)&lt;/em&gt; 😉&lt;/p&gt;&#10;&lt;p&gt;This post covers my journey on Mastodon that led me to self-host my own Mastodon instance in a fairly reliable way.&lt;/p&gt;&#10;&lt;h1 id="early-start"&gt;Early start&lt;/h1&gt;&#10;&lt;p&gt;My early Mastodon adventure started out much like the story of &lt;a href="https://en.wikipedia.org/wiki/Goldilocks_and_the_Three_Bears"&gt;Goldilocks&lt;/a&gt;:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;I started out on &lt;a href="https://mastodon.social/explore"&gt;mastodon.social&lt;/a&gt;, but it was &lt;em&gt;too big&lt;/em&gt;.&#10;There were so many people on the server that the federated timeline was flying by.&#10;Rules seemed to be enforced well, but it was a bit like Twitter all over again.&lt;/li&gt;&#10;&lt;li&gt;I deployed my own, but it was &lt;em&gt;too small&lt;/em&gt; (the federated timeline was empty).&#10;Finding new people to talk to or following new topics was difficult.&lt;/li&gt;&#10;&lt;li&gt;Finally, I discovered &lt;a href="https://fosstodon.org/"&gt;Fosstodon&lt;/a&gt; after several friends in the open source community joined.&#10;It felt &lt;em&gt;just right&lt;/em&gt;.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;The admins of the Fosstodon instance are fantastic.&#10;Sure, there was downtime as the usage levels increased, but the admin team was quick to communicate the issues at hand along with future plans.&#10;My interactions with the community were almost all positive and it was fun to reconnect with some open source contributors that I had not spoken to in ages.&lt;/p&gt;&#10;&lt;p&gt;As time went on, I read various toots&lt;sup id="fnref:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt; about Mastodon servers changing owners, suddenly going offline, or altering rules abruptly.&#10;Someone talked about taking control of your online identity and that Mastodon should be included in that.&lt;/p&gt;&#10;&lt;p&gt;This aligned with my existing approach to hosting blogs on my own domains.&#10;Also, after the Twitter fiasco, I&amp;rsquo;d like people to find me via the systems where I have full control, such as my blog.&lt;/p&gt;&#10;&lt;h1 id="self-hosted-adventure"&gt;Self-hosted adventure&lt;/h1&gt;&#10;&lt;p&gt;So far, there are three main deployment methods for Mastodon that I&amp;rsquo;ve found:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;The &lt;a href="https://docs.joinmastodon.org/user/run-your-own/"&gt;official guide&lt;/a&gt; uses a custom Ruby, lots of steps, and systemd&lt;/li&gt;&#10;&lt;li&gt;Using &lt;a href="https://github.com/mastodon/mastodon/blob/main/docker-compose.yml"&gt;docker-compose&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;Deploying in kubernetes using &lt;a href="https://github.com/mastodon/chart"&gt;Mastodon&amp;rsquo;s charts&lt;/a&gt; or the &lt;a href="https://github.com/bitnami/charts/tree/main/bitnami/mastodon"&gt;ones from Bitnami&lt;/a&gt;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="official-guide"&gt;Official guide&lt;/h2&gt;&#10;&lt;p&gt;Although the official guide looks fairly straightforward, it has a lot of steps.&#10;I struggled to get the right Ruby version compiled on Fedora 37 and I found spots where I needed to tweak the guide to make things work.&#10;Also, I wasn&amp;rsquo;t sure if I could get the steps done the same way again if I needed to migrate the instance or recover from a failure.&lt;/p&gt;&#10;&lt;h2 id="docker-compose"&gt;docker-compose&lt;/h2&gt;&#10;&lt;p&gt;Next up was docker-compose.&#10;I use docker-compose &lt;a href="https://major.io/tags/docker/"&gt;quite often&lt;/a&gt; and I know my way around many of the rough edges.&#10;However, I couldn&amp;rsquo;t get the upstream compose file to work properly.&#10;Sometimes the database migrations would not run.&#10;Sometimes certain pieces of the Mastodon infrastructure couldn&amp;rsquo;t find each other.&#10;As soon as I tried to set passwords for postgres and redis, I couldn&amp;rsquo;t get Mastodon&amp;rsquo;s rails app to work again.&lt;/p&gt;&#10;&lt;p&gt;In addition, the docker-compose file from upstream builds containers on your local machine rather than pulling the official containers that were built and tested upstream.&#10;That&amp;rsquo;s a quick fix in the compose file, but I still had issues during the deployment.&lt;/p&gt;&#10;&lt;h2 id="kubernetes"&gt;kubernetes&lt;/h2&gt;&#10;&lt;p&gt;Finally, I looked at kubernetes.&#10;Surely you can just add kubernetes to something and make it better, right? 😆&lt;/p&gt;&#10;&lt;p&gt;The Bitnami charts made it much further along than the charts from upstream, but I still had errors flowing about database migrations cut off during their run and occasionally unreachable postgres servers.&lt;/p&gt;&#10;&lt;p&gt;There must be a better way.&lt;/p&gt;&#10;&lt;h1 id="deployment"&gt;Deployment&lt;/h1&gt;&#10;&lt;p&gt;For this Mastodon deployment to work well, I needed a few things:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;The deployment should be &lt;em&gt;mostly&lt;/em&gt; hands off. Said another way, moving it to another server or re-deploying should be a &lt;code&gt;docker-compose up -d&lt;/code&gt; plus one or two commands &lt;strong&gt;maximum&lt;/strong&gt;.&lt;/li&gt;&#10;&lt;li&gt;It should be relatively easy to back up and restore.&lt;/li&gt;&#10;&lt;li&gt;The big file of secret environment variables should be generated ahead of time and not at deploy time.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;After plenty of trial and error, I came up with this plan:&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Start with an empty secrets environment file. Deploy all of the containers and run the &lt;code&gt;rake db:setup&lt;/code&gt; to generate the environment file.&lt;/li&gt;&#10;&lt;li&gt;Copy the environment file to &lt;code&gt;.env.production&lt;/code&gt; so that it can be used along with upstream&amp;rsquo;s docker-compose file.&lt;/li&gt;&#10;&lt;li&gt;Delete the entire deployment. Remove all existing volumes and containers.&lt;/li&gt;&#10;&lt;li&gt;Add &lt;a href="https://caddyserver.com/"&gt;Caddy&lt;/a&gt; to the deployment to handle TLS and serving cached content.&lt;/li&gt;&#10;&lt;li&gt;Deploy again with &lt;code&gt;docker-compose up -d&lt;/code&gt; and run &lt;code&gt;rake db:setup&lt;/code&gt; to prepare the database with the environment file.&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p&gt;Without further ado, let&amp;rsquo;s get to the guide!&lt;/p&gt;&#10;&lt;h2 id="generate-the-environment-file"&gt;Generate the environment file&lt;/h2&gt;&#10;&lt;p&gt;Here&amp;rsquo;s my initial docker-compose file:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;3&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;services&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;postgres&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;restart&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;always&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;container_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;postgres&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;docker.io/library/postgres:14&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;networks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;internal_network&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;healthcheck&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;test&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;CMD&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;pg_isready&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;-U&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;postgres&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;volumes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;postgres:/var/lib/postgresql/data&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;POSTGRES_HOST_AUTH_METHOD=trust&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;POSTGRES_PASSWORD=my-super-secret-postgres-password&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;POSTGRES_USER=postgres&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;restart&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;always&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;container_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;redis&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;redis:7&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;networks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;internal_network&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;healthcheck&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;test&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;CMD&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;redis-cli&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;ping&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;volumes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;redis:/data&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;web&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;tootsuite/mastodon&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;container_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;web&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;restart&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;always&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;env_file&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;.env.production&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;command&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;bash -c &amp;#34;rm -f /mastodon/tmp/pids/server.pid; bundle exec rails s -p 3000&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;networks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;external_network&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;internal_network&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;healthcheck&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;test&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;CMD-SHELL&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;wget -q --spider --proxy=off localhost:3000/health || exit 1&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;ports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="s1"&gt;&amp;#39;127.0.0.1:3000:3000&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;depends_on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;postgres&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;redis&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# - es&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;volumes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;mastodon-public:/mastodon/public/system&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;streaming&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;tootsuite/mastodon&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;container_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;streaming&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;restart&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;always&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;env_file&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;.env.production&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;command&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;node ./streaming&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;networks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;external_network&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;internal_network&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;healthcheck&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;test&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;CMD-SHELL&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;wget -q --spider --proxy=off localhost:4000/api/v1/streaming/health || exit 1&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;ports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="s1"&gt;&amp;#39;127.0.0.1:4000:4000&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;depends_on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;postgres&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;redis&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;sidekiq&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;tootsuite/mastodon&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;container_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;sidekiq&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;restart&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;always&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;env_file&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;.env.production&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;command&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;bundle exec sidekiq -c 1&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;depends_on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;postgres&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;redis&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;networks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;external_network&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;internal_network&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;volumes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;mastodon-public:/mastodon/public/system&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;healthcheck&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;test&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;CMD-SHELL&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;ps aux | grep &amp;#39;[s]idekiq\ 6&amp;#39; || false&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;networks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;external_network&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;internal_network&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;internal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;volumes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;mastodon-public&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;{}&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;postgres&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;{}&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;{}&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;I&amp;rsquo;ve made a few alterations to the upstream compose file:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;I&amp;rsquo;m using the upstream containers from docker hub rather than building them on startup&lt;/li&gt;&#10;&lt;li&gt;My containers use docker volumes instead of mounting local directories&lt;/li&gt;&#10;&lt;li&gt;The sidekiq container only uses one worker (keeping resource usage low)&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;At this point, I can run &lt;code&gt;docker-compose up -d&lt;/code&gt; and all of the containers are running.&#10;Now we can use Mastodon&amp;rsquo;s interactive configuration tool to generate our environments file:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;docker-compose run --rm web bundle exec rake db:setup&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Go through the interactive configuration and answer all of the questions there.&lt;/p&gt;&#10;&lt;p&gt;For SMTP, I used &lt;a href="https://www.mailgun.com/"&gt;Mailgun&lt;/a&gt; since it&amp;rsquo;s very inexpensive for my Mastodon use case.&#10;Once you set up your account there, look for the SMTP credentials under your domain in Mailgun&amp;rsquo;s control panel.&#10;The Mastodon setup process will ask for those credentials.&lt;/p&gt;&#10;&lt;p&gt;Also, I keep all of my assets in &lt;a href="https://www.backblaze.com/b2/cloud-storage.html"&gt;Backblaze B2&lt;/a&gt; to avoid clogging up all of the storage on my VM that runs Mastodon.&#10;Create a public bucket in Backblaze and create some access keys.&#10;When Mastodon asks for your S3 endpoint, use &lt;code&gt;https://s3.us-west-001.backblazeb2.com&lt;/code&gt;.&#10;If it asks for a hostname, you can use &lt;code&gt;s3.us-west-001.backblazeb2.com&lt;/code&gt;.&lt;/p&gt;&#10;&lt;p&gt;Once the setup completes, take the environments file that prints to the screen and store that as &lt;code&gt;.env.production&lt;/code&gt;.&lt;/p&gt;&#10;&lt;h2 id="delete-the-deployment-for-real"&gt;Delete the deployment (for real)&lt;/h2&gt;&#10;&lt;p&gt;This is going to sound weird, but we need to throw everything away at this point.&#10;I like this step because it allows me to start fresh with a fully generated environments file.&#10;It&amp;rsquo;s a good simulation of how things might look in a brand new deployment or during a migration from one server to another.&lt;/p&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;[!NOTE]&#10;💣 &lt;strong&gt;WARNING!&lt;/strong&gt;&#10;This assumes that Mastodon&amp;rsquo;s containers are the only ones running on your system.&#10;If you are running other containers for other services, &lt;strong&gt;don&amp;rsquo;t run these commands&lt;/strong&gt;.&#10;You must go through each container, remove it, and remove the associated volume carefully.&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Stop all of the current containers and delete them (see warning above!)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ docker-compose rm -sfv&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Destroy all of the container volumes (see warning above!)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ docker system prune --volumes&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h1 id="add-caddy"&gt;Add Caddy&lt;/h1&gt;&#10;&lt;p&gt;For most container deployments, I&amp;rsquo;d use &lt;a href="https://traefik.io/"&gt;traefik&lt;/a&gt; here.&#10;Its configuration discovery abilities, especially when paired with docker-compose, are top-notch.&#10;There&amp;rsquo;s almost no little one-off configuration issues when you use traefik.&lt;/p&gt;&#10;&lt;p&gt;However, Mastodon has tons of static assets, such as images, stylesheets, and other media.&#10;Serving those through Mastodon&amp;rsquo;s rails web server is possible, but it&amp;rsquo;s horribly inefficient.&#10;It chews up much more CPU time and it&amp;rsquo;s slower to respond.&lt;/p&gt;&#10;&lt;p&gt;That&amp;rsquo;s where Caddy comes in.&#10;Caddy has automatic TLS capabilities with LetsEncrypt and it can also serve static content.&#10;This takes the load off of Mastodon&amp;rsquo;s rails web server.&lt;/p&gt;&#10;&lt;p&gt;Start by adding a new service to your compose file:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;caddy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;caddy:2-alpine&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;restart&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;unless-stopped&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;container_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;caddy&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;ports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="s2"&gt;&amp;#34;80:80&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="s2"&gt;&amp;#34;443:443&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;volumes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;./caddy/etc-caddy:/etc/caddy:Z&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;./caddy/logs:/logs:Z&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;mastodon-public:/srv/mastodon/public:ro&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;hostname&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;tootchute.com&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;networks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;internal_network&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;external_network&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Change the &lt;code&gt;hostname&lt;/code&gt; to fit your server.&#10;The &lt;code&gt;mastodon-public&lt;/code&gt; volume is the one that Mastodon uses for its public content and mounting it inside the Caddy container allows Caddy to serve those assets.&lt;/p&gt;&#10;&lt;p&gt;In my case, I created a &lt;code&gt;caddy&lt;/code&gt; directory in my home directory to hold the configuration and log files:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; mkdir caddy/&lt;span class="o"&gt;{&lt;/span&gt;etc-caddy,logs&lt;span class="o"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;🤓 &lt;strong&gt;NERD ALERT.&lt;/strong&gt;&#10;The &lt;code&gt;:Z&lt;/code&gt; on the volumes for configuration and logs ensures that these directories have the right SELinux contexts so that the container can access the files in these directories.&#10;If your system does not use SELinux, you can omit the &lt;code&gt;:Z&lt;/code&gt;.&lt;/p&gt;&#10;&lt;p&gt;I wrote a caddy configuration in &lt;code&gt;./caddy/etc-caddy/Caddyfile&lt;/code&gt; that is a slight tweak of &lt;a href="https://blog.riemann.cc/digitalisation/2022/02/09/mastodon-setup-with-docker-and-caddy/"&gt;Robert Riemann&amp;rsquo;s version&lt;/a&gt;:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;{&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; # Global options block. Entirely optional, https is on by default&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; # Optional email key for lets encrypt&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; email major@mhtx.net&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; # Optional staging lets encrypt for testing. Comment out for production.&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; # acme_ca https://acme-staging-v02.api.letsencrypt.org/directory&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; # admin off&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;tootchute.com {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; log {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; # format single_field common_log&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; output file /logs/access.log&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; root * /srv/mastodon/public&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; encode gzip&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; @static file&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; handle @static {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; file_server&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; handle /api/v1/streaming* {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; reverse_proxy streaming:4000&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; handle {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; reverse_proxy web:3000&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; #header {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; # Strict-Transport-Security &amp;#34;max-age=31536000;&amp;#34;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; #}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; header /sw.js Cache-Control &amp;#34;public, max-age=0&amp;#34;;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; header /emoji* Cache-Control &amp;#34;public, max-age=31536000, immutable&amp;#34;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; header /packs* Cache-Control &amp;#34;public, max-age=31536000, immutable&amp;#34;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; header /system/accounts/avatars* Cache-Control &amp;#34;public, max-age=31536000, immutable&amp;#34;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; header /system/media_attachments/files* Cache-Control &amp;#34;public, max-age=31536000, immutable&amp;#34;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; handle_errors {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; @5xx expression `{http.error.status_code} &amp;gt;= 500 &amp;amp;&amp;amp; {http.error.status_code} &amp;lt; 600`&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; rewrite @5xx /500.html&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; file_server&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Be sure to change &lt;code&gt;tootchute.com&lt;/code&gt; to your Mastodon server&amp;rsquo;s domain as well as &lt;code&gt;email&lt;/code&gt; to your email.&#10;In addition, you may want to uncomment the &lt;code&gt;acme_ca&lt;/code&gt; option shown there to avoid hitting LetsEncrypt&amp;rsquo;s production API limits while you are testing your deployment.&#10;&lt;em&gt;(Comment out the staging server later to ensure you get a valid, trusted certificate.)&lt;/em&gt;&lt;/p&gt;&#10;&lt;p&gt;Let&amp;rsquo;s bring up our new Caddy container!&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; docker-compose up -d&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="initialize-mastodon"&gt;Initialize Mastodon&lt;/h2&gt;&#10;&lt;p&gt;At this point, we have Caddy serving content and all of our Mastodon containers are running.&#10;However, the Mastodon database isn&amp;rsquo;t populated at all.&#10;Let&amp;rsquo;s do that now:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;docker-compose run --rm web bundle exec rake db:setup&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This step uses your environments file to run all of Mastodon&amp;rsquo;s database migrations and perform some initial setup steps.&#10;It might take about 30 seconds to run.&lt;/p&gt;&#10;&lt;p&gt;Create our first user once the setup process finishes:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; docker-compose run --rm web bin/tootctl accounts create USERNAME --email YOUR_EMAIL --confirmed --role Owner&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This command creates a new administrative user, sets the email address for that user, and confirms the account.&#10;The confirmation part allows you to skip the email confirmation process for that first account.&#10;Your initial password prints out as soon as the command finishes.&lt;/p&gt;&#10;&lt;p&gt;You should be able to access your Mastodon deployment on the domain you chose (mine is &lt;a href="https://tootchute.com"&gt;tootchute.com&lt;/a&gt; and log in as the user you just created.&#10;If something doesn&amp;rsquo;t look right, examine the container logs to see if it&amp;rsquo;s something obvious:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; docker-compose logs -f --since 5m&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;If a container is in a restart loop, you should catch it fairly quickly in the logs.&lt;/p&gt;&#10;&lt;h1 id="next-steps"&gt;Next steps&lt;/h1&gt;&#10;&lt;p&gt;First, turn off new registrations if you plan to run a single user instance like I do.&#10;Click the preferences gear/cog on the main page, click &lt;strong&gt;Administration&lt;/strong&gt;, *&lt;em&gt;Server Settings&lt;/em&gt;, and &lt;strong&gt;Registrations&lt;/strong&gt;.&lt;/p&gt;&#10;&lt;p&gt;Next, enable two-factor authentication for your account.&#10;Click the preferences gear/cog on the main page, click &lt;strong&gt;Account&lt;/strong&gt;, and then &lt;strong&gt;Two-factor Auth&lt;/strong&gt;.&lt;/p&gt;&#10;&lt;p&gt;Finally, back up your environments file (&lt;code&gt;.env.production&lt;/code&gt;) and your &lt;code&gt;docker-compose.yaml&lt;/code&gt;.&#10;This will make it much easier to recover from a failure or migrate to a new server.&lt;/p&gt;&#10;&lt;p&gt;If you&amp;rsquo;re using remote assets in S3 or Backblaze, you don&amp;rsquo;t need to back up that content.&#10;Focus on backing up postgres and redis on a regular basis:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;#&lt;/span&gt; Dump postgres data&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; docker-compose &lt;span class="nb"&gt;exec&lt;/span&gt; postgres pg_dump -d mastodon -U postgres --no-owner &amp;gt; backups/pgdump-&lt;span class="k"&gt;$(&lt;/span&gt;date +%F_%H-%M-%S&lt;span class="k"&gt;)&lt;/span&gt;.sql&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;#&lt;/span&gt; Copy redis data&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; docker-compose cp redis:/data/dump.rdb backups/&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;a href="https://tootchute.com/@major"&gt;Let me know&lt;/a&gt; if you run into problems with the steps described in this post.&#10;I assembled them from my shell history and some notes I took along the way.&#10;There&amp;rsquo;s always a chance I missed something.&lt;/p&gt;&#10;&lt;div class="footnotes" role="doc-endnotes"&gt;&#10;&lt;hr&gt;&#10;&lt;ol&gt;&#10;&lt;li id="fn:1"&gt;&#10;&lt;p&gt;Posts on Mastodon were called &amp;ldquo;toots&amp;rdquo; for ages since that&amp;rsquo;s the supposed sound that an elephant trunk makes.&#10;Many people want to switch that to &amp;ldquo;posts&amp;rdquo; and the latest version of Mastodon changed the &amp;ldquo;toot&amp;rdquo; button to &amp;ldquo;publish.&amp;rdquo;&#10;I&amp;rsquo;ll call them toots forever.&#10;Heck, I&amp;rsquo;m the owner of &lt;a href="https://tootchute.com"&gt;tootchute.com&lt;/a&gt;. 😉&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;/div&gt;&#10;</description></item><item><title>docker-compose on Fedora CoreOS</title><link>https://major.io/p/docker-compose-on-coreos/</link><pubDate>Sat, 17 Dec 2022 00:00:00 +0000</pubDate><author>major@mhtx.net (Major Hayden)</author><guid>https://major.io/p/docker-compose-on-coreos/</guid><description>&lt;p&gt;Deploying applications in containers provides lots of flexibility and compatibility benefits.&lt;/p&gt;&#10;&lt;p&gt;Once you package your application and its dependencies in a container, that container runs almost anywhere without issues.&#10;Very few of the old &amp;ldquo;it worked on my machine!&amp;rdquo; problems remain.&#10;However, the challenge of running a container and linking it up with other helpful pieces of software still remains.&lt;/p&gt;&#10;&lt;p&gt;Web applications need something to serve HTTP requests and handle TLS.&#10;They also need databases, and those databases must be online and available first.&#10;All of these need reliable storage that is easily managed.&lt;/p&gt;&#10;&lt;p&gt;In my personal infrastructure, I keep coming back to &lt;a href="https://github.com/docker/compose"&gt;docker-compose&lt;/a&gt;.&lt;/p&gt;&#10;&lt;p&gt;🐇 &lt;strong&gt;In a hurry?&lt;/strong&gt;&#10;Skip to the last section of this post if you want to skip my reasons for using docker-compose and you just want to see the steps.&lt;/p&gt;&#10;&lt;h1 id="why-docker-compose"&gt;Why docker-compose?&lt;/h1&gt;&#10;&lt;p&gt;The tried-and-true docker-compose is one of the original &amp;ldquo;set your desired state&amp;rdquo; systems for managing containers.&#10;You specify what your container deployment should look like and docker-compose finds a way to get your containers in order.&#10;Sometimes that&amp;rsquo;s a fresh start without any existing containers.&#10;Sometimes it involves managing an existing fleet of containers and adjusting their configuration.&lt;/p&gt;&#10;&lt;p&gt;As an example, deploying your first container with docker-compose is easy.&#10;Assemble a &lt;a href="https://compose-spec.io/"&gt;basic YAML file&lt;/a&gt;, point docker-compose at it, and your container is running!&lt;/p&gt;&#10;&lt;p&gt;Need to change the configuration?&#10;Just make your changes in the YAML file, re-run docker-compose, and it knows enough to make the right changes.&#10;If containers need to be restarted, it takes care of that.&lt;/p&gt;&#10;&lt;h2 id="why-not-kubernetes"&gt;Why not kubernetes?&lt;/h2&gt;&#10;&lt;p&gt;I&amp;rsquo;ve run my own kubernetes deployment and I&amp;rsquo;ve also been a consumer of large kubernetes and OpenShift deployments.&#10;All of this experience taught me two things:&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Kubernetes and OpenShift are great.&#10;Like really great.&#10;Once you learn them, they are incredibly powerful tools that make a developer&amp;rsquo;s life much easier.&lt;/li&gt;&#10;&lt;li&gt;Running my own kubernetes or OpenShift deployment on my own time (and own dime) is not enjoyable.&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p&gt;Deploying, maintaining, and troubleshooting kubernetes infrastructure on your own is time consuming.&#10;Shared storage and networking caused me the most headaches in the past.&lt;/p&gt;&#10;&lt;h2 id="what-about-k3s"&gt;What about k3s?&lt;/h2&gt;&#10;&lt;p&gt;I love &lt;a href="https://k3s.io/"&gt;k3s&lt;/a&gt;.&#10;However, that still means I have to figure out networking for inter-container communication and load balancing.&#10;Shared storage is also needed.&lt;/p&gt;&#10;&lt;p&gt;I could argue that running my own k3s deployment is easier than full kubernetes, but in the end, there&amp;rsquo;s more extra stuff around it that I don&amp;rsquo;t want to maintain.&lt;/p&gt;&#10;&lt;h2 id="why-dont-you-use-managed-kubernetes"&gt;Why don&amp;rsquo;t you use managed kubernetes?&lt;/h2&gt;&#10;&lt;p&gt;Great question!&#10;Several providers have some excellent kubernetes offerings out there.&#10;Smaller providers, such as &lt;a href="https://www.digitalocean.com/products/kubernetes"&gt;Digital Ocean&lt;/a&gt; and &lt;a href="https://www.vultr.com/kubernetes/"&gt;VULTR&lt;/a&gt;, have affordable offerings that are packed with features.&lt;/p&gt;&#10;&lt;p&gt;The challenge is that kubernetes deployments have overhead for the control plane, so you can&amp;rsquo;t use all of the virtual machines that you rent.&#10;For example, you may get three virtual machines in your cluster, each with 2GB RAM, but you can really only use about 1GB of RAM from each instance for your containers.&lt;/p&gt;&#10;&lt;p&gt;In addition, you will eventually need shared storage and some type of load balancer.&#10;The costs add up quickly.&lt;/p&gt;&#10;&lt;p&gt;It&amp;rsquo;s easy to start with a $50-$70 kubernetes offering and later find yourself cracking $100 per month after adding on storage and load balancers.&lt;/p&gt;&#10;&lt;h2 id="what-about-podman"&gt;What about podman?&lt;/h2&gt;&#10;&lt;p&gt;&lt;a href="https://podman.io/"&gt;Podman&lt;/a&gt; is a delight to use.&#10;You can toss some kubernetes YAML at it for deployments or pods and it will start them up for you.&#10;It also has a handy feature for exporting a container to a systemd unit file so you can manage it like any other systemd unit.&lt;/p&gt;&#10;&lt;p&gt;However, when I want to make quick adjustments to a container configuration, it can be frustrating to get that done.&#10;I also like to make adjustments to all of my containers in one place since some applications depend on multiple containers running in tandem.&lt;/p&gt;&#10;&lt;p&gt;The &lt;a href="https://github.com/containers/podman-compose"&gt;podman-compose&lt;/a&gt; project helps quite a bit, but it still lacks some of docker-compose&amp;rsquo;s features.&lt;/p&gt;&#10;&lt;p&gt;I use podman &lt;em&gt;constantly&lt;/em&gt; on my laptop and desktop for development, testing, and toolbox containers.&lt;/p&gt;&#10;&lt;h1 id="whats-the-big-deal-about-coreos"&gt;What&amp;rsquo;s the big deal about CoreOS?&lt;/h1&gt;&#10;&lt;p&gt;&lt;a href="https://getfedora.org/en/coreos"&gt;Fedora CoreOS&lt;/a&gt; provides the foundation for my container infrastructure.&#10;I love Fedora already, but here&amp;rsquo;s what makes CoreOS special to me:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Automatic updates arrive via &lt;a href="https://ostreedev.github.io/ostree/"&gt;ostree&lt;/a&gt; as a fully tested minimal unit&lt;/li&gt;&#10;&lt;li&gt;My system reboots automatically to apply the updates and it reverts back to the previous working update if the update fails&lt;/li&gt;&#10;&lt;li&gt;It comes with all of the container tools and configurations that I need&lt;/li&gt;&#10;&lt;li&gt;Whenever I need development or troubleshooting tools, &lt;a href="https://github.com/containers/toolbox"&gt;toolbox containers&lt;/a&gt; are one step away&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;It&amp;rsquo;s a constantly-updated OS that is designed for containers.&#10;What could be better than that?&lt;/p&gt;&#10;&lt;h1 id="adding-docker-compose-to-coreos"&gt;Adding docker-compose to CoreOS&lt;/h1&gt;&#10;&lt;p&gt;Deploy CoreOS in your favorite cloud or on your favorite piece of hardware (I have &lt;a href="https://major.io/2021/08/20/deploy-fedora-coreos-in-hetzner-cloud/"&gt;a post&lt;/a&gt; about deploying it on &lt;a href="https://www.hetzner.com/cloud"&gt;Hetzner Cloud&lt;/a&gt;).&#10;Login as the &lt;code&gt;core&lt;/code&gt; user via ssh.&lt;/p&gt;&#10;&lt;p&gt;Go to the &lt;a href="https://github.com/docker/compose/releases"&gt;releases page&lt;/a&gt; for docker-compose and get the latest release for your architecture.&#10;Move it into place once you download it:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; curl -sLO https://github.com/docker/compose/releases/download/v2.14.1/docker-compose-linux-x86_64&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; sudo mv docker-compose-linux-x86_64 /usr/local/bin/docker-compose&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; sudo chmod +x /usr/local/bin/docker-compose&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Let&amp;rsquo;s add a really basic deployment of traefik&amp;rsquo;s &lt;a href="https://hub.docker.com/r/traefik/whoami"&gt;whoami&lt;/a&gt; container.&#10;Start a new file called &lt;code&gt;docker-compose.yaml&lt;/code&gt;:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;services&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;whoami&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;container_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;whoami&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;docker.io/traefik/whoami&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;ports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="m"&gt;8080&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="m"&gt;80&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;restart&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;unless-stopped&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;If we try to bring up our containers now, we get an error:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; docker-compose up -d&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock: Get &amp;#34;http://%2Fvar%2Frun%2Fdocker.sock/v1.24/containers/json?all=1&amp;amp;filters=%7B%22label%22%3A%7B%22com.docker.compose.project%3Dcore%22%3Atrue%7D%7D&amp;#34;: dial unix /var/run/docker.sock: connect: permission denied&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The &lt;code&gt;core&lt;/code&gt; user is not in the &lt;code&gt;docker&lt;/code&gt; group and does not have permissions to talk to the docker socket.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; id&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;uid=1000(core) gid=1000(core) groups=1000(core),4(adm),10(wheel),16(sudo),190(systemd-journal) context=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Add the &lt;code&gt;docker&lt;/code&gt; group to the &lt;code&gt;core&lt;/code&gt; user as a supplementary group:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; sudo usermod -a -G docker core&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Log out of your ssh session and log in again to pick up the new group.&#10;Start the containers once more:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; id&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;uid=1000(core) gid=1000(core) groups=1000(core),4(adm),10(wheel),16(sudo),190(systemd-journal),982(docker) context=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; docker-compose up -d&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;[+] Running 4/4&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; ⠿ whoami Pulled&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; ⠿ 029cd1bf7e7c Pull complete&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; ⠿ e73b694ead4f Pull complete&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; ⠿ 99df6e9e9886 Pull complete&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;[+] Running 2/2&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; ⠿ Network core_default Created&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; ⠿ Container whoami Started&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Success!&lt;/p&gt;&#10;&lt;p&gt;🤔 &lt;strong&gt;BUT WAIT!&lt;/strong&gt;&#10;If we reboot our system, the containers won&amp;rsquo;t start up at boot time!&lt;/p&gt;&#10;&lt;p&gt;On CoreOS, there&amp;rsquo;s a &lt;code&gt;docker.socket&lt;/code&gt; file that starts the actual docker service (which is actually &lt;a href="https://mobyproject.org/"&gt;moby-engine&lt;/a&gt; on Fedora and not full-fledged docker).&#10;As soon as something touches the socket, systemd starts the docker service.&#10;That keeps it out of the way until something actually asks to use it.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; systemctl status docker.socket&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;● docker.socket - Docker Socket for the API&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; Loaded: loaded (/usr/lib/systemd/system/docker.socket; enabled; preset: enabled)&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; Active: active (running) since Sat 2022-12-17 22:48:42 UTC; 4min 47s ago&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; Until: Sat 2022-12-17 22:48:42 UTC; 4min 47s ago&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; Triggers: ● docker.service&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; Listen: /run/docker.sock (Stream)&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; Tasks: 0 (limit: 2207)&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; Memory: 0B&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; CPU: 596us&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; CGroup: /system.slice/docker.socket&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;After a reboot, nothing pokes the socket and the associated service never starts.&#10;You can try it yourself!&#10;Reboot and the whoami container will be down.&#10;Run &lt;code&gt;docker-compose ps&lt;/code&gt; one time and suddenly your containers are running!&lt;/p&gt;&#10;&lt;p&gt;Let&amp;rsquo;s fix this so containers come up on boot without any extra work (or socket poking):&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; sudo systemctl &lt;span class="nb"&gt;enable&lt;/span&gt; --now docker.service&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The socket will still be handled by systemd, but now the docker service itself will always start at boot whether someone touched it or not.&lt;/p&gt;&#10;&lt;p&gt;Enjoy your new lightweight container infrastructure. ✨&lt;/p&gt;&#10;</description></item><item><title>Adventures with the mastodon herd</title><link>https://major.io/p/adventures-with-the-mastodon-herd/</link><pubDate>Fri, 11 Nov 2022 00:00:00 +0000</pubDate><author>major@mhtx.net (Major Hayden)</author><guid>https://major.io/p/adventures-with-the-mastodon-herd/</guid><description>&lt;p&gt;All of the recent changes at Twitter inspired me to take a second look at &lt;a href="https://en.wikipedia.org/wiki/Mastodon_(software)"&gt;mastodon&lt;/a&gt;.&#10;In short, mastodon is a federated social network that feels a bit like someone took Twitter and split it up into a vast network of independent servers.&lt;/p&gt;&#10;&lt;h1 id="why-mastodon"&gt;Why mastodon?&lt;/h1&gt;&#10;&lt;p&gt;It feels a lot like Twitter, but better.&lt;/p&gt;&#10;&lt;p&gt;You can search for people, follow them, and publish messages (called &lt;em&gt;toots&lt;/em&gt;).&#10;They can also follow you and see the messages you publish.&lt;/p&gt;&#10;&lt;p&gt;The big difference is that you don&amp;rsquo;t join a central server with mastodon.&#10;There&amp;rsquo;s a massive network of servers to choose from and you can create accounts on one or more of those servers to get started.&#10;You can even run your own!&lt;/p&gt;&#10;&lt;p&gt;Mastodon reminds me of email for many reasons:&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;There&amp;rsquo;s no central server. You join a server (from the &lt;a href="https://joinmastodon.org/servers"&gt;massive, growing list&lt;/a&gt;) and&#10;start publishing messages.&lt;/li&gt;&#10;&lt;li&gt;Everything is on an eventual consistency model. If a mastodon server goes offline for&#10;a bit or has network issues, messages and other data will synchronize when it&amp;rsquo;s back&#10;online.&lt;/li&gt;&#10;&lt;li&gt;You can follow people on your server or on other servers. You choose who to mute or&#10;block and you can create lists that help you group certain contacts.&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p&gt;After joining the &lt;a href="https://fosstodon.org/"&gt;fosstodon.org&lt;/a&gt; server, I noticed that it was really easy to begin following people and get messages.&#10;I reconnected with people that I had not heard from in a very long time!&lt;/p&gt;&#10;&lt;h1 id="migrating-from-twitter"&gt;Migrating from Twitter&lt;/h1&gt;&#10;&lt;p&gt;One of my first questions after joining mastodon was: &amp;ldquo;How do I find the people I follow on Twitter?&amp;rdquo;&lt;/p&gt;&#10;&lt;p&gt;Many Twitter users are adding their mastodon accounts to their bio to make them easier to find on mastodon.&#10;For example, I added my mastodon account, &lt;a href="https://fosstodon.org/@major"&gt;@major@fosstodon.org&lt;/a&gt;, to my &lt;a href="https://twitter.com/majorhayden"&gt;twitter bio&lt;/a&gt;:&lt;/p&gt;&#10;&lt;p&gt;&lt;img src="twitter_bio.png" alt="twitter_bio.png" title="Twitter bio showing off my mastodon link"&gt;&lt;/p&gt;&#10;&lt;p&gt;Adding this to your bio makes it easier for people to find you via some helpful tools.&#10;I used &lt;a href="https://pruvisto.org/debirdify/"&gt;debirdify&lt;/a&gt; to look through my Twitter account for mastodon handles of the people I follow.&#10;Within seconds, it provided links to about 15 mastodon accounts and offered me a CSV that I could directly import into my mastodon server. 🎉&#10;&lt;em&gt;(Mastodon servers have some awesome import and export capabilities.)&lt;/em&gt;&lt;/p&gt;&#10;&lt;p&gt;I&amp;rsquo;ve also heard good things about &lt;a href="https://fedifinder.glitch.me/"&gt;Fedifinder&lt;/a&gt; and &lt;a href="https://twitodon.com/"&gt;Twitodon&lt;/a&gt; for helping you find Twitter friends on mastodon.&#10;There&amp;rsquo;s a &lt;a href="https://www.wired.com/story/how-to-find-twitter-friends-on-mastodon/"&gt;helpful article on Wired&lt;/a&gt; with more suggestions.&lt;/p&gt;&#10;&lt;h1 id="apps"&gt;Apps&lt;/h1&gt;&#10;&lt;p&gt;I&amp;rsquo;m neck-deep in the Android ecosystem, so most of my suggestions here are for Android devices.&#10;I tried the &lt;a href="https://play.google.com/store/apps/details?id=org.joinmastodon.android&amp;amp;gl=US"&gt;main mastodon app&lt;/a&gt; first.&#10;It looks great, updates quickly, and is very easy to use.&#10;However, inserting GIFs into toots became really frustrating (although I hear that&amp;rsquo;s being fixed).&lt;/p&gt;&#10;&lt;p&gt;I moved to &lt;a href="https://tusky.app/"&gt;Tusky&lt;/a&gt; and it&amp;rsquo;s my go-to mastodon app.&#10;You can add multiple accounts, posting media is incredibly easy, and it has tons of configuration knobs everywhere.&lt;/p&gt;&#10;&lt;p&gt;There are various desktop applications for mastodon, but the web interface is good enough for me!&#10;The default web interface looks a lot like Twitter with a big timeline running down the middle and section links on the right.&lt;/p&gt;&#10;&lt;p&gt;However, I use Tweetdeck with Twitter and I wanted something similar on mastodon.&#10;Go into the settings for the web application, and choose &lt;em&gt;Appearance&lt;/em&gt;.&#10;Click &lt;em&gt;Enable advanced web interface&lt;/em&gt;, save the changes, and click &lt;em&gt;Back to Mastodon&lt;/em&gt;.&#10;Enjoy your Tweetdeck-like multi-column interface! ✨&lt;/p&gt;&#10;&lt;h1 id="run-your-own-instance"&gt;Run your own instance&lt;/h1&gt;&#10;&lt;p&gt;The federated nature of mastodon means you can run your own single user instance if you want!&#10;Buy a domain you like (or use a subdomain off an existing domain) and deploy!&lt;/p&gt;&#10;&lt;p&gt;The upstream repository has a &lt;a href="https://github.com/mastodon/mastodon/tree/main/chart"&gt;helm chart&lt;/a&gt; which works well with kubernetes.&#10;Also, there&amp;rsquo;s a &lt;a href="https://github.com/mastodon/mastodon/blob/main/docker-compose.yml"&gt;docker-compose&lt;/a&gt; file which works well for smaller deployments.&lt;/p&gt;&#10;&lt;p&gt;I went the docker-compose route on a small cloud instance at Hetzner, but I modified the upstream &lt;code&gt;docker-compose.yml&lt;/code&gt;:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;3&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;volumes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;certs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;postgres&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;mastodon&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;services&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;traefik&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;docker.io/library/traefik:latest&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;container_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;traefik&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;restart&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;unless-stopped&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;command&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# Tell Traefik to discover containers using the Docker API&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- --&lt;span class="l"&gt;providers.docker=true&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- --&lt;span class="l"&gt;providers.docker.exposedbydefault=false&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# Enable the Trafik dashboard&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- --&lt;span class="l"&gt;api.dashboard=true&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# Set up LetsEncrypt&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;#- --certificatesresolvers.letsencrypt.acme.caServer=https://acme-staging-v02.api.letsencrypt.org/directory&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- --&lt;span class="l"&gt;certificatesresolvers.letsencrypt.acme.dnschallenge=true&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- --&lt;span class="l"&gt;certificatesresolvers.letsencrypt.acme.dnschallenge.provider=porkbun&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- --&lt;span class="l"&gt;certificatesresolvers.letsencrypt.acme.email=major@mhtx.net&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- --&lt;span class="l"&gt;certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# Set up an insecure listener that redirects all traffic to TLS&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- --&lt;span class="l"&gt;entrypoints.web.address=:80&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- --&lt;span class="l"&gt;entrypoints.web.http.redirections.entrypoint.to=websecure&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- --&lt;span class="l"&gt;entrypoints.web.http.redirections.entrypoint.scheme=https&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- --&lt;span class="l"&gt;entrypoints.websecure.address=:443&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# Set up the TLS configuration for our websecure listener&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- --&lt;span class="l"&gt;entrypoints.websecure.http.tls=true&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- --&lt;span class="l"&gt;entrypoints.websecure.http.tls.certResolver=letsencrypt&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- --&lt;span class="l"&gt;entrypoints.websecure.http.tls.domains[0].main=toots.cloud&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- --&lt;span class="l"&gt;entrypoints.websecure.http.tls.domains[0].sans=*.toots.cloud&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;PORKBUN_SECRET_API_KEY=*****&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;PORKBUN_API_KEY=*****&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;ports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="s1"&gt;&amp;#39;80:80&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="s1"&gt;&amp;#39;443:443&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;volumes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;/var/run/docker.sock:/var/run/docker.sock:ro&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;certs:/letsencrypt&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;labels&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="s2"&gt;&amp;#34;traefik.enable=true&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="s1"&gt;&amp;#39;traefik.http.routers.traefik.rule=Host(`traefik.toots.cloud`)&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="s2"&gt;&amp;#34;traefik.http.routers.traefik.entrypoints=websecure&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="s2"&gt;&amp;#34;traefik.http.routers.traefik.tls.certresolver=letsencrypt&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="s2"&gt;&amp;#34;traefik.http.routers.traefik.service=api@internal&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="s1"&gt;&amp;#39;traefik.http.routers.traefik.middlewares=strip&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="s1"&gt;&amp;#39;traefik.http.middlewares.strip.stripprefix.prefixes=/traefik&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;postgres&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;container_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;postgres&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;restart&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;always&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;docker.io/library/postgres:14-alpine&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;shm_size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;256mb&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;env_file&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;.env.production&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;healthcheck&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;test&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;CMD&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;pg_isready&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;-U&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;postgres&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;volumes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;postgres:/var/lib/postgresql/data&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;./postgres-setup.sh:/docker-entrypoint-initdb.d/init-user-db.sh:Z&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="s1"&gt;&amp;#39;POSTGRES_HOST_AUTH_METHOD=trust&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;container_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;redis&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;restart&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;always&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;docker.io/library/redis:7-alpine&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;healthcheck&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;test&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;CMD&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;redis-cli&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;ping&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;volumes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;redis:/data&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;web&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;container_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;web&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;docker.io/tootsuite/mastodon:v3.5.3&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;restart&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;always&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;env_file&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;.env.production&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;command&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;bash -c &amp;#34;rm -f /mastodon/tmp/pids/server.pid; bundle exec rails s -p 3000&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;healthcheck&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# prettier-ignore&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;test&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;CMD-SHELL&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;wget -q --spider --proxy=off localhost:3000/health || exit 1&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;ports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="s1"&gt;&amp;#39;3000&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;depends_on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;postgres&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;redis&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;volumes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;mastodon:/mastodon/public/system&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;labels&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="s2"&gt;&amp;#34;traefik.enable=true&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="s2"&gt;&amp;#34;traefik.http.routers.web.rule=Host(`toots.cloud`)&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="s2"&gt;&amp;#34;traefik.http.routers.web.entrypoints=websecure&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="s2"&gt;&amp;#34;traefik.http.routers.web.tls.certresolver=letsencrypt&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;streaming&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;container_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;streaming&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;docker.io/tootsuite/mastodon:v3.5.3&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;restart&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;always&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;env_file&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;.env.production&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;command&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;node ./streaming&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;healthcheck&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# prettier-ignore&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;test&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;CMD-SHELL&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;wget -q --spider --proxy=off localhost:4000/api/v1/streaming/health || exit 1&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;ports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="s1"&gt;&amp;#39;4000&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;depends_on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;postgres&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;redis&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;sidekiq&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;container_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;sidekiq&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;docker.io/tootsuite/mastodon:v3.5.3&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;restart&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;always&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;env_file&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;.env.production&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;command&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;bundle exec sidekiq&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;depends_on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;postgres&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;redis&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;volumes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;mastodon:/mastodon/public/system&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;healthcheck&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;test&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;CMD-SHELL&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;ps aux | grep &amp;#39;[s]idekiq\ 6&amp;#39; || false&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Here are the main changes I made:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Specified the exact URL/tag for each container&lt;/li&gt;&#10;&lt;li&gt;Added traefik to handle TLS&lt;/li&gt;&#10;&lt;li&gt;Used named volumes instead of filesystem directories (made SELinux much happier)&lt;/li&gt;&#10;&lt;li&gt;Added a provisioning script for postgres&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;The provisioning script for postgres allows me to bring up postgres without needing to run any extra commands:&lt;/p&gt;&#10;&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&#10;&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&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;psql -v &lt;span class="nv"&gt;ON_ERROR_STOP&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt; --username postgres &lt;span class="s"&gt;&amp;lt;&amp;lt;-EOSQL&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;&#9;CREATE USER mastodon WITH PASSWORD &amp;#39;super-secret-password&amp;#39; CREATEDB;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;EOSQL&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Once you have all of this in place, run the usual &lt;code&gt;docker-compose up -d&lt;/code&gt; and wait for everything to start.&#10;Then you can run through the initial mastodon setup:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; docker-compose run --rm web bundle &lt;span class="nb"&gt;exec&lt;/span&gt; rake mastodon:setup&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;You will need to answer lots of questions, including your domain name, postgres/redis details, email configuration, and object storage configuration.&#10;I use &lt;a href="https://www.mailgun.com/"&gt;Mailgun&lt;/a&gt; for mastodon&amp;rsquo;s email since it makes the setup much easier and has a very low cost.&#10;For object storage, I went with a public &lt;a href="https://www.backblaze.com/b2/cloud-storage.html"&gt;Backblaze B2&lt;/a&gt; bucket since it&amp;rsquo;s Amazon S3 compatible but very inexpensive&lt;sup id="fnref:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;&#10;&lt;p&gt;When the setup finishes, it will dump an environments file to the screen.&#10;&lt;strong&gt;Be sure to save that file.&lt;/strong&gt;&#10;This will allow you to start up all of the containers again with the same configuration later.&#10;A copy of the environments file will be kept inside the container storage as well.&lt;/p&gt;&#10;&lt;h1 id="self-hosted-instance-takeaways"&gt;Self-hosted instance takeaways&lt;/h1&gt;&#10;&lt;p&gt;I&amp;rsquo;ve been running my own mastodon instance for a few days and I&amp;rsquo;m not sure if I will keep it.&#10;Sure, I love having an instance on a hilarious domain like &lt;em&gt;toots.cloud&lt;/em&gt; and having full control over my mastodon experience.&lt;/p&gt;&#10;&lt;p&gt;But it&amp;rsquo;s one more thing to manage, patch, and back up.&lt;/p&gt;&#10;&lt;p&gt;The fosstodon.org community has been excellent so far and I&amp;rsquo;m contributing to their costs each month via their &lt;a href="https://www.patreon.com/fosstodon/posts"&gt;Patreon page&lt;/a&gt;.&#10;Every mastodon instance is going through growing pains recently due to really high demand.&lt;/p&gt;&#10;&lt;p&gt;The last count from the &lt;a href="https://bitcoinhackers.org/@mastodonusercount"&gt;Mastodon Users bot&lt;/a&gt; shows massive interest:&lt;/p&gt;&#10;&lt;p&gt;&lt;img src="mastodon_users.png" alt="mastodon_users.png" title="🤖 Mastodon user count bot"&gt;&lt;/p&gt;&#10;&lt;p&gt;If you&amp;rsquo;re on a server that isn&amp;rsquo;t performing well: &lt;strong&gt;be patient&lt;/strong&gt;.&lt;/p&gt;&#10;&lt;p&gt;Ask for ways that you can help technically or financially.&#10;One of the biggest reminders that I get from mastodon is that every server is a &lt;em&gt;community&lt;/em&gt;.&#10;The community must come together to make each server successful as a part of the big fediverse.&lt;/p&gt;&#10;&lt;div class="footnotes" role="doc-endnotes"&gt;&#10;&lt;hr&gt;&#10;&lt;ol&gt;&#10;&lt;li id="fn:1"&gt;&#10;&lt;p&gt;Be sure to note the endpoint for your Backblaze bucket when you create it.&#10;You will need to specify that endpoint when you set up mastodon.&#10;As an example, my endpoint is &lt;code&gt;https://s3.us-west-001.backblazeb2.com&lt;/code&gt; and my region is &lt;code&gt;us-west-001&lt;/code&gt;.&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;/div&gt;&#10;</description></item><item><title>Mount NFS shares in kubernetes</title><link>https://major.io/p/mount-nfs-shares-in-kubernetes/</link><pubDate>Fri, 08 Apr 2022 00:00:00 +0000</pubDate><author>major@mhtx.net (Major Hayden)</author><guid>https://major.io/p/mount-nfs-shares-in-kubernetes/</guid><description>&lt;p&gt;Kubernetes offers a plethora of &lt;a href="https://kubernetes.io/docs/concepts/storage/volumes/"&gt;storage options&lt;/a&gt; for mounting volumes in pods, and NFS&#10;is included. I have a Synology NAS at home and some of my pods in my home kubernetes&#10;deployment need access to files via NFS.&lt;/p&gt;&#10;&lt;p&gt;Although the Kubernetes documentation has a &lt;a href="https://github.com/kubernetes/examples/tree/master/staging/volumes/nfs"&gt;bunch of examples&lt;/a&gt; about setting up NFS&#10;mounts, I ended up being more confused than when I started. This post covers a simple&#10;example that you can copy, adapt, and paste as needed.&lt;/p&gt;&#10;&lt;h1 id="verify-that-nfs-is-working"&gt;Verify that NFS is working&lt;/h1&gt;&#10;&lt;p&gt;NFS can be tricky to get right and it&amp;rsquo;s important to verify that it&amp;rsquo;s working &lt;em&gt;outside&#10;of kubernetes&lt;/em&gt; before you try mounting it in a pod. Trust me &amp;ndash; NFS looks quite simple&#10;at first glance but you can get confused quickly. Test out the easiest stuff first.&lt;/p&gt;&#10;&lt;p&gt;As an example: Synology made some recent changes to their NFS configuration where you&#10;must specify shares with a netmask (&lt;code&gt;192.168.10.0/255.255.255.0&lt;/code&gt;) or in CIDR notation&#10;(&lt;code&gt;192.168.10.0/24&lt;/code&gt;). That took me a while to figure out going back and forth from server&#10;to client and back again.&lt;/p&gt;&#10;&lt;p&gt;If you&amp;rsquo;re making your shares from a regular Linux server, refer to the &lt;a href="https://wiki.archlinux.org/title/NFS"&gt;Arch Linux NFS&#10;documentation&lt;/a&gt;. It&amp;rsquo;s one of the best write-ups on NFS around!&lt;/p&gt;&#10;&lt;p&gt;First, I verified that the mount is showing up via &lt;code&gt;showmount&lt;/code&gt;:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; showmount -e 192.168.10.60&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Export list for 192.168.10.60:&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;/volume1/media 192.168.10.50/32&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;My NFS server is on &lt;code&gt;192.168.10.60&lt;/code&gt; and my NFS client (running kubernetes) is on&#10;&lt;code&gt;192.168.10.50&lt;/code&gt;.&lt;/p&gt;&#10;&lt;p&gt;🤔 Got an error or can&amp;rsquo;t see any exports? Double check the IP addresses allowed to&#10;access the share on the server side and verify your client machine&amp;rsquo;s IP address.&lt;/p&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;☝🏻 Remember to re-export your shares on the server with &lt;code&gt;exportfs -arv&lt;/code&gt; if you made&#10;changes! The NFS server won&amp;rsquo;t pick them up automatically. Display your currently&#10;running exports with &lt;code&gt;exportfs -v&lt;/code&gt;.&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;Let&amp;rsquo;s try mounting the share next:&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;$ sudo mount -t nfs 192.168.10.60:/volume1/media /tmp/test&#10;$ df -hT /tmp/test&#10;Filesystem Type Size Used Avail Use% Mounted on&#10;192.168.10.60:/volume1/media nfs4 16T 7.5T 8.3T 48% /tmp/test&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Awesome! 🎉&lt;/p&gt;&#10;&lt;p&gt;Unmount the test:&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;$ sudo umount /tmp/test&#10;&lt;/code&gt;&lt;/pre&gt;&lt;h1 id="mount-nfs-in-a-pod"&gt;Mount NFS in a pod&lt;/h1&gt;&#10;&lt;p&gt;First off, we need a deployment where we can mount up an NFS share. I decided to take a&#10;Fedora 35 container and create a really basic deployment:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;apiVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;apps/v1&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Deployment&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;labels&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;app&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;fedoratest&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;fedoratest&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;namespace&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;fedoratest&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;template&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;containers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;registry.fedoraproject.org/fedora:35&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;fedora&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;command&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;/bin/bash&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;-c&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;--&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;while true; do sleep 30; done;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This is a really silly deployment that causes Fedora to sleep forever until someone&#10;stops the pod. I mainly want something that I can shell into and ensure NFS is working.&lt;/p&gt;&#10;&lt;p&gt;Now we need to add two pieces to the deployment:&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;An NFS volume:&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;volumes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;nfs-vol&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;nfs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;server&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;192.168.10.60&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;/volume1/media&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ol start="2"&gt;&#10;&lt;li&gt;A path to mount the volume&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;volumeMounts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;nfs-vol&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;mountPath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;/opt/nfs&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;When we add in those NFS pieces, we get the following deployment:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;apiVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;apps/v1&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Deployment&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;labels&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;app&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;fedoratest&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;fedoratest&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;namespace&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;fedoratest&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;template&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;containers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;registry.fedoraproject.org/fedora:35&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;fedora&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;command&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;/bin/bash&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;-c&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;--&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;while true; do sleep 30; done;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;volumeMounts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;nfs-vol&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;mountPath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;/opt/nfs&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;volumes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;nfs-vol&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;nfs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;server&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;192.168.10.60&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;/volume1/media&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Save that as &lt;code&gt;fedoratest.yaml&lt;/code&gt; and apply it:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; kubectl apply -f fedoratest.yaml&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Let&amp;rsquo;s see if the volume worked:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; kubectl describe -n fedoratest deployment/fedoratest&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;✂&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; Volumes:&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; nfs-vol:&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; Type: NFS (an NFS mount that lasts the lifetime of a pod)&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; Server: 192.168.10.60&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; Path: /volume1/media&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; ReadOnly: false&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;✂&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now let&amp;rsquo;s have a look at it inside the container itself:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; kubectl -n fedoratest &lt;span class="nb"&gt;exec&lt;/span&gt; -it deployment/fedoratest -- sh&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;sh-5.1$ cd /opt/nfs&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;sh-5.1$ ls&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;dir1 dir2 dir3&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Let&amp;rsquo;s ensure we can write files:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;sh-5.1$ touch doot&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;sh-5.1$ ls -al doot&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;-rwxrwxrwx. 1 root root 0 Apr 8 21:03 doot&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;sh-5.1$ rm doot&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;I can write files, but writing them as root causes problems for other applications. In&#10;this case, my NFS server uses UID &lt;code&gt;1035&lt;/code&gt; for my user and GID &lt;code&gt;100&lt;/code&gt; for my group. Lucky&#10;for us, we can set this up within our deployment configuration using &lt;code&gt;securityContext&lt;/code&gt;:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;apiVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;apps/v1&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Deployment&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;labels&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;app&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;fedoratest&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;fedoratest&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;namespace&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;fedoratest&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;template&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;securityContext&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;runAsUser&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1035&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# Use my UID on the NFS server&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;runAsGroup&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;100&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# Use my GID on the NFS server&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;containers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;registry.fedoraproject.org/fedora:35&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;fedora&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;command&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;/bin/bash&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;-c&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;--&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;while true; do sleep 30; done;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;volumeMounts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;nfs-vol&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;mountPath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;/opt/nfs&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;volumes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;nfs-vol&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;nfs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;server&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;192.168.10.60&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;/volume1/media&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Apply this change:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; kubectl apply -f fedoratest.yaml&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now try to write a file again:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; kubectl -n fedoratest &lt;span class="nb"&gt;exec&lt;/span&gt; -it deployment/fedoratest -- sh&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;sh-5.1$ touch doot&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;sh-5.1$ ls -al doot&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;-rwxrwxrwx. 1 1035 100 0 Apr 8 21:08 doot&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;sh-5.1$ rm doot&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Perfect! Now files are owned by the correct UID and GID.&lt;/p&gt;&#10;&lt;h2 id="extra-credit"&gt;Extra credit&lt;/h2&gt;&#10;&lt;p&gt;If you plan to have plenty of pods mounting storage from the same NFS server, you might&#10;want to consider building out a persistent volume first and then making claims from it.&#10;The kubernetes examples repository has a good example of a &lt;a href="https://github.com/kubernetes/examples/blob/master/staging/volumes/nfs/nfs-pv.yaml"&gt;persistent NFS volume&lt;/a&gt; and a&#10;&lt;a href="https://github.com/kubernetes/examples/blob/master/staging/volumes/nfs/nfs-pvc.yaml"&gt;persistent volume claim&lt;/a&gt; made from that volume.&lt;/p&gt;&#10;</description></item><item><title>Run Xorg applications with podman</title><link>https://major.io/p/run-xorg-applications-with-podman/</link><pubDate>Sun, 17 Oct 2021 00:00:00 +0000</pubDate><author>major@mhtx.net (Major Hayden)</author><guid>https://major.io/p/run-xorg-applications-with-podman/</guid><description>&lt;p&gt;Containers are a great way to deliver and run all kinds of applications.&#10;Although many people build containers for server applications, you can also use&#10;them for client applications on your local workstation. This helps when you want&#10;to test new applications without disrupting your existing system or when you use&#10;an immutable system such as &lt;a href="https://silverblue.fedoraproject.org/"&gt;Fedora Silverblue&lt;/a&gt;.&lt;/p&gt;&#10;&lt;p&gt;Podman takes this further by allowing you to run a client application without&#10;root access or daemons. This post covers how to build a container with an Xorg&#10;application and run it on a Fedora system.&lt;/p&gt;&#10;&lt;h2 id="building-the-container"&gt;Building the container&lt;/h2&gt;&#10;&lt;p&gt;Let&amp;rsquo;s start with a simple container that contains &lt;code&gt;xeyes&lt;/code&gt;. This simple&#10;application simply puts a pair of eyes on your screen that follow your mouse&#10;movements around the desktop. It has very few dependencies and it&amp;rsquo;s a great way&#10;to test several capabilities on the desktop.&lt;/p&gt;&#10;&lt;p&gt;Here&amp;rsquo;s a very simple container build file:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-dockerfile" data-lang="dockerfile"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# xeyes-container&lt;/span&gt;&lt;span class="err"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;registry.fedoraproject.org/fedora:latest&lt;/span&gt;&lt;span class="err"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;RUN&lt;/span&gt; dnf -y install xeyes&lt;span class="err"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;CMD&lt;/span&gt; xeyes&lt;span class="err"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Let&amp;rsquo;s install podman and build the container:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; sudo dnf -y install podman&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; podman build -t xeyes -f xeyes-container .&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="run-the-container"&gt;Run the container&lt;/h2&gt;&#10;&lt;p&gt;Now that we have our &lt;code&gt;xeyes&lt;/code&gt; container, let&amp;rsquo;s run it.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; podman run --rm xeyes&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Error: Can&amp;#39;t open display:&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;We&amp;rsquo;re missing the &lt;code&gt;DISPLAY&lt;/code&gt; variable inside the container. Let&amp;rsquo;s add it:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$DISPLAY&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;:0&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; podman run --rm -e DISPLAY xeyes&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Error: Can&amp;#39;t open display: :0&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Well, we have the display variable inside now, but there&amp;rsquo;s another problem.&#10;Inside the container, &lt;code&gt;xeyes&lt;/code&gt; can&amp;rsquo;t make a connection to our X daemon. This&#10;socket normally appears in &lt;code&gt;/tmp/.X11-unix&lt;/code&gt;, but the container doesn&amp;rsquo;t have it.&#10;Let&amp;rsquo;s try adding this inside the container:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; podman run --rm -e DISPLAY -v /tmp/.X11-unix:/tmp/.X11-unix xeyes&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Error: Can&amp;#39;t open display: :0&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Darn! This should be working. Let&amp;rsquo;s check the system journal:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;AVC avc: denied { write } for pid=10817 comm=&amp;#34;xeyes&amp;#34; name=&amp;#34;X0&amp;#34; dev=&amp;#34;tmpfs&amp;#34;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;ino=42 scontext=system_u:system_r:container_t:s0:c143,c574&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;tcontext=system_u:object_r:user_tmp_t:s0 tclass=sock_file permissive=0&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Uh oh. SELinux is upset that a container is trying to mess with the &lt;code&gt;X0&lt;/code&gt; socket&#10;for our Xorg server that sits in &lt;code&gt;/tmp/.X11-unix&lt;/code&gt;. You may be tempted to run&#10;&lt;code&gt;setenforce 0&lt;/code&gt;, but wait. We can fix this with podman!&lt;/p&gt;&#10;&lt;p&gt;Podman allows you to set security options for a particular container with&#10;&lt;code&gt;--security-opt&lt;/code&gt;. We need to run this container with an SELinux context that&#10;allows it to talk to something in &lt;code&gt;tmpfs&lt;/code&gt;. Examining the &lt;a href="https://github.com/containers/container-selinux/blob/main/container.te#L77"&gt;container-selinux&#10;project&lt;/a&gt; shows that &lt;code&gt;container_runtime_t&lt;/code&gt; can work with &lt;code&gt;tmpfs&lt;/code&gt;:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;type container_runtime_tmp_t alias docker_tmp_t;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;files_tmp_file(container_runtime_tmp_t)&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Let&amp;rsquo;s try adding this to our podman command now:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; podman run --rm -e DISPLAY &lt;span class="se"&gt;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; -v /tmp/.X11-unix:/tmp/.X11-unix \&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; --security-opt label=type:container_runtime_t xeyes&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;I now have a set of eyeballs on my desktop! 👀&lt;/p&gt;&#10;&lt;p&gt;&lt;img src="2021-10-17-xeyes.png" alt="2021-10-17-xeyes.png" title="xeyes running on my desktop"&gt;&lt;/p&gt;&#10;&lt;h2 id="extra-credit"&gt;Extra credit&lt;/h2&gt;&#10;&lt;p&gt;The &lt;code&gt;xeyes&lt;/code&gt; application is extremely simple, but you can run much more complex&#10;applications using this same method. Keep in mind that certain applications&#10;might require extra packages inside the container, such as fonts or GTK themes.&#10;Jessie Frazelle has a &lt;a href="https://github.com/jessfraz/dockerfiles"&gt;great repository&lt;/a&gt; full of containers that she uses&#10;regularly and this might give you inspiration to create some of your own! 🤓&lt;/p&gt;&#10;&lt;p&gt;&lt;em&gt;Photo credit: &lt;a href="https://unsplash.com/photos/gBr5Hmx1STc"&gt;Jonny Gios on Unsplash&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;&#10;</description></item><item><title>Wildcard LetsEncrypt certificates with Traefik and Cloudflare</title><link>https://major.io/p/wildcard-letsencrypt-certificates-traefik-cloudflare/</link><pubDate>Mon, 16 Aug 2021 00:00:00 +0000</pubDate><author>major@mhtx.net (Major Hayden)</author><guid>https://major.io/p/wildcard-letsencrypt-certificates-traefik-cloudflare/</guid><description>&lt;p&gt;Wildcard certificates make it easy to secure lots of subdomains under a single&#10;domain. For example, you can secure &lt;code&gt;web.example.com&lt;/code&gt; and &lt;code&gt;mail.example.com&lt;/code&gt;&#10;with a single certificate for &lt;code&gt;*.example.com&lt;/code&gt;. Fortunately, LetsEncrypt allows&#10;you to get wildcard certificates via a &lt;a href="https://letsencrypt.org/docs/challenge-types/"&gt;DNS ownership check&lt;/a&gt; (often called a&#10;&lt;em&gt;DNS-01 challenge&lt;/em&gt;).&lt;/p&gt;&#10;&lt;p&gt;Fortunately, &lt;a href="https://traefik.io/"&gt;Traefik&lt;/a&gt; can request a certificate from&#10;LetsEncrypt automatically and complete the challenge for you. It can publish DNS&#10;records to multiple providers, but my favorite is Cloudflare. They will host&#10;your DNS zones and records for free. They also have a robust API for managing&#10;DNS records (also free).&lt;/p&gt;&#10;&lt;p&gt;In this post, we will cover the basics of getting TLS working with Traefik. We&#10;can add a wildcard certificate on top and then re-use that same certificate for&#10;other containers running behind Traefik.&lt;/p&gt;&#10;&lt;h1 id="basic-setup"&gt;Basic setup&lt;/h1&gt;&#10;&lt;p&gt;First, we need a running instance of Traefik. The &lt;a href="https://doc.traefik.io/traefik/"&gt;Traefik documentation&lt;/a&gt;&#10;explains this entire process in detail and I highly recommend reading the basics&#10;on configuration discovery, routers, and TLS settings.&lt;/p&gt;&#10;&lt;p&gt;We will use &lt;a href="https://docs.docker.com/compose/"&gt;docker-compose&lt;/a&gt; to make this easier to manage. If you&amp;rsquo;re on Fedora, install docker-compose:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;dnf install docker-compose&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now we need a &lt;code&gt;docker-compose.yml&lt;/code&gt; file:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nn"&gt;---&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;3&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;services&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;traefik&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;traefik:latest&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;container_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;traefik&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;restart&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;unless-stopped&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;command&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# Tell Traefik to discover containers using the Docker API&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- --&lt;span class="l"&gt;providers.docker=true&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# Enable the Trafik dashboard&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- --&lt;span class="l"&gt;api.dashboard=true&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# Set up LetsEncrypt&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- --&lt;span class="l"&gt;certificatesresolvers.letsencrypt.acme.dnschallenge=true&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- --&lt;span class="l"&gt;certificatesresolvers.letsencrypt.acme.dnschallenge.provider=cloudflare&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- --&lt;span class="l"&gt;certificatesresolvers.letsencrypt.acme.email=EMAIL_ADDRESS&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- --&lt;span class="l"&gt;certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# Set up an insecure listener that redirects all traffic to TLS&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- --&lt;span class="l"&gt;entrypoints.web.address=:80&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- --&lt;span class="l"&gt;entrypoints.web.http.redirections.entrypoint.to=websecure&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- --&lt;span class="l"&gt;entrypoints.web.http.redirections.entrypoint.scheme=https&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- --&lt;span class="l"&gt;entrypoints.websecure.address=:443&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# Set up the TLS configuration for our websecure listener&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- --&lt;span class="l"&gt;entrypoints.websecure.http.tls=true&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- --&lt;span class="l"&gt;entrypoints.websecure.http.tls.certResolver=letsencrypt&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- --&lt;span class="l"&gt;entrypoints.websecure.http.tls.domains[0].main=home.example.com&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- --&lt;span class="l"&gt;entrypoints.websecure.http.tls.domains[0].sans=*.home.example.com&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;CLOUDFLARE_EMAIL=CLOUDFLARE_ACCOUNT_EMAIL_ADDRESS&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;CLOUDFLARE_DNS_API_TOKEN=CLOUDFLARE_TOKEN_GOES_HERE&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;ports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="m"&gt;80&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="m"&gt;80&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="m"&gt;443&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="m"&gt;443&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;volumes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;/var/run/docker.sock:/var/run/docker.sock:ro&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;certs:/letsencrypt&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;labels&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="s2"&gt;&amp;#34;traefik.enable=true&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="s1"&gt;&amp;#39;traefik.http.routers.traefik.rule=Host(`home.example.com`)&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="s2"&gt;&amp;#34;traefik.http.routers.traefik.entrypoints=websecure&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="s2"&gt;&amp;#34;traefik.http.routers.traefik.tls.certresolver=letsencrypt&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="s2"&gt;&amp;#34;traefik.http.routers.traefik.service=api@internal&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="s1"&gt;&amp;#39;traefik.http.routers.traefik.middlewares=strip&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="s1"&gt;&amp;#39;traefik.http.middlewares.strip.stripprefix.prefixes=/traefik&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;In this example, we tell Traefik about our desired setup in the &lt;code&gt;command&lt;/code&gt;&#10;section, including our listeners. Our insecure listener on port 80 redirects to&#10;secure connections on port 443 and we tell Traefik that we plan to use&#10;LetsEncrypt to get the certificates.&lt;/p&gt;&#10;&lt;p&gt;We provide the username and Cloudflare API key in the &lt;code&gt;environment&lt;/code&gt; section.&#10;Follow Cloudflare&amp;rsquo;s guides for &lt;a href="https://support.cloudflare.com/hc/en-us/articles/200167836-Managing-API-Tokens-and-Keys"&gt;managing API tokens and keys&lt;/a&gt; carefully to&#10;generate a token.&lt;/p&gt;&#10;&lt;p&gt;The &lt;code&gt;labels&lt;/code&gt; section sets up a rule where traffic destined for&#10;&lt;code&gt;home.example.com&lt;/code&gt; goes to the Traefik dashboard. this is helpful in case you&#10;make mistakes or you can&amp;rsquo;t figure out why something is working. You can go to&#10;the dashboard to show all of the existing services, listeners, and other&#10;configurations.&lt;/p&gt;&#10;&lt;p&gt;☝🏻 Before applying this docker-compose file, change a few things:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Set your LetsEncrypt email address in the line with&#10;&lt;code&gt;--certificatesresolvers.letsencrypt.acme.email&lt;/code&gt;&lt;/li&gt;&#10;&lt;li&gt;Set your Cloudflare account email address for the &lt;code&gt;CLOUDFLARE_EMAIL&lt;/code&gt;&#10;environment variable&lt;/li&gt;&#10;&lt;li&gt;Set your Cloudflare DNS API token for the &lt;code&gt;CLOUDFLARE_DNS_API_TOKEN&lt;/code&gt;&#10;environment variable&lt;/li&gt;&#10;&lt;li&gt;Change the &lt;code&gt;Host()&lt;/code&gt; rules from &lt;code&gt;example.com&lt;/code&gt; to match your domain name&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;Run &lt;code&gt;docker-compose up -d&lt;/code&gt; and then &lt;code&gt;docker-compose logs -f traefik&lt;/code&gt; to see if&#10;Traefik came up successfully with certificates. If you run into any problems,&#10;double check that your Cloudflare email and token are accurate. Also verify that&#10;your Cloudflare token has the correct permissions to adjust the dns zone.&lt;/p&gt;&#10;&lt;h1 id="adding-a-container"&gt;Adding a container&lt;/h1&gt;&#10;&lt;p&gt;At this point, we can add another container and it can use the same TLS&#10;certificate we requested from LetsEncrypt already!&lt;/p&gt;&#10;&lt;p&gt;The &lt;a href="https://librespeed.org/"&gt;librespeed&lt;/a&gt; project provides a self-hosted network speed test that you can&#10;run on any network. It also runs perfectly inside a container. The&#10;&lt;a href="https://docs.linuxserver.io/images/docker-librespeed"&gt;linuxserver.io librespeed container&lt;/a&gt; is well maintained and easy to run.&lt;/p&gt;&#10;&lt;p&gt;Add this to your &lt;code&gt;docker-compose.yml&lt;/code&gt; right under the Traefik configuration:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;librespeed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;ghcr.io/linuxserver/librespeed&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;container_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;librespeed&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;restart&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;unless-stopped&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;ports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="m"&gt;80&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;labels&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="s2"&gt;&amp;#34;traefik.enable=true&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="s2"&gt;&amp;#34;traefik.http.routers.librespeed.rule=Host(`librespeed.home.example.com`)&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="s2"&gt;&amp;#34;traefik.http.routers.librespeed.entrypoints=websecure&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="s2"&gt;&amp;#34;traefik.http.routers.librespeed.tls.certresolver=letsencrypt&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Check the &lt;code&gt;labels&lt;/code&gt; section. We first enable Traefik so it will route requests to&#10;the container. Then we set a host rule so that traffic for&#10;&lt;code&gt;librespeed.home.example.com&lt;/code&gt; comes to this container. We only listen for TLS&#10;traffic (remember our redirect for insecure traffic earlier).&lt;/p&gt;&#10;&lt;p&gt;Finally, we tell Traefik to use the same &lt;code&gt;certresolver&lt;/code&gt; as before. Traefik is&#10;smart enough to know that &lt;code&gt;*.home.example.com&lt;/code&gt; covers the&#10;&lt;code&gt;librespeed.home.example.com&lt;/code&gt; subdomain just fine.&lt;/p&gt;&#10;&lt;p&gt;Run &lt;code&gt;docker-compose up -d&lt;/code&gt; once more and now librespeed has a secure connection&#10;using the original wildcard certificate.&lt;/p&gt;&#10;&lt;h1 id="renewals"&gt;Renewals&lt;/h1&gt;&#10;&lt;p&gt;LetsEncrypt certificates are valid for only 90 days. That&amp;rsquo;s why automation plays&#10;such an important role in handling renewals. You certainly don&amp;rsquo;t want to set&#10;calendar reminders to log into your server and run a script every 90 days. 😱&lt;/p&gt;&#10;&lt;p&gt;Traefik &lt;a href="https://doc.traefik.io/traefik/https/acme/"&gt;automatically knows&lt;/a&gt; when the expiration date approaches. When the&#10;certificate has less than 30 days left until the expiration date, Traefik&#10;automatically renews the certificate.&lt;/p&gt;&#10;&lt;p&gt;💣 Be careful with your DNS zone and with your DNS API keys! If you accidentally&#10;delete the API key or make big changes to your DNS zone, there&amp;rsquo;s a chance that&#10;Traefik may not be able to renew the certificate.&lt;/p&gt;&#10;&lt;p&gt;&lt;em&gt;Photo credit: &lt;a href="https://unsplash.com/photos/GIcoFy0zrDo"&gt;Veron Wessels on Unsplash&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;&#10;</description></item><item><title>Rootless container management with docker-compose and podman</title><link>https://major.io/p/rootless-container-management-with-docker-compose-and-podman/</link><pubDate>Fri, 09 Jul 2021 00:00:00 +0000</pubDate><author>major@mhtx.net (Major Hayden)</author><guid>https://major.io/p/rootless-container-management-with-docker-compose-and-podman/</guid><description>&lt;p&gt;Everyone has an opinion for the best way to manage containers, and there are&#10;many contenders depending on how much complexity you can handle and how much&#10;automation you require. One of my favorite ways to manage containers is&#10;&lt;a href="https://docs.docker.com/compose/"&gt;docker-compose&lt;/a&gt;.&lt;/p&gt;&#10;&lt;h2 id="overview-of-docker-compose"&gt;Overview of docker-compose&lt;/h2&gt;&#10;&lt;p&gt;docker-compose uses a simple YAML syntax to explain what your desired end state&#10;should look like. The &lt;a href="https://github.com/compose-spec/compose-spec/blob/master/spec.md"&gt;compose specification&lt;/a&gt; covers all of the relevant&#10;configurations for containers, volumes, networks, and more. After each change,&#10;docker-compose compares your configuration to the running containers and makes&#10;all of the required changes.&lt;/p&gt;&#10;&lt;p&gt;This provides some advantages over using &lt;code&gt;docker run ...&lt;/code&gt; or &lt;code&gt;podman run ...&lt;/code&gt;&#10;since you can put the YAML into version control and track your configuration&#10;changes all in one place. I was tracking the configuration in shell scripts that&#10;ran &lt;code&gt;docker&lt;/code&gt; with lots of parameters and that became difficult to manage.&lt;/p&gt;&#10;&lt;h2 id="what-about-podman"&gt;What about Podman?&lt;/h2&gt;&#10;&lt;p&gt;&lt;a href="https://podman.io/"&gt;Podman&lt;/a&gt; is a tool for managing containers, much like Docker, but it has some&#10;distinct advantages:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;No daemons are needed&lt;/li&gt;&#10;&lt;li&gt;You can run containers as your user, or as root&lt;/li&gt;&#10;&lt;li&gt;The commands and arguments are &lt;em&gt;nearly&lt;/em&gt; identical to &lt;code&gt;docker&lt;/code&gt; &lt;em&gt;(no swarm&#10;support)&lt;/em&gt;&lt;/li&gt;&#10;&lt;li&gt;Podman 3 added a &lt;a href="https://docs.podman.io/en/latest/_static/api.html"&gt;complete Docker-compatible API&lt;/a&gt;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;This last part, the Docker-compatible API is quite interesting and this allows&#10;docker-compose to work with podman as well as it does with docker.&lt;/p&gt;&#10;&lt;p&gt;Let&amp;rsquo;s try it out!&lt;/p&gt;&#10;&lt;h2 id="getting-everything-ready"&gt;Getting everything ready&lt;/h2&gt;&#10;&lt;p&gt;Start with a working Fedora 34 system and install some packages:&lt;/p&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;💣 &lt;strong&gt;HEADS UP:&lt;/strong&gt; The &lt;code&gt;podman-docker&lt;/code&gt; package brings in podman, an alias for the&#10;&lt;code&gt;docker&lt;/code&gt; command that actually runs &lt;code&gt;podman&lt;/code&gt;, and the docker-compatible API via&#10;a socket. If you want to run podman and docker side by side on the same machine,&#10;install &lt;code&gt;podman&lt;/code&gt; instead of &lt;code&gt;podman-docker&lt;/code&gt; here. If you had docker installed&#10;already, you may need to remove it with &lt;code&gt;dnf remove docker-ce docker-ce-cli&lt;/code&gt;.&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;dnf install docker-compose podman-docker&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;We&amp;rsquo;re going to do something different here. Intead of starting the podman socket&#10;or docker daemon as root, we&amp;rsquo;re going to start the podman socket as a regular&#10;user. Switch to a regular user and start the socket:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; systemctl &lt;span class="nb"&gt;enable&lt;/span&gt; --now --user podman.socket&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Created symlink /home/major/.config/systemd/user/sockets.target.wants/podman.socket → /usr/lib/systemd/user/podman.socket.&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;But wait, where&amp;rsquo;s the socket?&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; ls -al &lt;span class="nv"&gt;$XDG_RUNTIME_DIR&lt;/span&gt;/podman/podman.sock&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;srw-rw----. 1 major major 0 Jul 9 16:49 /run/user/1000/podman/podman.sock&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;That&amp;rsquo;s a podman socket running as my user and exposing a docker-compatible API.&#10;🎉&lt;/p&gt;&#10;&lt;h2 id="time-for-docker-compose"&gt;Time for docker-compose&lt;/h2&gt;&#10;&lt;p&gt;Now it&amp;rsquo;s time to use docker-compose with podman as a regular user and run a&#10;container as our regular user.&lt;/p&gt;&#10;&lt;p&gt;We can use &lt;a href="https://github.com/librespeed/speedtest"&gt;librespeed&lt;/a&gt; for this example, and the &lt;a href="https://docs.linuxserver.io/images/docker-librespeed"&gt;LinuxServer librespeed&#10;container&lt;/a&gt; is a great way to deploy it. It&amp;rsquo;s a self-hosted speed test&#10;application that works well with desktops and mobile devices.&lt;/p&gt;&#10;&lt;p&gt;First, we begin with the suggested docker-compose configuration:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nn"&gt;---&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;2.1&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;services&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;librespeed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;ghcr.io/linuxserver/librespeed&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;container_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;librespeed&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;PUID=1000&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;PGID=1000&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;TZ=Etc/UTC&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;volumes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;librespeed:/config&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;ports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="m"&gt;8080&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="m"&gt;80&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;restart&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;unless-stopped&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;volumes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;librespeed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;{}&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Save that as &lt;code&gt;docker-compose.yml&lt;/code&gt; in your current directory.&lt;/p&gt;&#10;&lt;p&gt;Keep in mind that docker-compose is expecting to find our docker socket in&#10;&lt;code&gt;/var/run/docker.sock&lt;/code&gt;, but we&amp;rsquo;re running the podman socket as our regular user.&#10;Let&amp;rsquo;s export the &lt;code&gt;DOCKER_HOST&lt;/code&gt; variable and run docker-compose to bring up our&#10;new container:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; &lt;span class="nb"&gt;export&lt;/span&gt; &lt;span class="nv"&gt;DOCKER_HOST&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;unix:&lt;/span&gt;&lt;span class="nv"&gt;$XDG_RUNTIME_DIR&lt;/span&gt;&lt;span class="s2"&gt;/podman/podman.sock&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; docker-compose up -d&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Pulling librespeed (ghcr.io/linuxserver/librespeed:)...&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;10f45b17b9ab: Download complete&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;f23b92877416: Download complete&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;a5bf9c523af4: Download complete&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;00fe9b963179: Download complete&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;bfafa0ba1dc9: Download complete&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;c583b34264f1: Download complete&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;9d26cce56b8d: Download complete&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;70de87880afd: Download complete&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;0ad6c2578069: Download complete&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;a8792749de3b: Download complete&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;2d31530d2d8b: Download complete&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Creating librespeed ... done&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; docker-compose ps&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; Name Command State Ports&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;--------------------------------------------&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;librespeed /init Up () :8080-&amp;gt;80/tcp&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The container is up and running as our user. Let&amp;rsquo;s check the &lt;code&gt;nginx&lt;/code&gt; process&#10;inside the container to be sure:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; ps -xu &lt;span class="p"&gt;|&lt;/span&gt;grep &lt;span class="s2"&gt;&amp;#34;nginx: master&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;major 3805 0.0 0.4 5860 4692 ? Ss 16:53 0:00 nginx: master process /usr/sbin/nginx -c /config/nginx/nginx.conf&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Sweet! 🥳&lt;/p&gt;&#10;&lt;h2 id="time-for-a-speed-test"&gt;Time for a speed test&lt;/h2&gt;&#10;&lt;p&gt;If we&amp;rsquo;ve come this far, we might as well test our internet speed to ensure the&#10;container works!&lt;/p&gt;&#10;&lt;p&gt;&lt;img src="2021-07-09-speedtest-prior.png" alt="2021-07-09-speedtest-prior.png" title="Librespeed speed test interface before testing"&gt;&lt;/p&gt;&#10;&lt;p&gt;Remember that we used port 8080 as a replacement for 80 in our docker-compose&#10;file to avoid issues with regular users being denied access to create a listener&#10;on ports under 1024.&lt;/p&gt;&#10;&lt;p&gt;Let&amp;rsquo;s see how fast my connection is today:&lt;/p&gt;&#10;&lt;p&gt;&lt;img src="2021-07-09-speedtest-prior.png" alt="2021-07-09-speedtest-prior.png" title="Librespeed speed test interface after testing"&gt;&lt;/p&gt;&#10;&lt;p&gt;&lt;em&gt;Photo credit: &lt;a href="https://unsplash.com/photos/gXN8cfWlYCo"&gt;Michael D Beckwith on Unsplash&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;&#10;</description></item><item><title>Get faster GitLab runners with a ramdisk</title><link>https://major.io/p/get-faster-gitlab-runners-with-a-ramdisk/</link><pubDate>Fri, 16 Aug 2019 00:00:00 +0000</pubDate><author>major@mhtx.net (Major Hayden)</author><guid>https://major.io/p/get-faster-gitlab-runners-with-a-ramdisk/</guid><description>&lt;p&gt;When you build tons of kernels every day like my team does, you look for&#10;speed improvements anywhere you can. Caching repositories, artifacts, and&#10;compiled objects makes kernel builds faster and it reduces infrastructure&#10;costs.&lt;/p&gt;&#10;&lt;h2 id="need-for-speed"&gt;Need for speed&lt;/h2&gt;&#10;&lt;p&gt;We use GitLab CI in plenty of places, and that means we have a lot of&#10;gitlab-runner configurations for OpenShift (using the kubernetes executor)&#10;and AWS (using the docker-machine executor). The runner&amp;rsquo;s built-in &lt;a href="https://docs.gitlab.com/runner/configuration/advanced-configuration.html#the-runnerscache-section"&gt;caching&lt;/a&gt;&#10;makes it easy to upload and download cached items from object storage&#10;repositories like Google Cloud Storage or Amazon S3.&lt;/p&gt;&#10;&lt;p&gt;However, there&amp;rsquo;s an often overlooked feature hiding in the &lt;a href="https://docs.gitlab.com/runner/configuration/advanced-configuration.html#the-runnersdocker-section"&gt;configuration for&#10;the docker executor&lt;/a&gt; that provides a great performance boost: mounting tmpfs&#10;inside your container. Not familiar with tmpfs? Arch Linux has a &lt;a href="https://wiki.archlinux.org/index.php/Tmpfs"&gt;great wiki&#10;page for tmpfs&lt;/a&gt; and James Coyle has a &lt;a href="https://www.jamescoyle.net/knowledge/951-the-difference-between-a-tmpfs-and-ramfs-ram-disk"&gt;well-written blog post&lt;/a&gt; about what&#10;makes it unique from the older ramfs.&lt;/p&gt;&#10;&lt;p&gt;RAM is much faster than your average cloud provider&amp;rsquo;s block storage. It also&#10;has incredibly low latency relative to most storage media. There&amp;rsquo;s a &lt;a href="https://people.eecs.berkeley.edu/~rcs/research/interactive_latency.html"&gt;great&#10;interactive latency page&lt;/a&gt; that allows you to use a slider to travel back in&#10;time to 1990 and compare all kinds of storage performance numbers. &lt;em&gt;(It&amp;rsquo;s&#10;really fun! Go drag the slider and be amazed.)&lt;/em&gt;&lt;/p&gt;&#10;&lt;p&gt;Better yet, many cloud providers give you lots of RAM per CPU on their&#10;instances, so if your work isn&amp;rsquo;t terribly memory intensive, you can use a lot&#10;of this RAM for faster storage.&lt;/p&gt;&#10;&lt;h2 id="enabling-tmpfs-in-docker-containers"&gt;Enabling tmpfs in Docker containers&lt;/h2&gt;&#10;&lt;p&gt;⚠️ &lt;strong&gt;Beware of the dangers of tmpfs before adjusting your runner&#10;configuration!&lt;/strong&gt; See the warnings at the end of this post.&lt;/p&gt;&#10;&lt;p&gt;This configuration is buried in the middle of the &lt;a href="https://docs.gitlab.com/runner/executors/docker.html#mounting-a-directory-in-ram"&gt;docker executor&#10;documentation&lt;/a&gt;. You will need to add some extra configuration to your&#10;&lt;code&gt;[runners.docker]&lt;/code&gt; section to make it work:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-toml" data-lang="toml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;runners&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;docker&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&#10;&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 class="nx"&gt;runners&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;docker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tmpfs&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;/ramdisk&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;rw,noexec&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This configuration mounts a tmpfs volume underneath &lt;code&gt;/ramdisk&lt;/code&gt; inside the&#10;container. By default, this directory will be mounted with &lt;code&gt;noexec&lt;/code&gt;, but if&#10;you need to execute scripts from that directory, change &lt;code&gt;noexec&lt;/code&gt; to &lt;code&gt;exec&lt;/code&gt;:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-toml" data-lang="toml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;runners&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;docker&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&#10;&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 class="nx"&gt;runners&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;docker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tmpfs&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;/ramdisk&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;rw,exec&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;In our case, compiling kernels requires executing scripts, so we use &lt;code&gt;exec&lt;/code&gt;&#10;for our tmpfs mounts.&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;You must be specific for &lt;code&gt;exec&lt;/code&gt;!&lt;/strong&gt; As an example, this tmpfs volume will be&#10;mounted with &lt;code&gt;noexec&lt;/code&gt; since that is the default:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-toml" data-lang="toml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;runners&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;docker&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&#10;&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 class="nx"&gt;runners&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;docker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tmpfs&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;/ramdisk&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;rw&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="extra-speed"&gt;Extra speed&lt;/h2&gt;&#10;&lt;p&gt;For even more speed, we moved the objects generated by &lt;code&gt;ccache&lt;/code&gt; to the&#10;ramdisk. The seek times are much lower and this allows &lt;code&gt;ccache&lt;/code&gt; to look for&#10;its cached objects much more quickly.&lt;/p&gt;&#10;&lt;p&gt;Git repositories are also great things to stash on tmpfs. Big kernel&#10;repositories are usually 1.5GB to 2GB in size with tons of files. Checkouts&#10;are really fast when they&amp;rsquo;re done in tmpfs.&lt;/p&gt;&#10;&lt;h2 id="dangers-are-lurking"&gt;Dangers are lurking&lt;/h2&gt;&#10;&lt;p&gt;⚠️ As mentioned earlier, &lt;strong&gt;beware of the dangers of tmpfs.&lt;/strong&gt;&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;All of the containers on the machine will share the same amount of RAM for&#10;their tmpfs volumes. Be sure to account for how much each container will use&#10;and how many containers &lt;em&gt;could&lt;/em&gt; be present on the same machine.&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;Be aware of how much memory your tests will use when they run. In our case,&#10;kernel compiles can consume 2-4GB of RAM, depending on configuration, so we&#10;try our best to leave some memory free.&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;These volumes also have no limits on how much data can go into the volume.&#10;However, if you put too much data into the tmpfs volume and your system runs&#10;critically low on available RAM, you could see a huge drop in performance,&#10;system instability, or even a crash. 🔥&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;</description></item><item><title>buildah error: vfs driver does not support overlay.mountopt options</title><link>https://major.io/p/buildah-error-vfs-driver-does-not-support-overlay-mountopt-options/</link><pubDate>Tue, 13 Aug 2019 00:00:00 +0000</pubDate><author>major@mhtx.net (Major Hayden)</author><guid>https://major.io/p/buildah-error-vfs-driver-does-not-support-overlay-mountopt-options/</guid><description>&lt;p&gt;Buildah and podman make a great pair for building, managing and running&#10;containers on a Linux system. You can even &lt;a href="https://major.io/2019/05/24/build-containers-in-gitlab-ci-with-buildah/"&gt;use them with GitLab CI&lt;/a&gt; with a&#10;few small adjustments, namely the switch from the overlayfs to vfs storage&#10;driver.&lt;/p&gt;&#10;&lt;p&gt;I have some regularly scheduled GitLab CI jobs that attempt to build fresh&#10;containers each morning and I use these to get the latest packages and find&#10;out early when something is broken in the build process. A failed build&#10;appeared in my inbox earlier this week with the following error:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;+ buildah bud -f builds/builder-fedora30 -t builder-fedora30 .&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;vfs driver does not support overlay.mountopt options&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;My container build script&lt;sup id="fnref:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt; is fairly basic, but it does include a change to&#10;use the vfs storage driver:&lt;/p&gt;&#10;&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="c1"&gt;# Use vfs with buildah. Docker offers overlayfs as a default, but buildah&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# cannot stack overlayfs on top of another overlayfs filesystem.&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;export&lt;/span&gt; &lt;span class="nv"&gt;STORAGE_DRIVER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;vfs&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The script doesn&amp;rsquo;t change any mount options during the build process. A quick&#10;glance at the &lt;code&gt;/etc/containers/storage.conf&lt;/code&gt; revealed a possible problem:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-ini" data-lang="ini"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;[storage.options]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Storage options to be passed to underlying storage drivers&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# mountopt specifies comma separated list of extra mount options&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt;mountopt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;nodev,metacopy=on&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;These mount options make sense when used with an overlayfs filesystem, but&#10;they are not used with vfs. I commented out the &lt;code&gt;mountopt&lt;/code&gt; option, saved the&#10;file, and ran a test build locally. &lt;strong&gt;Success!&lt;/strong&gt;&lt;/p&gt;&#10;&lt;p&gt;Fixing the build script involved a small change to the &lt;code&gt;storage.conf&lt;/code&gt; just&#10;before building the container:&lt;/p&gt;&#10;&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="c1"&gt;# Use vfs with buildah. Docker offers overlayfs as a default, but buildah&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# cannot stack overlayfs on top of another overlayfs filesystem.&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;export&lt;/span&gt; &lt;span class="nv"&gt;STORAGE_DRIVER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;vfs&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Newer versions of podman/buildah try to set overlayfs mount options when&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# using the vfs driver, and this causes errors.&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sed -i &lt;span class="s1"&gt;&amp;#39;/^mountopt =.*/d&amp;#39;&lt;/span&gt; /etc/containers/storage.conf&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;My containers are happily building again in GitLab.&lt;/p&gt;&#10;&lt;div class="footnotes" role="doc-endnotes"&gt;&#10;&lt;hr&gt;&#10;&lt;ol&gt;&#10;&lt;li id="fn:1"&gt;&#10;&lt;p&gt;The original build script is no longer available, but the &lt;a href="https://gitlab.com/cki-project/containers/"&gt;remainder of&#10;the repository&lt;/a&gt; still exists.&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;/div&gt;&#10;</description></item><item><title>Build containers in GitLab CI with buildah</title><link>https://major.io/p/build-containers-in-gitlab-ci-with-buildah/</link><pubDate>Fri, 24 May 2019 00:00:00 +0000</pubDate><author>major@mhtx.net (Major Hayden)</author><guid>https://major.io/p/build-containers-in-gitlab-ci-with-buildah/</guid><description>&lt;p&gt;My team at Red Hat depends heavily on &lt;a href="https://about.gitlab.com/product/continuous-integration/"&gt;GitLab CI&lt;/a&gt; and we build containers&#10;often to run all kinds of tests. Fortunately, GitLab offers up CI to build&#10;containers and a &lt;a href="https://about.gitlab.com/2016/05/23/gitlab-container-registry/"&gt;container registry&lt;/a&gt; in every repository to hold the&#10;containers we build.&lt;/p&gt;&#10;&lt;p&gt;This is really handy because it keeps everything together in one place: your&#10;container build scripts, your container build infrastructure, and the&#10;registry that holds your containers. Better yet, you can put multiple types&#10;of containers underneath a single git repository if you need to build&#10;containers based on different Linux distributions.&lt;/p&gt;&#10;&lt;h2 id="building-with-docker-in-gitlab-ci"&gt;Building with Docker in GitLab CI&lt;/h2&gt;&#10;&lt;p&gt;By default, GitLab offers up a &lt;a href="https://docs.gitlab.com/ee/ci/docker/using_docker_build.html"&gt;Docker builder&lt;/a&gt; that works just fine. The CI&#10;system clones your repository, builds your containers and pushes them&#10;wherever you want. There&amp;rsquo;s even a &lt;a href="https://docs.gitlab.com/ee/ci/docker/using_docker_build.html#using-docker-caching"&gt;simple CI YAML file&lt;/a&gt; that does everything&#10;end-to-end for you.&lt;/p&gt;&#10;&lt;p&gt;However, I have two issues with the Docker builder:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;&lt;strong&gt;Larger images:&lt;/strong&gt; The Docker image layering is handy, but the images end up&#10;being a bit larger, especially if you don&amp;rsquo;t do a little cleanup in each&#10;stage.&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;&lt;strong&gt;Additional service:&lt;/strong&gt; It requires an additional service inside the CI&#10;runner for the &lt;code&gt;dind&lt;/code&gt; (&amp;ldquo;Docker in Docker&amp;rdquo;) builder. This has caused some CI&#10;delays for me several times.&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="building-with-buildah-in-gitlab-ci"&gt;Building with buildah in GitLab CI&lt;/h2&gt;&#10;&lt;p&gt;On my local workstation, I use &lt;a href="https://podman.io/"&gt;podman&lt;/a&gt; and &lt;a href="https://buildah.io/"&gt;buildah&lt;/a&gt; all the time to build,&#10;run, and test containers. These tools are handy because I don&amp;rsquo;t need to&#10;remember to start the Docker daemon each time I want to mess with a&#10;container. I also don&amp;rsquo;t need sudo.&lt;/p&gt;&#10;&lt;p&gt;All of my containers are stored beneath my home directory. That&amp;rsquo;s good for&#10;keeping disk space in check, but it&amp;rsquo;s especially helpful on shared servers&#10;since each user has their own unique storage. My container pulls and builds&#10;won&amp;rsquo;t disrupt anyone else&amp;rsquo;s work on the server and their work won&amp;rsquo;t disrupt&#10;mine.&lt;/p&gt;&#10;&lt;p&gt;Finally, buildah offers some nice options out of the box. First, when you&#10;build a container with &lt;code&gt;buildah bud&lt;/code&gt;, you end up with only three layers by&#10;default:&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Original OS layer (example: &lt;code&gt;fedora:30&lt;/code&gt;)&lt;/li&gt;&#10;&lt;li&gt;Everything you added on top of the OS layer&lt;/li&gt;&#10;&lt;li&gt;Tiny bit of metadata&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p&gt;This is incredibly helpful if you use package managers like &lt;code&gt;dnf&lt;/code&gt;, &lt;code&gt;apt&lt;/code&gt;, and&#10;&lt;code&gt;yum&lt;/code&gt; that download a bunch of metadata before installing packages. You would&#10;normally have to clear the metadata carefully for the package manager so that&#10;your container wouldn&amp;rsquo;t grow in size. Buildah takes care of that by squashing&#10;all the stuff you add into one layer.&lt;/p&gt;&#10;&lt;p&gt;Of course, if you want to be more aggressive, buildah offers the &lt;code&gt;--squash&lt;/code&gt;&#10;option which squashes the whole image down into one layer. This can be&#10;helpful if disk space is at a premium and you change the layers often.&lt;/p&gt;&#10;&lt;h2 id="getting-started"&gt;Getting started&lt;/h2&gt;&#10;&lt;p&gt;I have a repository called &lt;a href="https://gitlab.com/majorhayden/os-containers"&gt;os-containers&lt;/a&gt; in GitLab that maintains fully&#10;updated containers for Fedora 29 and 30. The &lt;code&gt;.gitlab-ci.yml&lt;/code&gt; file calls&#10;&lt;code&gt;build.sh&lt;/code&gt; for two containers: &lt;em&gt;fedora29&lt;/em&gt; and &lt;em&gt;fedora30&lt;/em&gt;. Open the &lt;code&gt;build.sh&lt;/code&gt;&#10;file and follow along here:&lt;/p&gt;&#10;&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="c1"&gt;# Use vfs with buildah. Docker offers overlayfs as a default, but buildah&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# cannot stack overlayfs on top of another overlayfs filesystem.&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;export&lt;/span&gt; &lt;span class="nv"&gt;STORAGE_DRIVER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;vfs&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;First off, we need to tell buildah to use the vfs storage driver. Docker uses&#10;overlayfs by default and stacking overlay filesystems will definitely lead to&#10;problems. Buildah won&amp;rsquo;t let you try it.&lt;/p&gt;&#10;&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="c1"&gt;# Write all image metadata in the docker format, not the standard OCI format.&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Newer versions of docker can handle the OCI format, but older versions, like&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# the one shipped with Fedora 30, cannot handle the format.&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;export&lt;/span&gt; &lt;span class="nv"&gt;BUILDAH_FORMAT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;docker&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;By default, buildah uses the &lt;a href="https://github.com/opencontainers/image-spec"&gt;&lt;em&gt;oci&lt;/em&gt; container format&lt;/a&gt;. This sometimes causes&#10;issues with older versions of Docker that don&amp;rsquo;t understand how to parse that&#10;type of metadata. By setting the format to &lt;code&gt;docker&lt;/code&gt;, we&amp;rsquo;re using a format&#10;that almost all container runtimes can understand.&lt;/p&gt;&#10;&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="c1"&gt;# Log into GitLab&amp;#39;s container repository.&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;export&lt;/span&gt; &lt;span class="nv"&gt;REGISTRY_AUTH_FILE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;HOME&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;/auth.json&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$CI_REGISTRY_PASSWORD&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; buildah login -u &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$CI_REGISTRY_USER&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; --password-stdin &lt;span class="nv"&gt;$CI_REGISTRY&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Here we set a path for the &lt;code&gt;auth.json&lt;/code&gt; that contains the credentials for&#10;talking to the container repository. We also use buildah to authenticate to&#10;GitLab&amp;rsquo;s built-in container repository. GitLab automatically exports these&#10;variables for us (and hides them in the job output), so we can use them here.&lt;/p&gt;&#10;&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;buildah bud -f builds/&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;IMAGE_NAME&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; -t &lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;IMAGE_NAME&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; .&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;We&amp;rsquo;re now building the container and storing it temporarily as the bare image&#10;name, such as &lt;em&gt;fedora30&lt;/em&gt;. This is roughly equivalent to &lt;code&gt;docker build&lt;/code&gt;.&lt;/p&gt;&#10;&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="nv"&gt;CONTAINER_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;buildah from &lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;IMAGE_NAME&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="k"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;buildah commit --squash &lt;span class="nv"&gt;$CONTAINER_ID&lt;/span&gt; &lt;span class="nv"&gt;$FQ_IMAGE_NAME&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now we are making a reference to our container with &lt;code&gt;buildah from&lt;/code&gt; and using&#10;that reference to squash that container down into a single layer. This keeps&#10;the container as small as possible.&lt;/p&gt;&#10;&lt;p&gt;The &lt;code&gt;commit&lt;/code&gt; step also tags the resulting image using our fully qualified&#10;image name (in this case, it&amp;rsquo;s&#10;&lt;code&gt;registry.gitlab.com/majorhayden/os-containers/fedora30:latest&lt;/code&gt;)&lt;/p&gt;&#10;&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;buildah push &lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;FQ_IMAGE_NAME&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This is the same as &lt;code&gt;docker push&lt;/code&gt;. There&amp;rsquo;s not much special to see here.&lt;/p&gt;&#10;&lt;h2 id="maintaining-containers"&gt;Maintaining containers&lt;/h2&gt;&#10;&lt;p&gt;GitLab allows you to take things to the next level with CI schedules. In my&#10;repository, there is a schedule to build my containers once a day to catch&#10;the latest updates. I use these containers a lot and they need to be up to&#10;date before I can run tests.&lt;/p&gt;&#10;&lt;p&gt;If the container build fails for some reason, GitLab will send me an email to&#10;let me know.&lt;/p&gt;&#10;</description></item><item><title>Use a secret as an environment variable in OpenShift deployments</title><link>https://major.io/p/use-secret-as-environment-variable-in-openshift-deployments/</link><pubDate>Thu, 06 Dec 2018 00:00:00 +0000</pubDate><author>major@mhtx.net (Major Hayden)</author><guid>https://major.io/p/use-secret-as-environment-variable-in-openshift-deployments/</guid><description>&lt;p&gt;OpenShift &lt;a href="https://docs.openshift.com/container-platform/3.9/dev_guide/deployments/how_deployments_work.html"&gt;deployments&lt;/a&gt; allow you to take a container image and run it within a&#10;cluster. You can easily add extra items to the deployment, such as&#10;environment variables or volumes.&lt;/p&gt;&#10;&lt;p&gt;The best practice for sensitive environment variables is to place them into a&#10;&lt;a href="https://docs.openshift.com/container-platform/3.9/dev_guide/secrets.html"&gt;secret object&lt;/a&gt; rather than directly in the deployment configuration itself.&#10;Although this keeps the secret data out of the deployment, the environment&#10;variable is still exposed to the running application inside the container.&lt;/p&gt;&#10;&lt;h2 id="creating-a-secret"&gt;Creating a secret&lt;/h2&gt;&#10;&lt;p&gt;Let&amp;rsquo;s start with a snippet of a &lt;code&gt;deploymentConfig&lt;/code&gt; that has a sensitive&#10;environment variable in plain text:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yml" data-lang="yml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;containers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;MYAPP_SECRET_TOKEN&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;vPWps5E7KO8KPlljaD3eXED3f6jmLsV5mQ&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;fedora:29&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;my_app&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The first step is to create a secret object that contains our sensitive&#10;environment variable:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yml" data-lang="yml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;apiVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;v1&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Secret&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;secret-token-for-my-app&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;stringData&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;SECRET_TOKEN&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;vPWps5E7KO8KPlljaD3eXED3f6jmLsV5mQ&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Save this file as &lt;code&gt;secret-token.yml&lt;/code&gt; and deploy it to OpenShift:&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;oc apply -f secret-token.yml&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Query OpenShift to ensure the secret is ready to use:&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;$ oc get secret/secret-token-for-my-app&#10;NAME TYPE DATA AGE&#10;secret-token-for-my-app Opaque 1 1h&#10;&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="using-the-secret"&gt;Using the secret&lt;/h2&gt;&#10;&lt;p&gt;We can adjust the deployment configuration to use this new secret:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yml" data-lang="yml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;containers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;MYAPP_SECRET_TOKEN&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;valueFrom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;secretKeyRef&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;SECRET_TOKEN&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;secret-token-for-my-app&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;fedora:29&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;my_app&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This configuration tells OpenShift to look inside the secret object called&#10;&lt;code&gt;secret-token-for-my-app&lt;/code&gt; for a key matching &lt;code&gt;SECRET_TOKEN&lt;/code&gt;. The value will&#10;be passed into the &lt;code&gt;MYAPP_SECRET_TOKEN&lt;/code&gt; environment variable and it will be&#10;available to the application running in the container.&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;Security note:&lt;/strong&gt; If someone has access to change the deployment&#10;configuration object, they could get access to the value of the secret&#10;without having direct access to the secret object itself. It would be trivial&#10;to change the startup command in the container to print all of the&#10;environment variables in the container (using the &lt;code&gt;env&lt;/code&gt; command) and view them&#10;in the container logs.&lt;/p&gt;&#10;</description></item><item><title>Research Paper: Securing Linux Containers</title><link>https://major.io/p/research-paper-securing-linux-containers/</link><pubDate>Fri, 14 Aug 2015 20:45:50 +0000</pubDate><author>major@mhtx.net (Major Hayden)</author><guid>https://major.io/p/research-paper-securing-linux-containers/</guid><description>&lt;p&gt;It seems like there&amp;rsquo;s a new way to run containers every week. The advantages and drawbacks of each approach are argued about on mailing lists, in IRC channels, and in person, around the world. However, the largest amount of confusion seems to be around security.&lt;/p&gt;&#10;&lt;h3 id="launching-secure-containers"&gt;Launching secure containers&lt;/h3&gt;&#10;&lt;p&gt;I&amp;rsquo;ve written about launching secure containers on this blog many times before:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;a href="https://major.io/2014/04/21/launch-secure-lxc-containers-on-fedora-20-using-selinux-and-svirt/"&gt;Launch secure LXC containers on Fedora 20 using SELinux and sVirt&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://major.io/2015/06/18/improving-lxc-template-security/"&gt;Improving LXC template security&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://major.io/2014/12/17/try-lxc-ansible-playbook/"&gt;Try out LXC with an Ansible playbook&lt;/a&gt;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;However, my goal this time around was to do something more comprehensive and slightly more formal. After getting my &lt;a href="http://www.giac.org/certified-professional/major-hayden/138471"&gt;GSEC and GCUX&lt;/a&gt; certifications from &lt;a href="http://www.giac.org/"&gt;SANS/GIAC&lt;/a&gt;, there was an option to enhance the certification to a gold status by &lt;a href="http://www.giac.org/certifications/gold"&gt;writing a peer-reviewed research paper&lt;/a&gt; on a topic related to the exam. It was a great experience to go through the review process and get feedback on the technical material as well as the structure of the paper itself.&lt;/p&gt;&#10;&lt;h3 id="the-paper"&gt;The paper&lt;/h3&gt;&#10;&lt;p&gt;Without further ado, here are links to the &lt;em&gt;Securing Linux Containers&lt;/em&gt; paper:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;a href="https://major.io/wp-content/uploads/2015/08/Securing-Linux-Containers-GCUX-Gold-Paper-Major-Hayden.pdf"&gt;PDF version without watermarks&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://www.sans.org/reading-room/whitepapers/linux/securing-linux-containers-36142"&gt;PDF version from SANS&lt;/a&gt; &lt;em&gt;(has some watermarks and SANS/GIAC extra pages)&lt;/em&gt;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;The paper is written for readers who have some level of familiarity with Linux and some virtualization technologies. It&amp;rsquo;s a useful paper even for people who haven&amp;rsquo;t worked with containers.&lt;/p&gt;&#10;&lt;p&gt;It starts with an overview of Linux containers and how they differ from other types of virtualization, such as KVM or Xen. From there, it covers how to secure the host system underneath the containers and how to provide security within the containers themselves. There&amp;rsquo;s also a section on how to start a simple container on CentOS 7 and inspect the security controls inside and outside the container.&lt;/p&gt;&#10;&lt;h3 id="licensing"&gt;Licensing&lt;/h3&gt;&#10;&lt;p&gt;I&amp;rsquo;m also very proud to announce that the paper is licensed under the &lt;a href="http://creativecommons.org/licenses/by-sa/4.0/"&gt;Creative Commons Attribution-ShareAlike 4.0 International License (CC-BY-SA)&lt;/a&gt;. You are free to quote it as much as you like (even for commercial purposes), but I&amp;rsquo;d ask that you maintain the same license and attribute me as the author.&lt;/p&gt;&#10;&lt;h3 id="thank-you"&gt;Thank you&lt;/h3&gt;&#10;&lt;p&gt;This paper wouldn&amp;rsquo;t have been possible without some serious help from these awesome people:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;strong&gt;Richard Carbone&lt;/strong&gt; was my advisor from SANS and he helped tremendously&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Dan Walsh&lt;/strong&gt; reviewed the content and gave me several pointers on topics to add and adjust&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Paul Voccio, Antony Messerli, and Brad McConnell&lt;/strong&gt; from Rackspace also provided feedback&lt;/li&gt;&#10;&lt;li&gt;My mother, &lt;strong&gt;Neta Greene&lt;/strong&gt;, is the best educator I know and she fueled my interest in writing and sharing with others&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h3 id="feedback"&gt;Feedback&lt;/h3&gt;&#10;&lt;p&gt;Please let me know if you spot any errors or areas that need clarification. This is one of my favorite topics and I enjoy talking about it. Find me on Freenode IRC as &lt;em&gt;mhayden&lt;/em&gt; and I&amp;rsquo;ll be glad to talk more there.&lt;/p&gt;&#10;</description></item><item><title>Improving LXC template security</title><link>https://major.io/p/improving-lxc-template-security/</link><pubDate>Thu, 18 Jun 2015 19:52:11 +0000</pubDate><author>major@mhtx.net (Major Hayden)</author><guid>https://major.io/p/improving-lxc-template-security/</guid><description>&lt;p&gt;&lt;a href="https://major.io/wp-content/uploads/2015/06/containers.png"&gt;&lt;img src="https://major.io/wp-content/uploads/2015/06/containers-300x276.png" alt="LXC logo" width="300" height="276" class="alignright size-medium wp-image-5669" srcset="https://major.io/wp-content/uploads/2015/06/containers-300x276.png 300w, https://major.io/wp-content/uploads/2015/06/containers.png 318w" sizes="(max-width: 300px) 100vw, 300px" /&gt;&lt;/a&gt;I&amp;rsquo;ve been getting involved with the &lt;a href="https://fedoraproject.org/wiki/Security_Team"&gt;Fedora Security Team&lt;/a&gt; lately and we&amp;rsquo;re working as a group to crush security bugs that affect Fedora, CentOS (via EPEL) and Red Hat Enterprise Linux (via EPEL). During some of this work, I stumbled upon a &lt;a href="https://bugzilla.redhat.com/show_bug.cgi?id=1132001"&gt;group of Red Hat Bugzilla tickets&lt;/a&gt; talking about LXC template security.&lt;/p&gt;&#10;&lt;p&gt;The gist of the problem is that there&amp;rsquo;s a wide variance in how users and user credentials are handled by the different LXC templates. An &lt;a href="https://fedoraproject.org/wiki/LXC_Template_Security_Improvements"&gt;inventory of the current situation&lt;/a&gt; revealed some horrifying problems with many OS templates.&lt;/p&gt;&#10;&lt;p&gt;Many of the templates set an &lt;strong&gt;awful&lt;/strong&gt; default root password, like rooter, toor, or root. Some of the others create a regular user with sudo privileges and give it a default, predictable password unless the user specifies otherwise.&lt;/p&gt;&#10;&lt;p&gt;There are some bright spots, though. Fedora and CentOS templates will accept a root password from the user during the build and set a randomized password for the root user if a password isn&amp;rsquo;t specified. Ubuntu Cloud takes another approach by locking out the root user and requiring cloud-init configuration data to configure the root account.&lt;/p&gt;&#10;&lt;p&gt;I kicked off a &lt;a href="https://lists.linuxcontainers.org/pipermail/lxc-devel/2015-June/011883.html"&gt;mailing list thread&lt;/a&gt; and wrote a &lt;a href="https://github.com/lxc/lxc/pull/574"&gt;terrible pull request&lt;/a&gt; to get things underway. Stéphane Graber requested that all templates use a shared script to handle users and credentials via standardized environment variables and command line arguments. In addition, all passwords for users (regular or root) should be empty with password-less logins disabled. Those are some reasonable requests and I&amp;rsquo;m working on a shell script that&amp;rsquo;s easy to import into LXC templates.&lt;/p&gt;&#10;&lt;p&gt;There&amp;rsquo;s also a push to remove sshd from all LXC templates by default, but I&amp;rsquo;m hoping to keep that one tabled until the credentials issue is solved.&lt;/p&gt;&#10;&lt;p&gt;If you&amp;rsquo;d like to help out with the effort, let me know! I&amp;rsquo;ll probably get some code up onto Github soon and as for comments.&lt;/p&gt;&#10;</description></item><item><title>Try out LXC with an Ansible playbook</title><link>https://major.io/p/try-lxc-ansible-playbook/</link><pubDate>Wed, 17 Dec 2014 13:50:26 +0000</pubDate><author>major@mhtx.net (Major Hayden)</author><guid>https://major.io/p/try-lxc-ansible-playbook/</guid><description>&lt;p&gt;&lt;a href="https://major.io/wp-content/uploads/2014/08/image-ansible.png"&gt;&lt;img src="https://major.io/wp-content/uploads/2014/08/image-ansible-300x300.png" alt="Ansible logo" width="300" height="300" class="alignright size-medium wp-image-5157" srcset="https://major.io/wp-content/uploads/2014/08/image-ansible-300x300.png 300w, https://major.io/wp-content/uploads/2014/08/image-ansible-150x150.png 150w, https://major.io/wp-content/uploads/2014/08/image-ansible.png 700w" sizes="(max-width: 300px) 100vw, 300px" /&gt;&lt;/a&gt;The world of containers is constantly evolving lately. The latest turn of events involves the CoreOS developers when they announced &lt;a href="https://coreos.com/blog/rocket/"&gt;Rocket&lt;/a&gt; as an alternative to &lt;a href="https://www.docker.com/"&gt;Docker&lt;/a&gt;. However, &lt;a href="https://linuxcontainers.org/"&gt;LXC&lt;/a&gt; still lingers as a very simple path to begin using containers.&lt;/p&gt;&#10;&lt;p&gt;When I talk to people about LXC, I often hear people talk about how difficult it is to get started with LXC. After all, Docker provides an easy-to-use image downloading function that allows you to spin up multiple different operating systems in Docker containers within a few minutes. It also comes with a daemon to help you manage your images and your containers.&lt;/p&gt;&#10;&lt;p&gt;Managing LXC containers using the basic LXC tools isn&amp;rsquo;t terribly easy - I&amp;rsquo;ll give you that. However, managing LXC through &lt;a href="https://libvirt.org/drvlxc.html"&gt;libvirt&lt;/a&gt; makes the process much easier. I &lt;a href="https://major.io/2014/04/21/launch-secure-lxc-containers-on-fedora-20-using-selinux-and-svirt/"&gt;wrote a little about this&lt;/a&gt; earlier in the year.&lt;/p&gt;&#10;&lt;p&gt;I decided to turn the LXC container deployment process into an &lt;a href="https://github.com/major/ansible-lxc"&gt;Ansible playbook&lt;/a&gt; that you can use to automatically spawn an LXC container on any server or virtual machine. At the moment, only Fedora 20 and 21 are supported. I plan to add CentOS 7 and Debian support soon.&lt;/p&gt;&#10;&lt;p&gt;Clone the repository to get started:&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;git clone https://github.com/major/ansible-lxc.git&#10;cd ansible-lxc&#10;ansible-playbook -i hosts playbook.yml&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;If you&amp;rsquo;re running the playbook on the actual server or virtual machine where you want to run LXC, there&amp;rsquo;s no need to alter the &lt;code&gt;hosts&lt;/code&gt; file. You will need to adjust it if you&amp;rsquo;re running your playbook from a remote machine.&lt;/p&gt;&#10;&lt;p&gt;As the playbook runs, it will install all of the necessary packages and begin assembling a Fedora 21 chroot. It will register the container with libvirt and do some basic configuration of the chroot so that it will work as a container. You&amp;rsquo;ll end up with a running Fedora 21 LXC container that is using the built-in default NAT network created by libvirt. The playbook will print out the IP address of the container at the end. The default password for root is &lt;em&gt;fedora&lt;/em&gt;. I wouldn&amp;rsquo;t recommend leaving that for a production use container. ;)&lt;/p&gt;&#10;&lt;p&gt;All of the normal &lt;code&gt;virsh&lt;/code&gt; commands should work on the container. For example:&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;# Stop the container gracefully&#10;virsh shutdown fedora21&#10;# Start the container&#10;virsh start fedora21&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Feel free to install the virt-manager tool and manage everything via a GUI locally or via X forwarding:&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;yum -y install virt-manager dejavu* xorg-x11-xauth&#10;# OPTIONAL: For a better looking virt-manager interface, install these, too&#10;yum -y install gnome-icon-theme gnome-themes-standard&#10;&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>Launch secure LXC containers on Fedora 20 using SELinux and sVirt</title><link>https://major.io/p/launch-secure-lxc-containers-on-fedora-20-using-selinux-and-svirt/</link><pubDate>Tue, 22 Apr 2014 04:11:00 +0000</pubDate><author>major@mhtx.net (Major Hayden)</author><guid>https://major.io/p/launch-secure-lxc-containers-on-fedora-20-using-selinux-and-svirt/</guid><description>&lt;p&gt;&lt;img src="https://major.io/wp-content/uploads/2013/07/selinux-penguin-new_medium.png" alt="1"&gt;&lt;/p&gt;&#10;&lt;p&gt;Getting started with &lt;a href="https://en.wikipedia.org/wiki/LXC"&gt;LXC&lt;/a&gt; is a bit awkward and I&amp;rsquo;ve assembled this guide for anyone who wants to begin experimenting with LXC containers in Fedora 20. As an added benefit, you can follow almost every step shown here when creating LXC containers on &lt;a href="https://access.redhat.com/site/products/Red_Hat_Enterprise_Linux/Get-Beta"&gt;Red Hat Enterprise Linux 7&lt;/a&gt; Beta (which is based on Fedora 19).&lt;/p&gt;&#10;&lt;p&gt;You&amp;rsquo;ll need a physical machine or a VM running Fedora 20 to get started. &lt;span style="color: #888888"&gt;(You could put a container in a container, but things get a little dicey with that setup. Let&amp;rsquo;s just avoid talking about nested containers for now. No, really, I shouldn&amp;rsquo;t have even brought it up. Sorry about that.)&lt;/span&gt;&lt;/p&gt;&#10;&lt;h3 id="prep-work"&gt;Prep Work&lt;/h3&gt;&#10;&lt;p&gt;Start by updating all packages to the latest versions available:&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;yum -y upgrade&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Verify that SELinux is in enforcing mode by running &lt;code&gt;getenforce&lt;/code&gt;. If you see &lt;em&gt;Disabled&lt;/em&gt; or &lt;em&gt;Permissive&lt;/em&gt;, get SELinux into enforcing mode with a quick configuration change:&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;sed -i &amp;#39;s/^SELINUX=.*/SELINUX=enforcing/&amp;#39; /etc/selinux/config&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;I recommend installing &lt;code&gt;setroubleshoot-server&lt;/code&gt; to make it easier to find the root cause of AVC denials:&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code class="language-yum" data-lang="yum"&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Reboot now. This will ensure that SELinux comes up in enforcing mode (verify that with &lt;code&gt;getenforce&lt;/code&gt; after reboot) and it ensures that auditd starts up sedispatch (for setroubleshoot).&lt;/p&gt;&#10;&lt;h3 id="install-management-libraries-and-utilities"&gt;Install management libraries and utilities&lt;/h3&gt;&#10;&lt;p&gt;Let&amp;rsquo;s grab libvirt along with LXC support and a basic NAT networking configuration.&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;yum -y install libvirt-daemon-lxc libvirt-daemon-config-network&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Launch libvirtd via systemd and ensure that it always comes up on boot. This step will also adjust firewalld for your containers and ensure that dnsmasq is serving up IP addresses via DHCP on your default NAT network.&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;systemctl start libvirtd.service&#10;systemctl enable libvirtd.service&#10;&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="bootstrap-our-container"&gt;Bootstrap our container&lt;/h3&gt;&#10;&lt;p&gt;Installing packages into the container&amp;rsquo;s filesystem will take some time.&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;yum -y --installroot=/var/lib/libvirt/filesystems/fedora20 --releasever=20 --nogpg install systemd passwd yum fedora-release vim-minimal openssh-server procps-ng iproute net-tools dhclient&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This step fills in the filesystem with the necessary packages to run a Fedora 20 container. We now need to tell libvirt about the container we&amp;rsquo;ve just created.&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;virt-install --connect lxc:// --name fedora20 --ram 512 --filesystem /var/lib/libvirt/filesystems/fedora20/,/&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;At this point, libvirt will know enough about the container to start it and you&amp;rsquo;ll be connected to the console of the container! We need to adjust some configuration files within the container to use it properly. Detach from the console with CTRL-].&lt;/p&gt;&#10;&lt;p&gt;Let&amp;rsquo;s stop the container so we can make some adjustments.&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;virsh -c lxc:// shutdown fedora20&#10;&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="get-the-container-ready-for-production"&gt;Get the container ready for production&lt;/h3&gt;&#10;&lt;p&gt;Hop into your container and set a root password.&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;chroot /var/lib/libvirt/filesystems/fedora20 /bin/passwd root&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We will be logging in as root via the console occasionally and we need to allow that access.&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;echo &amp;#34;pts/0&amp;#34; &amp;gt;&amp;gt; /var/lib/libvirt/filesystems/fedora20/etc/securetty&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Since we will be using our NAT network with our auto-configured dnsmasq server (thanks to libvirt), we can configure a simple DHCP setup for eth0:&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;cat &amp;lt; &amp;lt; EOF &amp;gt; /var/lib/libvirt/filesystems/fedora20/etc/sysconfig/network&#10;NETWORKING=yes&#10;EOF&#10;cat &amp;lt; &amp;lt; EOF &amp;gt; /var/lib/libvirt/filesystems/fedora20/etc/sysconfig/network-scripts/ifcfg-eth0&#10;BOOTPROTO=dhcp&#10;ONBOOT=yes&#10;DEVICE=eth0&#10;EOF&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Using ssh makes the container a lot easier to manage, so let&amp;rsquo;s ensure that it starts when the container boots. (You could do this via systemctl after logging in at the console, but I&amp;rsquo;m lazy.)&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;chroot /var/lib/libvirt/filesystems/fedora20/&#10;ln -s /usr/lib/systemd/system/sshd.service /etc/systemd/system/multi-user.target.wants/&#10;exit&#10;&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="launch"&gt;Launch!&lt;/h3&gt;&#10;&lt;p&gt;Cross your fingers and launch the container.&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;virsh -c lxc:// start --console fedora20&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;You&amp;rsquo;ll be attached to the console during boot but don&amp;rsquo;t worry, hold down CTRL-] to get back to your host prompt. Check the dnsmasq leases to find your container&amp;rsquo;s IP address and you can login as root over ssh.&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;cat /var/lib/libvirt/dnsmasq/default.leases&#10;&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="security"&gt;Security&lt;/h3&gt;&#10;&lt;p&gt;After logging into your container via ssh, check the process labels within the container:&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;# ps aufxZ&#10;LABEL USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND&#10;system_u:system_r:virtd_lxc_t:s0-s0:c0.c1023 root 1 0.0 1.3 47444 3444 ? Ss 03:18 0:00 /sbin/init&#10;system_u:system_r:virtd_lxc_t:s0-s0:c0.c1023 root 18 0.0 2.0 43016 5368 ? Ss 03:18 0:00 /usr/lib/systemd/systemd-journald&#10;system_u:system_r:virtd_lxc_t:s0-s0:c0.c1023 root 38 0.4 7.8 223456 20680 ? Ssl 03:18 0:00 /usr/bin/python -Es /usr/sbin/firewalld -&#10;system_u:system_r:virtd_lxc_t:s0-s0:c0.c1023 root 40 0.0 0.7 26504 2084 ? Ss 03:18 0:00 /usr/sbin/smartd -n -q never&#10;system_u:system_r:virtd_lxc_t:s0-s0:c0.c1023 root 41 0.0 0.4 19268 1252 ? Ss 03:18 0:00 /usr/sbin/irqbalance --foreground&#10;system_u:system_r:virtd_lxc_t:s0-s0:c0.c1023 root 44 0.0 0.6 34696 1636 ? Ss 03:18 0:00 /usr/lib/systemd/systemd-logind&#10;system_u:system_r:virtd_lxc_t:s0-s0:c0.c1023 root 46 0.0 1.8 267500 4832 ? Ssl 03:18 0:00 /sbin/rsyslogd -n&#10;system_u:system_r:virtd_lxc_t:s0-s0:c0.c1023 dbus 47 0.0 0.6 26708 1680 ? Ss 03:18 0:00 /bin/dbus-daemon --system --address=syste&#10;system_u:system_r:virtd_lxc_t:s0-s0:c0.c1023 rpc 54 0.0 0.5 41992 1344 ? Ss 03:18 0:00 /sbin/rpcbind -w&#10;system_u:system_r:virtd_lxc_t:s0-s0:c0.c1023 root 55 0.0 0.3 25936 924 ? Ss 03:18 0:00 /usr/sbin/atd -f&#10;system_u:system_r:virtd_lxc_t:s0-s0:c0.c1023 root 56 0.0 0.5 22728 1488 ? Ss 03:18 0:00 /usr/sbin/crond -n&#10;system_u:system_r:virtd_lxc_t:s0-s0:c0.c1023 root 60 0.0 0.2 6412 784 pts/0 Ss+ 03:18 0:00 /sbin/agetty --noclear -s console 115200&#10;system_u:system_r:virtd_lxc_t:s0-s0:c0.c1023 root 74 0.0 3.2 339808 8456 ? Ssl 03:18 0:00 /usr/sbin/NetworkManager --no-daemon&#10;system_u:system_r:virtd_lxc_t:s0-s0:c0.c1023 root 394 0.0 5.9 102356 15708 ? S 03:18 0:00 \_ /sbin/dhclient -d -sf /usr/libexec/nm&#10;system_u:system_r:virtd_lxc_t:s0-s0:c0.c1023 polkitd 83 0.0 4.4 514792 11548 ? Ssl 03:18 0:00 /usr/lib/polkit-1/polkitd --no-debug&#10;system_u:system_r:virtd_lxc_t:s0-s0:c0.c1023 rpcuser 110 0.0 0.6 46564 1824 ? Ss 03:18 0:00 /sbin/rpc.statd&#10;system_u:system_r:virtd_lxc_t:s0-s0:c0.c1023 root 111 0.0 1.3 82980 3620 ? Ss 03:18 0:00 /usr/sbin/sshd -D&#10;system_u:system_r:virtd_lxc_t:s0-s0:c0.c1023 root 409 0.0 1.9 131576 5084 ? Ss 03:18 0:00 \_ sshd: root@pts/1&#10;system_u:system_r:virtd_lxc_t:s0-s0:c0.c1023 root 413 0.0 0.9 115872 2592 pts/1 Ss 03:18 0:00 \_ -bash&#10;system_u:system_r:virtd_lxc_t:s0-s0:c0.c1023 root 438 0.0 0.5 123352 1344 pts/1 R+ 03:19 0:00 \_ ps aufxZ&#10;system_u:system_r:virtd_lxc_t:s0-s0:c0.c1023 root 411 0.0 0.8 44376 2252 ? Ss 03:18 0:00 /usr/lib/systemd/systemd --user&#10;system_u:system_r:virtd_lxc_t:s0-s0:c0.c1023 root 412 0.0 0.5 66828 1328 ? S 03:18 0:00 \_ (sd-pam)&#10;system_u:system_r:virtd_lxc_t:s0-s0:c0.c1023 root 436 0.0 0.4 21980 1144 ? Ss 03:19 0:00 /usr/lib/systemd/systemd-hostnamed&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;You&amp;rsquo;ll notice something interesting if you run &lt;code&gt;getenforce&lt;/code&gt; now within the container — SELinux is disabled. Actually, it&amp;rsquo;s not really disabled. The processing of SELinux policy is done on the host. The container isn&amp;rsquo;t able to see what&amp;rsquo;s going on outside of its own files and processes. The &lt;a href="http://libvirt.org/drvlxc.html#security"&gt;libvirt documentation for LXC&lt;/a&gt; hints at the importance of this isolation:&lt;/p&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;A suitably configured UID/GID mapping is a pre-requisite to making containers secure, in the absence of sVirt confinement.&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;In the absence of the “user” namespace being used, containers cannot be considered secure against exploits of the host OS. The sVirt SELinux driver provides a way to secure containers even when the “user” namespace is not used. The cost is that writing a policy to allow execution of arbitrary OS is not practical. The SELinux sVirt policy is typically tailored to work with an simpler application confinement use case, as provided by the “libvirt-sandbox” project.&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;This leads to something really critical to understand:&lt;/p&gt;&#10;&lt;h3 id="containers-dont-contain"&gt;Containers don&amp;rsquo;t contain&lt;/h3&gt;&#10;&lt;p&gt;Dan Walsh has a &lt;a href="https://danwalsh.livejournal.com/30565.html"&gt;great post&lt;/a&gt; that goes into the need for sVirt and the protections it can provide when you need to be insulated from potentially dangerous virtual machines or containers. If a user is root inside a container, they&amp;rsquo;re root on the host as well. &lt;span style="color: #888888"&gt;(There&amp;rsquo;s an exception: &lt;a href="https://lwn.net/Articles/436445/"&gt;UID namespaces&lt;/a&gt;. But let&amp;rsquo;s not talk about that now. Oh great, first it was nested containers and now I brought up UID namespaces. Sorry again.)&lt;/span&gt;&lt;/p&gt;&#10;&lt;p&gt;Dan&amp;rsquo;s talk about securing containers hasn&amp;rsquo;t popped up on the &lt;a href="http://www.redhat.com/summit/2014/presentations/"&gt;Red Hat Summit presentations&lt;/a&gt; page quite yet but here are some notes that I took and then highlighted:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Containers don&amp;rsquo;t contain. The kernel doesn&amp;rsquo;t know about containers. Containers simply use kernel subsystems to carve up namespaces for applications.&lt;/li&gt;&#10;&lt;li&gt;Containers on Linux aren&amp;rsquo;t complete. Don&amp;rsquo;t compare directly to Solaris zones yet.&lt;/li&gt;&#10;&lt;li&gt;Running containers without Mandatory Access Control (MAC) systems like SELinux or AppArmor opens the door for full system compromise via untrusted applications and users within containers.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;Using MAC gives you one extra barrier to keep a malicious container from getting higher levels of access to the underlying host. There&amp;rsquo;s always a chance that a kernel exploit could bypass MAC but it certainly raises the level of difficulty for an attacker and allows server operators extra time to react to alerts.&lt;/p&gt;&#10;</description></item><item><title>Docker, trusted builds, and Fedora 20</title><link>https://major.io/p/docker-trusted-builds-and-fedora-20/</link><pubDate>Wed, 26 Mar 2014 05:17:58 +0000</pubDate><author>major@mhtx.net (Major Hayden)</author><guid>https://major.io/p/docker-trusted-builds-and-fedora-20/</guid><description>&lt;p&gt;Docker is a hot topic in the Linux world at the moment and I decided to try out the new &lt;a href="http://blog.docker.io/2013/11/introducing-trusted-builds/"&gt;trusted build process&lt;/a&gt;. Long story short, you put your Dockerfile along with any additional content into your GitHub repository, link your GitHub account with Docker, and then fire off a build. The Docker index labels it as &amp;ldquo;trusted&amp;rdquo; since it was build from source files in your repository.&lt;/p&gt;&#10;&lt;p&gt;I set off to build a Dockerfile to provision a container that would run all of the &lt;a href="https://major.io/icanhazip-com-faq/"&gt;icanhazip&lt;/a&gt; services. Getting httpd running was a little tricky, but I soon had a &lt;a href="https://github.com/major/icanhaz/blob/master/docker/Dockerfile"&gt;working Dockerfile&lt;/a&gt; that built and ran successfully on Fedora 20.&lt;/p&gt;&#10;&lt;p&gt;The trusted build process kicked off without much fuss and I found myself waiting for a couple of hours for my job to start. I was sad to see an error after waiting so long:&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;Installing : httpd-2.4.7-3.fc20.x86_64&#10;error: unpacking of archive failed on file /usr/sbin/suexec: cpio: cap_set_file&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Well, that&amp;rsquo;s weird. It turns out that &lt;code&gt;cap_set_file&lt;/code&gt; is part of libcap that sets filesystem capabilities based on the POSIX.1e standards. You can read up on capabilities in the &lt;a href="https://www.kernel.org/pub/linux/libs/security/linux-privs/kernel-2.2/capfaq-0.2.txt"&gt;Linux kernel capabilities FAQ&lt;/a&gt;. &lt;em&gt;(Special thanks to Andrew Clayton getting me pointed in the right direction there.)&lt;/em&gt;&lt;/p&gt;&#10;&lt;p&gt;&lt;a href="http://fedoraproject.org/wiki/User:Goldmann"&gt;Marek Goldmann&lt;/a&gt; ran into this problem back in September 2013 and opened a &lt;a href="https://bugzilla.redhat.com/show_bug.cgi?id=1012952"&gt;bug report&lt;/a&gt;. Marek &lt;a href="https://bugzilla.redhat.com/attachment.cgi?id=804061&amp;amp;action=diff"&gt;proposed a change&lt;/a&gt; to the Docker codebase that would remove setfcap from the list of banned capabilities in the LXC template used by docker. Another workaround would be to use the &lt;code&gt;-privileged&lt;/code&gt; option to perform a build in privileged mode (available in docker 0.6+).&lt;/p&gt;&#10;&lt;p&gt;Both of those workarounds are unavailable when doing trusted builds with docker&amp;rsquo;s index. Sigh.&lt;/p&gt;&#10;&lt;p&gt;I fired off an email to Docker&amp;rsquo;s support staff and received a quick reply:&lt;/p&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;Major,&lt;/p&gt;&#10;&lt;p&gt;We are aware of this issue, and we are currently working on a fix, and we hope to have something we can start testing this week. I&amp;rsquo;m not sure when we will be able to roll out the fix, but we are hoping soon. Until then, there isn&amp;rsquo;t anything you can do to work around it. Sorry for the inconvenience.&lt;/p&gt;&#10;&lt;p&gt;If anything changes, we will be sure to let you know.&lt;/p&gt;&#10;&lt;p&gt;Ken&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;It wasn&amp;rsquo;t the answer I wanted but it&amp;rsquo;s good to know that the issue is being worked. In the meantime, I&amp;rsquo;ll push an untrusted build of the icanhazip Docker container up to the index for everyone to enjoy.&lt;/p&gt;&#10;&lt;p&gt;Stay tuned for updates.&lt;/p&gt;&#10;&lt;p&gt;&lt;em&gt;&lt;strong&gt;UPDATED 2014-08-08:&lt;/strong&gt; Per Thomas&amp;rsquo; comment below, this has been &lt;a href="https://github.com/docker/docker/pull/5930"&gt;fixed upstream&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;&#10;</description></item></channel></rss>