<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Podman on Matty Spangler</title>
    <link>https://mattyspangler.codeberg.page/tags/podman/</link>
    <description>Recent content in Podman on Matty Spangler</description>
    <generator>Hugo -- gohugo.io</generator>
    <language>en-us</language>
    <lastBuildDate>Tue, 10 Mar 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://mattyspangler.codeberg.page/tags/podman/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Gitea with Podman Quadlets on Kicksecure</title>
      <link>https://mattyspangler.codeberg.page/posts/kicksecure-podman-quadlets/</link>
      <pubDate>Tue, 10 Mar 2026 00:00:00 +0000</pubDate>
      
      <guid>https://mattyspangler.codeberg.page/posts/kicksecure-podman-quadlets/</guid>
      <description>&lt;p&gt;This article is part of my &lt;a href=&#34;../kicksecure-self-hosting/&#34;&gt;Self-Hosting with Kicksecure&lt;/a&gt; series.&lt;/p&gt;
&lt;h2 id=&#34;1-introduction&#34;&gt;1. Introduction&lt;/h2&gt;
&lt;p&gt;After moving away from Kubernetes, I wanted something simpler but still declarative. Quadlets let me define containers as systemd units—a format I already know from managing other system services.&lt;/p&gt;
&lt;p&gt;What I like about quadlets:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Systemd integration&lt;/strong&gt; - Containers start on boot, restart on failure, and log to journald&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;GitOps friendly&lt;/strong&gt; - Unit files are plain text, easy to track in version control&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Rootless&lt;/strong&gt; - I can run everything as a regular user with systemd user sessions&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;No docker-compose needed&lt;/strong&gt; - Native systemd handles dependencies&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;2-prerequisites&#34;&gt;2. Prerequisites&lt;/h2&gt;
&lt;p&gt;Before this, I had:&lt;/p&gt;</description>
      <content>&lt;p&gt;This article is part of my &lt;a href=&#34;../kicksecure-self-hosting/&#34;&gt;Self-Hosting with Kicksecure&lt;/a&gt; series.&lt;/p&gt;
&lt;h2 id=&#34;1-introduction&#34;&gt;1. Introduction&lt;/h2&gt;
&lt;p&gt;After moving away from Kubernetes, I wanted something simpler but still declarative. Quadlets let me define containers as systemd units—a format I already know from managing other system services.&lt;/p&gt;
&lt;p&gt;What I like about quadlets:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Systemd integration&lt;/strong&gt; - Containers start on boot, restart on failure, and log to journald&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;GitOps friendly&lt;/strong&gt; - Unit files are plain text, easy to track in version control&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Rootless&lt;/strong&gt; - I can run everything as a regular user with systemd user sessions&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;No docker-compose needed&lt;/strong&gt; - Native systemd handles dependencies&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;2-prerequisites&#34;&gt;2. Prerequisites&lt;/h2&gt;
&lt;p&gt;Before this, I had:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Rootless Podman configured (&lt;a href=&#34;../kicksecure-podman-permissions/&#34;&gt;Podman Permissions on Kicksecure&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Encrypted storage mounted (&lt;a href=&#34;../kicksecure-btrfs-storage/&#34;&gt;Encrypted BTRFS Storage on Kicksecure&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;3-directory-structure&#34;&gt;3. Directory Structure&lt;/h2&gt;
&lt;p&gt;Quadlets live in &lt;code&gt;~/.config/containers/systemd/&lt;/code&gt;:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;/home/user/.config/containers/systemd/
├── gitea.container
├── gitea-runner.container
└── gitea.network    # Optional: custom network definition
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&#34;4-prepare-storage&#34;&gt;4. Prepare Storage&lt;/h2&gt;
&lt;p&gt;Created a subvolume for Gitea data:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;sudo btrfs subvolume create /mnt/storage_name/gitea_data
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Set ownership for rootless Podman:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;podman unshare chown -R 1000:1000 /mnt/storage_name/gitea_data
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This maps the container&amp;rsquo;s UID 1000 to my user&amp;rsquo;s permissions via the ID mapping I configured earlier.&lt;/p&gt;
&lt;h2 id=&#34;5-the-gitea-quadlet&#34;&gt;5. The Gitea Quadlet&lt;/h2&gt;
&lt;p&gt;Created &lt;code&gt;~/.config/containers/systemd/gitea.container&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-ini&#34; data-lang=&#34;ini&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;[Unit]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;Description&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;Gitea&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;After&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;network-online.target&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;[Container]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;Image&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;docker.io/gitea/gitea:latest&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;ContainerName&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;gitea&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# Web interface - bind to localhost only&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;PublishPort&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;127.0.0.1:3000:3000&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# SSH - binds to all interfaces for external git access&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;PublishPort&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;2222:22&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;Environment&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;USER_UID=1000&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;Environment&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;USER_GID=1000&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;Volume&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;/mnt/storage_name/gitea_data:/data:Z&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;[Service]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;Restart&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;always&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;[Install]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;WantedBy&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;default.target&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;51-key-points&#34;&gt;5.1. Key Points&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;PublishPort 127.0.0.1:3000:3000&lt;/strong&gt; - Binds the web interface to localhost only. External access goes through my reverse proxy or VPN.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;PublishPort 2222:22&lt;/strong&gt; - No IP specified means it binds to all interfaces. This allows git operations over SSH from external hosts.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Volume with :Z&lt;/strong&gt; - The &lt;code&gt;Z&lt;/code&gt; suffix tells Podman to relabel the SELinux context (even though Kicksecure uses AppArmor, Podman still recognizes this flag for private volumes).&lt;/p&gt;
&lt;h2 id=&#34;6-enable-the-podman-socket&#34;&gt;6. Enable the Podman Socket&lt;/h2&gt;
&lt;p&gt;Quadlets require the Podman socket for user sessions:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;systemctl --user enable --now podman.socket
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This creates &lt;code&gt;/run/user/1000/podman/podman.sock&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id=&#34;7-starting-gitea&#34;&gt;7. Starting Gitea&lt;/h2&gt;
&lt;p&gt;Reloaded systemd to recognize the quadlet, then started the service:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;systemctl --user daemon-reload
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;systemctl --user enable --now gitea.service
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Checked status:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;systemctl --user status gitea.service
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Viewed logs:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;journalctl --user -u gitea.service
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;8-gitea-runner-for-cicd&#34;&gt;8. Gitea Runner for CI/CD&lt;/h2&gt;
&lt;p&gt;To run Gitea Actions, I deployed the act_runner container. Created &lt;code&gt;~/.config/containers/systemd/gitea-runner.container&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-ini&#34; data-lang=&#34;ini&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;[Unit]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;Description&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;Gitea Actions Runner&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;After&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;network-online.target gitea.service&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;Requires&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;podman.socket&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;[Container]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;Image&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;docker.io/gitea/act_runner:latest&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;ContainerName&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;gitea-runner&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# Host network to reach gitea at localhost&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;Network&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;host&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# Pass the podman socket for Docker-compatible CI&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;Volume&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;/run/user/1000/podman/podman.sock:/var/run/docker.sock:Z&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;Volume&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;/mnt/storage_name/app_cache/gitea_runner:/data:Z&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;Environment&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;GITEA_INSTANCE_URL=http://127.0.0.1:3000&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;Environment&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;GITEA_RUNNER_REGISTRATION_TOKEN=YOUR_REGISTRATION_TOKEN&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;Environment&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;DOCKER_HOST=unix:///var/run/docker.sock&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;[Service]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;Restart&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;always&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;[Install]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;WantedBy&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;default.target&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Got the registration token from Gitea: Site Administration → Actions → Runners → Create new Runner.&lt;/p&gt;
&lt;h3 id=&#34;81-why-host-networking&#34;&gt;8.1. Why Host Networking?&lt;/h3&gt;
&lt;p&gt;The runner uses &lt;code&gt;Network=host&lt;/code&gt; so it can reach Gitea at &lt;code&gt;localhost:3000&lt;/code&gt; without network configuration. Simpler than creating a dedicated pod network.&lt;/p&gt;
&lt;h3 id=&#34;82-podman-socket-passthrough&#34;&gt;8.2. Podman Socket Passthrough&lt;/h3&gt;
&lt;p&gt;The runner mounts the host&amp;rsquo;s Podman socket, allowing CI pipelines to build containers using the host&amp;rsquo;s Podman instance. This is powerful but has security implications—only use for trusted CI workloads.&lt;/p&gt;
&lt;h2 id=&#34;9-accessing-gitea&#34;&gt;9. Accessing Gitea&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Web UI&lt;/strong&gt;: http://localhost:3000 (or through reverse proxy)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SSH Git&lt;/strong&gt;: &lt;code&gt;git clone ssh://git@your-server:2222/username/repo.git&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Configured my SSH client to use port 2222:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;Host gitea
    HostName your-server
    Port 2222
    User git
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&#34;10-managing-containers&#34;&gt;10. Managing Containers&lt;/h2&gt;
&lt;p&gt;Common systemd commands I use:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# View status&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;systemctl --user status gitea.service
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;systemctl --user status gitea-runner.service
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# Restart&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;systemctl --user restart gitea.service
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# Stop&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;systemctl --user stop gitea.service
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# View logs&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;journalctl --user -u gitea.service -f
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# List containers&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;podman ps
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;11-tracking-quadlets-in-git&#34;&gt;11. Tracking Quadlets in Git&lt;/h2&gt;
&lt;p&gt;Since quadlets are just text files, I version control them:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;cd ~/.config/containers/systemd
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;git init
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;git add *.container *.network
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;git commit -m &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Initial container definitions&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This makes it easy to manage multiple services and push to a central repository.&lt;/p&gt;
&lt;h2 id=&#34;12-troubleshooting&#34;&gt;12. Troubleshooting&lt;/h2&gt;
&lt;h3 id=&#34;container-wont-start&#34;&gt;Container Won&amp;rsquo;t Start&lt;/h3&gt;
&lt;p&gt;Check logs:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;journalctl --user -u gitea.service -n &lt;span style=&#34;color:#ae81ff&#34;&gt;50&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Common issues:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Permission denied on volume&lt;/strong&gt;: Re-run &lt;code&gt;podman unshare chown&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Image pull failed&lt;/strong&gt;: Check network access to docker.io&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Port already in use&lt;/strong&gt;: Something else is using 3000 or 2222&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;quadlet-not-recognized&#34;&gt;Quadlet Not Recognized&lt;/h3&gt;
&lt;p&gt;Ensure the file ends in &lt;code&gt;.container&lt;/code&gt; and is in the correct directory:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;ls -la ~/.config/containers/systemd/
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Reload systemd:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;systemctl --user daemon-reload
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;firewall-issues&#34;&gt;Firewall Issues&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Locked Out&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;If I lock myself out:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Physical console access&lt;/li&gt;
&lt;li&gt;Recovery mode&lt;/li&gt;
&lt;li&gt;Reset UFW: &lt;code&gt;ufw reset&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Rules Not Applied&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Reload the firewall:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;ufw reload
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;Service Still Accessible&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I check if the service is bound to the correct interface:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;ss -tlnp
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;A service bound to &lt;code&gt;0.0.0.0&lt;/code&gt; listens on all interfaces. One bound to &lt;code&gt;127.0.0.1&lt;/code&gt; only listens on localhost.&lt;/p&gt;
&lt;h2 id=&#34;13-ufw-firewall-configuration&#34;&gt;13. UFW Firewall Configuration&lt;/h2&gt;
&lt;p&gt;I chose UFW (Uncomplicated Firewall) for its simple interface to iptables. While Kicksecure has some firewall defaults, I wanted explicit control over my network security—especially for my Podman container services.&lt;/p&gt;
&lt;h3 id=&#34;131-installation&#34;&gt;13.1. Installation&lt;/h3&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;apt install ufw
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;I also installed the GUI for easier visualization:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;apt install gufw
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;132-default-policy&#34;&gt;13.2. Default Policy&lt;/h3&gt;
&lt;p&gt;I set a default-deny policy—block all incoming, allow all outgoing:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;ufw default deny incoming
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;ufw default allow outgoing
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This means only ports I explicitly open are accessible.&lt;/p&gt;
&lt;h3 id=&#34;133-enable-the-firewall&#34;&gt;13.3. Enable the Firewall&lt;/h3&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;ufw enable
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Then enabled the systemd service:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;systemctl enable ufw.service
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;systemctl start ufw.service
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;134-ssh-access&#34;&gt;13.4. SSH Access&lt;/h3&gt;
&lt;p&gt;Since I manage this server remotely, I needed SSH access before enabling the firewall:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;ufw allow ssh
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Or specifying the port directly:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;ufw allow 22/tcp
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;135-gitea-ports&#34;&gt;13.5. Gitea Ports&lt;/h3&gt;
&lt;p&gt;For Gitea, I opened port 2222 for SSH git operations:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;ufw allow 2222/tcp
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The web interface (port 3000) is bound to localhost in my quadlet, so it doesn&amp;rsquo;t need a firewall rule. External access goes through my reverse proxy or VPN.&lt;/p&gt;
&lt;h3 id=&#34;136-restricting-access-by-source&#34;&gt;13.6. Restricting Access by Source&lt;/h3&gt;
&lt;p&gt;For services that shouldn&amp;rsquo;t be publicly accessible, I restricted them to specific IPs or subnets.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;VPN-Only Access&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I use Netbird for VPN access, so I restricted sensitive services to the VPN subnet:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;ufw allow from 100.64.0.0/10 to any port &lt;span style=&#34;color:#ae81ff&#34;&gt;3000&lt;/span&gt; proto tcp
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;Specific IP Access&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;For trusted IPs only:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;ufw allow from 192.168.1.100 to any port &lt;span style=&#34;color:#ae81ff&#34;&gt;3000&lt;/span&gt; proto tcp
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;Removing Rules&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Remove a rule by prefixing &lt;code&gt;delete&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;ufw delete allow 3000/tcp
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Or by number:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;ufw status numbered
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;ufw delete &lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;137-checking-status&#34;&gt;13.7. Checking Status&lt;/h3&gt;
&lt;p&gt;View current rules:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;ufw status verbose
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Output looks like:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;Status: active
Logging: on (low)
Default: deny (incoming), allow (outgoing), disabled (routed)

New profiles: skip

To                         Action      From
--                         ------      ----
22/tcp                     ALLOW IN    Anywhere
2222/tcp                   ALLOW IN    Anywhere
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&#34;138-why-default-deny-matters&#34;&gt;13.8. Why Default-Deny Matters&lt;/h3&gt;
&lt;p&gt;A default-deny firewall gives me:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Reduced attack surface&lt;/strong&gt; - Unknown services aren&amp;rsquo;t exposed&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Prevention of accidents&lt;/strong&gt; - New services aren&amp;rsquo;t accidentally exposed&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Limited reconnaissance&lt;/strong&gt; - Attackers can&amp;rsquo;t discover services&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Simple mental model&lt;/strong&gt; - If it&amp;rsquo;s not allowed, it&amp;rsquo;s blocked&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This is especially important on a server running multiple services.&lt;/p&gt;
&lt;h3 id=&#34;139-tracking-firewall-changes&#34;&gt;13.9. Tracking Firewall Changes&lt;/h3&gt;
&lt;p&gt;As with all system configuration, I tracked this with etckeeper:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;cd /etc
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;git add .
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;etckeeper commit &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Configured UFW firewall rules&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;14-whats-next&#34;&gt;14. What&amp;rsquo;s Next&lt;/h2&gt;
&lt;p&gt;This completes the core series. Future posts will cover ZFS RAID and Ansible automation with enroll.&lt;/p&gt;
</content>
    </item>
    
    <item>
      <title>Podman Permissions on Kicksecure</title>
      <link>https://mattyspangler.codeberg.page/posts/kicksecure-podman-permissions/</link>
      <pubDate>Mon, 09 Mar 2026 00:00:00 +0000</pubDate>
      
      <guid>https://mattyspangler.codeberg.page/posts/kicksecure-podman-permissions/</guid>
      <description>&lt;p&gt;This article is part of my &lt;a href=&#34;../kicksecure-self-hosting/&#34;&gt;Self-Hosting with Kicksecure&lt;/a&gt; series.&lt;/p&gt;
&lt;h2 id=&#34;1-introduction&#34;&gt;1. Introduction&lt;/h2&gt;
&lt;p&gt;When I first tried running rootless Podman on Kicksecure, containers wouldn&amp;rsquo;t start. The errors looked like:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;Error: command required for rootless mode with multiple IDs: exec: &amp;#34;newuidmap&amp;#34;: executable file not found in $PATH
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Or:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;zsh: permission denied: /usr/bin/newuidmap
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Rootless Podman requires &lt;code&gt;newuidmap&lt;/code&gt; and &lt;code&gt;newgidmap&lt;/code&gt; to map a single user ID to the thousands of IDs needed inside containers. The issue? &lt;strong&gt;Kicksecure restricts access to these binaries by default&lt;/strong&gt; as a security measure. This article documents how I solved this properly.&lt;/p&gt;</description>
      <content>&lt;p&gt;This article is part of my &lt;a href=&#34;../kicksecure-self-hosting/&#34;&gt;Self-Hosting with Kicksecure&lt;/a&gt; series.&lt;/p&gt;
&lt;h2 id=&#34;1-introduction&#34;&gt;1. Introduction&lt;/h2&gt;
&lt;p&gt;When I first tried running rootless Podman on Kicksecure, containers wouldn&amp;rsquo;t start. The errors looked like:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;Error: command required for rootless mode with multiple IDs: exec: &amp;#34;newuidmap&amp;#34;: executable file not found in $PATH
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Or:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;zsh: permission denied: /usr/bin/newuidmap
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Rootless Podman requires &lt;code&gt;newuidmap&lt;/code&gt; and &lt;code&gt;newgidmap&lt;/code&gt; to map a single user ID to the thousands of IDs needed inside containers. The issue? &lt;strong&gt;Kicksecure restricts access to these binaries by default&lt;/strong&gt; as a security measure. This article documents how I solved this properly.&lt;/p&gt;
&lt;h2 id=&#34;2-understanding-the-problem&#34;&gt;2. Understanding the Problem&lt;/h2&gt;
&lt;p&gt;I checked Kicksecure&amp;rsquo;s configuration:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;dpkg-statoverride --list /usr/bin/newuidmap
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Output:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;root root 744 /usr/bin/newuidmap
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Only root can execute these binaries. For most systems, this is good security. But rootless containers need access.&lt;/p&gt;
&lt;h2 id=&#34;3-first-attempt-dpkg-statoverride&#34;&gt;3. First Attempt: dpkg-statoverride&lt;/h2&gt;
&lt;p&gt;My first approach was to use &lt;code&gt;dpkg-statoverride&lt;/code&gt; to change permissions:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;addgroup podman-users
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;usermod -aG podman-users user
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;dpkg-statoverride --remove /usr/bin/newuidmap
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;dpkg-statoverride --remove /usr/bin/newgidmap
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;dpkg-statoverride --update --add root podman-users &lt;span style=&#34;color:#ae81ff&#34;&gt;4750&lt;/span&gt; /usr/bin/newuidmap
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;dpkg-statoverride --update --add root podman-users &lt;span style=&#34;color:#ae81ff&#34;&gt;4750&lt;/span&gt; /usr/bin/newgidmap
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This worked temporarily. But then I ran an apt upgrade, and &lt;code&gt;permission-hardener&lt;/code&gt; reverted my changes. My containers broke after the next update.&lt;/p&gt;
&lt;h2 id=&#34;4-the-correct-solution&#34;&gt;4. The Correct Solution&lt;/h2&gt;
&lt;p&gt;Kicksecure provides a proper way to whitelist binaries through &lt;code&gt;permission-hardener&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id=&#34;41-create-whitelist-configuration&#34;&gt;4.1. Create Whitelist Configuration&lt;/h3&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;mkdir /etc/permission-hardener.d
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Created &lt;code&gt;/etc/permission-hardener.d/50_podman.conf&lt;/code&gt;:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;/usr/bin/newuidmap exactwhitelist
/usr/bin/newgidmap exactwhitelist
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The &lt;code&gt;exactwhitelist&lt;/code&gt; directive tells permission-hardener to leave these binaries alone while still applying restrictions to everything else.&lt;/p&gt;
&lt;h3 id=&#34;42-apply-changes&#34;&gt;4.2. Apply Changes&lt;/h3&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;permission-hardener enable all
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Verified the permissions changed:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;ls -l /usr/bin/newuidmap
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;-rwsr-x--- 1 root podman-users 14976 Jan 10 12:00 /usr/bin/newuidmap
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&#34;5-configuring-podman&#34;&gt;5. Configuring Podman&lt;/h2&gt;
&lt;p&gt;Even with correct permissions, Podman couldn&amp;rsquo;t find these binaries in its path. I created a containers configuration file.&lt;/p&gt;
&lt;p&gt;As the &lt;code&gt;user&lt;/code&gt; account, created &lt;code&gt;~/.config/containers/containers.conf&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-ini&#34; data-lang=&#34;ini&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;[engine]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;newuidmap_path&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;/usr/bin/newuidmap&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;newgidmap_path&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;/usr/bin/newgidmap&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;6-verifying-user-id-mappings&#34;&gt;6. Verifying User ID Mappings&lt;/h2&gt;
&lt;p&gt;I confirmed my user had the necessary ID mappings:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;grep &lt;span style=&#34;color:#66d9ef&#34;&gt;$(&lt;/span&gt;whoami&lt;span style=&#34;color:#66d9ef&#34;&gt;)&lt;/span&gt; /etc/subuid
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;grep &lt;span style=&#34;color:#66d9ef&#34;&gt;$(&lt;/span&gt;whoami&lt;span style=&#34;color:#66d9ef&#34;&gt;)&lt;/span&gt; /etc/subgid
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Output:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;user:100000:65536
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This means &lt;code&gt;user&lt;/code&gt; can map UIDs 100000-165535 inside containers.&lt;/p&gt;
&lt;h2 id=&#34;7-testing&#34;&gt;7. Testing&lt;/h2&gt;
&lt;p&gt;Switched to the container user and tested:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;su - user
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;podman system migrate
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This succeeded without errors. Then I ran a test container:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;podman run --rm hello-world
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;8-why-this-matters&#34;&gt;8. Why This Matters&lt;/h2&gt;
&lt;p&gt;Rootless containers are a significant security improvement:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Container escapes&lt;/strong&gt; are contained to the user account&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Kernel exploits&lt;/strong&gt; have limited impact&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;File ownership&lt;/strong&gt; is handled via ID mapping, not root privileges&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Kicksecure&amp;rsquo;s default restrictions are correct for most use cases. Podman is an exception that requires explicit whitelisting.&lt;/p&gt;
&lt;h2 id=&#34;9-troubleshooting&#34;&gt;9. Troubleshooting&lt;/h2&gt;
&lt;h3 id=&#34;permission-denied&#34;&gt;Permission Denied&lt;/h3&gt;
&lt;p&gt;Check group membership:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;groups user
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Ensure &lt;code&gt;podman-users&lt;/code&gt; appears in the list. A logout/login might be needed.&lt;/p&gt;
&lt;h3 id=&#34;path-not-found&#34;&gt;Path Not Found&lt;/h3&gt;
&lt;p&gt;Ensure the containers.conf file exists:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;ls -la ~/.config/containers/containers.conf
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;changes-reverted-after-update&#34;&gt;Changes Reverted After Update&lt;/h3&gt;
&lt;p&gt;Verify the whitelist file:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;cat /etc/permission-hardener.d/50_podman.conf
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Then re-run:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;permission-hardener enable all
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;10-tracking-the-change&#34;&gt;10. Tracking the Change&lt;/h2&gt;
&lt;p&gt;This is exactly the kind of configuration change worth tracking:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;cd /etc
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;git add .
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;etckeeper commit &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Whitelisted newuidmap/newgidmap for rootless podman&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;More on this in my &lt;a href=&#34;../kicksecure-etckeeper/&#34;&gt;Etckeeper on Kicksecure&lt;/a&gt; article.&lt;/p&gt;
&lt;h2 id=&#34;10-whats-next&#34;&gt;10. What&amp;rsquo;s Next&lt;/h2&gt;
&lt;p&gt;&lt;a href=&#34;../kicksecure-podman-quadlets/&#34;&gt;Podman Quadlets on Kicksecure&lt;/a&gt;&lt;/p&gt;
</content>
    </item>
    
    <item>
      <title>Self-Hosting with Kicksecure</title>
      <link>https://mattyspangler.codeberg.page/posts/kicksecure-self-hosting/</link>
      <pubDate>Fri, 06 Mar 2026 00:00:00 +0000</pubDate>
      
      <guid>https://mattyspangler.codeberg.page/posts/kicksecure-self-hosting/</guid>
      <description>&lt;h2 id=&#34;1-intro&#34;&gt;1. Intro&lt;/h2&gt;
&lt;p&gt;I formerly self-hosted my services on a Kubernetes cluster with FluxCD inside openSUSE MicroOS VMs hosted by Proxmox. While that was really flexible, it became a burden maintaining it in my freetime and demanded too much RAM and storage on my resource-constrained servers. I still want to keep my K8s cluster around for tinkering in my homelab, but for a personal setup that I need to &amp;ldquo;just work&amp;rdquo; for NAS, git hosting, and backups, the complexity didn&amp;rsquo;t justify the benefits.&lt;/p&gt;</description>
      <content>&lt;h2 id=&#34;1-intro&#34;&gt;1. Intro&lt;/h2&gt;
&lt;p&gt;I formerly self-hosted my services on a Kubernetes cluster with FluxCD inside openSUSE MicroOS VMs hosted by Proxmox. While that was really flexible, it became a burden maintaining it in my freetime and demanded too much RAM and storage on my resource-constrained servers. I still want to keep my K8s cluster around for tinkering in my homelab, but for a personal setup that I need to &amp;ldquo;just work&amp;rdquo; for NAS, git hosting, and backups, the complexity didn&amp;rsquo;t justify the benefits.&lt;/p&gt;
&lt;p&gt;Yet, I loved my former workflows: GitOps, infrastructure as code, and declarative container management. I wanted to manage my self-hosting from a git repository. That&amp;rsquo;s how I landed on &lt;strong&gt;etckeeper&lt;/strong&gt; for managing my base system and &lt;strong&gt;Podman quadlets&lt;/strong&gt; for managing my container services declaratively with systemd.&lt;/p&gt;
&lt;p&gt;There was one more criteria I wanted to meet&amp;hellip;&lt;/p&gt;
&lt;h2 id=&#34;2-why-kicksecure&#34;&gt;2. Why Kicksecure?&lt;/h2&gt;
&lt;p&gt;I also don&amp;rsquo;t have enough time in my day to exhaustively follow hardening guides. I&amp;rsquo;d prefer an opinionated, secure by default system. I evaluated several options before choosing Kicksecure.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Secureblue&lt;/strong&gt; looks promising! But I&amp;rsquo;m more comfortable with Debian-based systems, and I wasn&amp;rsquo;t familiar with its development background and whether the project is reputable.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;NixOS and Gentoo&lt;/strong&gt; were considered. Gentoo could be a really secure option but would have been another maintenance burden. NixOS might have worked since I daily drive it on my gaming PC and I&amp;rsquo;ve already gone through some hardening guides so I could copy my existing configs. Unfortunately I ran into a known bug preventing NixOS/GuixSD from booting with Heads on my server. Heads is a security focused distribution based on the coreboot open source BIOS based with features to prevent boot tampering, and rather than toiling against this bug I opted to go with the next option:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Kicksecure&lt;/strong&gt; is connected to the same team behind Whonix which I already use! Whonix is a well-established project and it currently fits my constraints.&lt;/p&gt;
&lt;p&gt;Kicksecure does need extra configuration to work with podman, but it turned out to be much more manageable than my old stack.&lt;/p&gt;
&lt;h2 id=&#34;3-retrospective&#34;&gt;3. Retrospective&lt;/h2&gt;
&lt;p&gt;By the end of this project, I had assembled:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;An etckeeper managed Kicksecure server&lt;/li&gt;
&lt;li&gt;An encrypted BTRFS storage with subvolumes for backing up extremely critical stuff&lt;/li&gt;
&lt;li&gt;Rootless Podman containers managed with git tracked systemd quadlets&lt;/li&gt;
&lt;li&gt;A running Gitea instance with SSH access on port 4022&lt;/li&gt;
&lt;li&gt;Firewall rules restricting access to only necessary ports&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;31-posts-in-this-series&#34;&gt;3.1 Posts in this series&lt;/h3&gt;
&lt;p&gt;I&amp;rsquo;m documenting my journey through the setup process in these posts:&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;Step&lt;/th&gt;
					&lt;th&gt;Article&lt;/th&gt;
					&lt;th&gt;Description&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;1&lt;/td&gt;
					&lt;td&gt;&lt;a href=&#34;../kicksecure-etckeeper/&#34;&gt;Etckeeper GitOps on Kicksecure&lt;/a&gt;&lt;/td&gt;
					&lt;td&gt;Keep track of all my Kicksecure configuration changes from the start&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;2&lt;/td&gt;
					&lt;td&gt;&lt;a href=&#34;../kicksecure-btrfs-storage/&#34;&gt;Encrypted BTRFS Storage on Kicksecure&lt;/a&gt;&lt;/td&gt;
					&lt;td&gt;Configuring server storage on BTRFS subvolumes with LUKS encryption&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;3&lt;/td&gt;
					&lt;td&gt;&lt;a href=&#34;../kicksecure-podman-permissions/&#34;&gt;Podman Permissions on Kicksecure&lt;/a&gt;&lt;/td&gt;
					&lt;td&gt;Allowing rootless Podman to use newuidmap/newgidmap via Kicksecure&amp;rsquo;s Permission Hardener&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;4&lt;/td&gt;
					&lt;td&gt;&lt;a href=&#34;../kicksecure-podman-quadlets/&#34;&gt;Podman Quadlets on Kicksecure&lt;/a&gt;&lt;/td&gt;
					&lt;td&gt;Deploying containers with Git-tracked Podman quadlets and UFW firewall&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Additionally, I flashed and locked down Heads/coreboot, installed an open source zero trust mesh VPN for remote access, deployed host-based IDS, moved server management to Ansible roles, and set up a Nextcloud instance utilizing storage on a ZFS RAID 10 array with 4x 8TB HDD&amp;rsquo;s. In the future I hope to expand this series with posts detailing those efforts, as well as posts further exploring Kicksecure&amp;rsquo;s nifty security features.&lt;/p&gt;
</content>
    </item>
    
  </channel>
</rss>
