<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Quadlets on ascia.tech</title>
    <link>https://ascia.tech/tags/quadlets/</link>
    <description>Recent content in Quadlets on ascia.tech</description>
    <generator>Hugo -- gohugo.io</generator>
    <language>en-US</language>
    <managingEditor>cmhobbs@ascia.tech (C.M. Hobbs)</managingEditor>
    <webMaster>cmhobbs@ascia.tech (C.M. Hobbs)</webMaster>
    <copyright>C.M. Hobbs</copyright>
    <lastBuildDate>Thu, 23 Apr 2026 00:00:00 +0000</lastBuildDate>
    <atom:link href="https://ascia.tech/tags/quadlets/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>SmallOps Part 2:  Quadlets</title>
      <link>https://ascia.tech/blog/smallops-with-podman-pt2/</link>
      <pubDate>Thu, 23 Apr 2026 00:00:00 +0000</pubDate><author>cmhobbs@ascia.tech (C.M. Hobbs)</author>
      <guid>https://ascia.tech/blog/smallops-with-podman-pt2/</guid>
      <description>&lt;p&gt;In &lt;a href=&#34;https://ascia.tech/blog/smallops-with-podman-pt1/&#34;&gt;Part 1&lt;/a&gt;, I walked through how I deploy&#xA;rootless podman containers as systemd user units in my homelab using jinja2&#xA;templates.  I also said I don&amp;rsquo;t recommend that approach for anything new and&#xA;that I replaced it with Quadlets in some client deployments.  This post is&#xA;about that replacement.  I hope to migrate my homelab to quadlets in the&#xA;near future but inertia is a bear&amp;hellip;&lt;/p&gt;&#xA;&lt;p&gt;For the most part, my templates and systemd units in the homelab work just fine and there isn&amp;rsquo;t a&#xA;lot of reason for me to rip them out at the moment.  However, after reading&#xA;about Quadlets, I think they&amp;rsquo;re a better approach because they lean on podman&amp;rsquo;s&#xA;own tooling rather than external glue I have to maintain. When I started&#xA;standing up small services in a couple of my on-prem client sites, I decided to&#xA;use Quadlets instead of another pile of jinja2 templates.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>In <a href="/blog/smallops-with-podman-pt1/">Part 1</a>, I walked through how I deploy
rootless podman containers as systemd user units in my homelab using jinja2
templates.  I also said I don&rsquo;t recommend that approach for anything new and
that I replaced it with Quadlets in some client deployments.  This post is
about that replacement.  I hope to migrate my homelab to quadlets in the
near future but inertia is a bear&hellip;</p>
<p>For the most part, my templates and systemd units in the homelab work just fine and there isn&rsquo;t a
lot of reason for me to rip them out at the moment.  However, after reading
about Quadlets, I think they&rsquo;re a better approach because they lean on podman&rsquo;s
own tooling rather than external glue I have to maintain. When I started
standing up small services in a couple of my on-prem client sites, I decided to
use Quadlets instead of another pile of jinja2 templates.</p>
<p>Quadlets are podman&rsquo;s native systemd integration.  They landed in podman 4.4
and have been stable for a bit now.  Instead of writing a systemd unit with
<code>podman run</code> stuffed into <code>ExecStart</code>, you write a declarative <code>.container</code>
file.  Podman ships a systemd generator that reads those files and emits real
<code>.service</code> units at <code>daemon-reload</code> time.  You don&rsquo;t see the generated unit
unless you go looking for it.</p>
<p>For a rootless user, the generator picks up files from a few places.  The one
I use is <code>~/.config/containers/systemd/</code>.  The full search path also includes
<code>$XDG_RUNTIME_DIR/containers/systemd/</code> and some <code>/etc/containers/systemd/</code>
locations for root or per-UID system configs.</p>
<p>There are companion file types for related resources:  <code>.network</code>, <code>.volume</code>,
<code>.pod</code>, <code>.build</code>, and <code>.kube</code>.  You can describe a custom podman network in a
<code>.network</code> file and reference it from one or more <code>.container</code> files, which
ends up being cleaner than managing the network as a separate ansible task.</p>
<h2 id="before-and-after">Before and After</h2>
<p>Here&rsquo;s the jinja2 template from Part 1 that renders a systemd unit for my
pi-hole container:</p>





<pre tabindex="0"><code class="language-jinja2" data-lang="jinja2">[Unit]
Description=Pi-hole Container
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
Restart=always
RestartSec=10
ExecStartPre=-/usr/bin/podman rm -f pihole
ExecStart=/usr/bin/podman run --name pihole \
{% for port in containers.pihole.ports %}
  -p {{ port }} \
{% endfor %}
{% for key, val in containers.pihole.env.items() %}
  -e {{ key }}={{ val }} \
{% endfor %}
{% for vol in containers.pihole.volumes %}
  -v {{ vol }} \
{% endfor %}
  --replace \
  {{ containers.pihole.image }}
ExecStop=/usr/bin/podman stop pihole
ExecStopPost=-/usr/bin/podman rm -f pihole

[Install]
WantedBy=default.target</code></pre><p>And here is roughly the same thing as a Quadlet:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="c1"># ~/.config/containers/systemd/pihole.container</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="k">[Unit]</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="na">Description</span><span class="o">=</span><span class="s">Pi-hole</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="k">[Container]</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="na">Image</span><span class="o">=</span><span class="s">docker.io/pihole/pihole:latest</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="na">PublishPort</span><span class="o">=</span><span class="s">192.168.50.15:53:53/tcp</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="na">PublishPort</span><span class="o">=</span><span class="s">192.168.50.15:53:53/udp</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="na">PublishPort</span><span class="o">=</span><span class="s">192.168.50.15:8080:80/tcp</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="na">Volume</span><span class="o">=</span><span class="s">%h/containers/pihole/etc-pihole:/etc/pihole:Z</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="na">Volume</span><span class="o">=</span><span class="s">%h/containers/pihole/etc-dnsmasq.d:/etc/dnsmasq.d:Z</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="na">Environment</span><span class="o">=</span><span class="s">TZ=America/New_York</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="na">Environment</span><span class="o">=</span><span class="s">DNSMASQ_LISTENING=all</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="na">AutoUpdate</span><span class="o">=</span><span class="s">registry</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl">
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="k">[Service]</span>
</span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="na">Restart</span><span class="o">=</span><span class="s">on-failure</span>
</span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="na">RestartSec</span><span class="o">=</span><span class="s">30</span>
</span></span><span class="line"><span class="ln">19</span><span class="cl">
</span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="k">[Install]</span>
</span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="na">WantedBy</span><span class="o">=</span><span class="s">default.target</span></span></span></code></pre></div><p>A few things to point out here.  All of the <code>ExecStart</code>, <code>ExecStop</code>, and
<code>ExecStopPost</code> lines are gone.  I don&rsquo;t need the <code>podman rm -f</code> safety net
because the generator handles cleanup on its own.  I don&rsquo;t need the
<code>ExecStartPre</code> pull hack either because <code>AutoUpdate=registry</code> is the supported
way to do that with a companion <code>podman-auto-update.timer</code> (more on that in a
future post, maybe).  <code>%h</code> expands to the user&rsquo;s home directory, so I don&rsquo;t
have to hardcode paths for a service account.</p>
<p>I also don&rsquo;t have to write any jinja2.  The <code>.container</code> file is the source of
truth.  Ansible just copies it into place.</p>
<h2 id="real-example--reverse-proxy">Real Example:  Reverse Proxy</h2>
<p>Here is a lightly anonymized example from one of my client sites.  The job is
a reverse proxy in front of a couple of internal web services.  It&rsquo;s running
rootless podman on a Fedora server under a dedicated service account.  Nothing
fancy.</p>
<p>The <code>.container</code> file lives in the ansible repo as <code>files/caddy.container</code>:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">[Unit]</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="na">Description</span><span class="o">=</span><span class="s">Caddy reverse proxy</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="k">[Container]</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="na">ContainerName</span><span class="o">=</span><span class="s">caddy</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="na">Image</span><span class="o">=</span><span class="s">docker.io/library/caddy:2</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="na">Network</span><span class="o">=</span><span class="s">web</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="na">PublishPort</span><span class="o">=</span><span class="s">3443:3443</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="na">PublishPort</span><span class="o">=</span><span class="s">3444:3444</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="na">PublishPort</span><span class="o">=</span><span class="s">3445:3445</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="na">Exec</span><span class="o">=</span><span class="s">caddy run --config /etc/caddy/caddy.json</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="na">Volume</span><span class="o">=</span><span class="s">%h/caddy-config/caddy.json:/etc/caddy/caddy.json:Z</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="na">Volume</span><span class="o">=</span><span class="s">%h/caddy-data:/data:Z</span>
</span></span><span class="line"><span class="ln">14</span><span class="cl">
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="k">[Install]</span>
</span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="na">WantedBy</span><span class="o">=</span><span class="s">default.target</span></span></span></code></pre></div><p><code>Network=web</code> refers to a podman network I create once on the host.  Every
service that needs to be reachable by the proxy joins it.  I could describe
that network in a <code>.network</code> Quadlet file, but for these sites I create it
directly with the <code>containers.podman.podman_network</code> module because I was
already doing that before I moved to Quadlets and it still works fine.</p>
<p>The ansible that deploys it looks like this:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="ln"> 1</span><span class="cl">- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Open reverse proxy ports in firewall</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="w">  </span><span class="nt">become</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="w">  </span><span class="nt">ansible.posix.firewalld</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="w">    </span><span class="nt">port</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;{{ item }}&#34;</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="w">    </span><span class="nt">permanent</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="w">    </span><span class="nt">immediate</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="w">    </span><span class="nt">state</span><span class="p">:</span><span class="w"> </span><span class="l">enabled</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="w">  </span><span class="nt">loop</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="w">    </span>- <span class="m">3443</span><span class="l">/tcp</span><span class="w">
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="w">    </span>- <span class="m">3444</span><span class="l">/tcp</span><span class="w">
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="w">    </span>- <span class="m">3445</span><span class="l">/tcp</span><span class="w">
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">13</span><span class="cl">- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Create podman network for web services</span><span class="w">
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="w">  </span><span class="nt">containers.podman.podman_network</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="w">    </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">web</span><span class="w">
</span></span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="w">    </span><span class="nt">state</span><span class="p">:</span><span class="w"> </span><span class="l">present</span><span class="w">
</span></span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="w">  </span><span class="nt">become</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="w">
</span></span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">19</span><span class="cl">- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Create Caddy data and config directories</span><span class="w">
</span></span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="w">  </span><span class="nt">ansible.builtin.file</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="w">    </span><span class="nt">path</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;{{ item }}&#34;</span><span class="w">
</span></span></span><span class="line"><span class="ln">22</span><span class="cl"><span class="w">    </span><span class="nt">state</span><span class="p">:</span><span class="w"> </span><span class="l">directory</span><span class="w">
</span></span></span><span class="line"><span class="ln">23</span><span class="cl"><span class="w">    </span><span class="nt">mode</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;0755&#34;</span><span class="w">
</span></span></span><span class="line"><span class="ln">24</span><span class="cl"><span class="w">  </span><span class="nt">loop</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">25</span><span class="cl"><span class="w">    </span>- <span class="l">~/caddy-data</span><span class="w">
</span></span></span><span class="line"><span class="ln">26</span><span class="cl"><span class="w">    </span>- <span class="l">~/caddy-config</span><span class="w">
</span></span></span><span class="line"><span class="ln">27</span><span class="cl"><span class="w">  </span><span class="nt">become</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="w">
</span></span></span><span class="line"><span class="ln">28</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">29</span><span class="cl">- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Deploy Caddy JSON config</span><span class="w">
</span></span></span><span class="line"><span class="ln">30</span><span class="cl"><span class="w">  </span><span class="nt">ansible.builtin.copy</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">31</span><span class="cl"><span class="w">    </span><span class="nt">src</span><span class="p">:</span><span class="w"> </span><span class="l">files/caddy.json</span><span class="w">
</span></span></span><span class="line"><span class="ln">32</span><span class="cl"><span class="w">    </span><span class="nt">dest</span><span class="p">:</span><span class="w"> </span><span class="l">~/caddy-config/caddy.json</span><span class="w">
</span></span></span><span class="line"><span class="ln">33</span><span class="cl"><span class="w">    </span><span class="nt">mode</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;0644&#34;</span><span class="w">
</span></span></span><span class="line"><span class="ln">34</span><span class="cl"><span class="w">  </span><span class="nt">become</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="w">
</span></span></span><span class="line"><span class="ln">35</span><span class="cl"><span class="w">  </span><span class="nt">register</span><span class="p">:</span><span class="w"> </span><span class="l">caddy_config</span><span class="w">
</span></span></span><span class="line"><span class="ln">36</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">37</span><span class="cl">- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Create Quadlet directory</span><span class="w">
</span></span></span><span class="line"><span class="ln">38</span><span class="cl"><span class="w">  </span><span class="nt">ansible.builtin.file</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">39</span><span class="cl"><span class="w">    </span><span class="nt">path</span><span class="p">:</span><span class="w"> </span><span class="l">~/.config/containers/systemd</span><span class="w">
</span></span></span><span class="line"><span class="ln">40</span><span class="cl"><span class="w">    </span><span class="nt">state</span><span class="p">:</span><span class="w"> </span><span class="l">directory</span><span class="w">
</span></span></span><span class="line"><span class="ln">41</span><span class="cl"><span class="w">    </span><span class="nt">mode</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;0755&#34;</span><span class="w">
</span></span></span><span class="line"><span class="ln">42</span><span class="cl"><span class="w">  </span><span class="nt">become</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="w">
</span></span></span><span class="line"><span class="ln">43</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">44</span><span class="cl">- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Deploy Caddy Quadlet unit</span><span class="w">
</span></span></span><span class="line"><span class="ln">45</span><span class="cl"><span class="w">  </span><span class="nt">ansible.builtin.copy</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">46</span><span class="cl"><span class="w">    </span><span class="nt">src</span><span class="p">:</span><span class="w"> </span><span class="l">files/caddy.container</span><span class="w">
</span></span></span><span class="line"><span class="ln">47</span><span class="cl"><span class="w">    </span><span class="nt">dest</span><span class="p">:</span><span class="w"> </span><span class="l">~/.config/containers/systemd/caddy.container</span><span class="w">
</span></span></span><span class="line"><span class="ln">48</span><span class="cl"><span class="w">    </span><span class="nt">mode</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;0644&#34;</span><span class="w">
</span></span></span><span class="line"><span class="ln">49</span><span class="cl"><span class="w">  </span><span class="nt">become</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="w">
</span></span></span><span class="line"><span class="ln">50</span><span class="cl"><span class="w">  </span><span class="nt">register</span><span class="p">:</span><span class="w"> </span><span class="l">caddy_quadlet</span><span class="w">
</span></span></span><span class="line"><span class="ln">51</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">52</span><span class="cl">- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Reload systemd user daemon</span><span class="w">
</span></span></span><span class="line"><span class="ln">53</span><span class="cl"><span class="w">  </span><span class="nt">ansible.builtin.systemd</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">54</span><span class="cl"><span class="w">    </span><span class="nt">daemon_reload</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="ln">55</span><span class="cl"><span class="w">    </span><span class="nt">scope</span><span class="p">:</span><span class="w"> </span><span class="l">user</span><span class="w">
</span></span></span><span class="line"><span class="ln">56</span><span class="cl"><span class="w">  </span><span class="nt">become</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="w">
</span></span></span><span class="line"><span class="ln">57</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">58</span><span class="cl">- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Enable and start Caddy service</span><span class="w">
</span></span></span><span class="line"><span class="ln">59</span><span class="cl"><span class="w">  </span><span class="nt">ansible.builtin.systemd</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">60</span><span class="cl"><span class="w">    </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">caddy</span><span class="w">
</span></span></span><span class="line"><span class="ln">61</span><span class="cl"><span class="w">    </span><span class="nt">scope</span><span class="p">:</span><span class="w"> </span><span class="l">user</span><span class="w">
</span></span></span><span class="line"><span class="ln">62</span><span class="cl"><span class="w">    </span><span class="nt">enabled</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="ln">63</span><span class="cl"><span class="w">    </span><span class="nt">state</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;{{ (caddy_quadlet.changed or caddy_config.changed) | ternary(&#39;restarted&#39;, &#39;started&#39;) }}&#34;</span><span class="w">
</span></span></span><span class="line"><span class="ln">64</span><span class="cl"><span class="w">  </span><span class="nt">become</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span></span></span></code></pre></div><p>Compared with Part 1, the big differences are:</p>
<ol>
<li><code>ansible.builtin.copy</code> instead of <code>ansible.builtin.template</code> so there isn&rsquo;t a jinja2
rendering step and no per-container template file in
<code>playbooks/templates/</code>.</li>
<li>The service state is driven by whether the Quadlet file or its backing
config changed.  The <code>ternary</code> bit restarts only when something actually
moved, which is the behavior I want for a long-lived proxy.</li>
<li>I can <code>systemctl --user</code> the service by its plain name (<code>caddy</code>).  The
generator names the service after the <code>.container</code> file, so
<code>caddy.container</code> becomes <code>caddy.service</code>.</li>
<li>IPs and other host-specific values are hardcoded in the <code>.container</code> file
now that jinja2 isn&rsquo;t in the loop, which isn&rsquo;t great.  For a single host
it&rsquo;s probably fine.  For multiple hosts I&rsquo;d either template the Quadlet file (back
to <code>ansible.builtin.template</code>, just producing a <code>.container</code> instead of a
<code>.service</code>) or lean harder on Quadlet&rsquo;s own substitutions like <code>%h</code> and
drop site-specific bits into a <code>.env</code> file pulled in with
<code>EnvironmentFile=</code>.  I haven&rsquo;t had to do that yet because each client
site is a single box, but it&rsquo;ll come up.</li>
</ol>
<p>To show how minimal these can get, here is the uptime kuma instace I run next to
the proxy:</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="k">[Unit]</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="na">Description</span><span class="o">=</span><span class="s">Uptime monitoring</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl">
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="k">[Container]</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="na">ContainerName</span><span class="o">=</span><span class="s">uptime-monitor</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="na">Image</span><span class="o">=</span><span class="s">docker.io/louislam/uptime-kuma:1</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="na">Network</span><span class="o">=</span><span class="s">web</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="na">Volume</span><span class="o">=</span><span class="s">%h/uptime-data:/app/data:Z</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl">
</span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="k">[Install]</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="na">WantedBy</span><span class="o">=</span><span class="s">default.target</span></span></span></code></pre></div><p>It&rsquo;s pretty slim overall.   The ansible play that deploys it is
basically the same shape as the Caddy one with different file names. I like the
shape of using quadlets better at this point.  It&rsquo;s less to manage
and it lets me use existing tools instead of cobbling my own together.</p>
<p>Some references for this post:</p>
<ul>
<li><a href="https://wiki.archlinux.org/title/Podman">Podman - ArchWiki</a></li>
<li><a href="https://mo8it.com/blog/quadlet/">Quadlet:  Running Podman containers under systemd</a></li>
<li><a href="https://www.redhat.com/en/blog/quadlet-podman">Make systemd better for Podman with Quadlet</a></li>
<li><a href="https://docs.podman.io/en/latest/markdown/podman-systemd.unit.5.html">podman-systemd.unit(5)</a></li>
<li><a href="https://docs.podman.io/en/latest/markdown/podman-auto-update.1.html">podman-auto-update(1)</a></li>
<li><a href="https://github.com/containers/podlet">podlet</a></li>
</ul>
]]></content:encoded>
    </item>
  </channel>
</rss>
