<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Kicksecure on Matty Spangler</title>
    <link>https://mattyspangler.codeberg.page/tags/kicksecure/</link>
    <description>Recent content in Kicksecure 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/kicksecure/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>Etckeeper on Kicksecure</title>
      <link>https://mattyspangler.codeberg.page/posts/kicksecure-etckeeper/</link>
      <pubDate>Sun, 08 Mar 2026 00:00:00 +0000</pubDate>
      
      <guid>https://mattyspangler.codeberg.page/posts/kicksecure-etckeeper/</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;Before making any system changes, I want to decide on a way to record every change I make so that:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;I can easily reproduce my work if I ever have to reinstall or migrate to another machine&lt;/li&gt;
&lt;li&gt;I can easily identify problematic configs and revert them&lt;/li&gt;
&lt;li&gt;I don&amp;rsquo;t forget the reasoning behind a particular security decision or config change in 6 months&lt;/li&gt;
&lt;li&gt;I can identify what changes I made as opposed to changes made by software or other people&lt;/li&gt;
&lt;li&gt;I can compare the current state of my configs against a &amp;lsquo;known-good&amp;rsquo; config&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The &lt;code&gt;/etc&lt;/code&gt; directory will contain most of my configs. Outside of /etc I will need to pay attention to my dotfiles which I like to manage with GNU Stow.&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;Before making any system changes, I want to decide on a way to record every change I make so that:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;I can easily reproduce my work if I ever have to reinstall or migrate to another machine&lt;/li&gt;
&lt;li&gt;I can easily identify problematic configs and revert them&lt;/li&gt;
&lt;li&gt;I don&amp;rsquo;t forget the reasoning behind a particular security decision or config change in 6 months&lt;/li&gt;
&lt;li&gt;I can identify what changes I made as opposed to changes made by software or other people&lt;/li&gt;
&lt;li&gt;I can compare the current state of my configs against a &amp;lsquo;known-good&amp;rsquo; config&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The &lt;code&gt;/etc&lt;/code&gt; directory will contain most of my configs. Outside of /etc I will need to pay attention to my dotfiles which I like to manage with GNU Stow.&lt;/p&gt;
&lt;p&gt;Etckeeper is a neat piece of software that turns &lt;code&gt;/etc&lt;/code&gt; into a git repository. It automatically commits changes before and after package installations, and I can manually commit my own changes too.&lt;/p&gt;
&lt;h2 id=&#34;2-installation&#34;&gt;2. Installation&lt;/h2&gt;
&lt;p&gt;Kicksecure protects itself by isolating system maintainence tasks from daily activity. This means administrative tasks like installing new software cannot be performed from the standard user account, even with sudo. You have to reboot and select an option to boot into a session with the sysmaint account, make your changes, and then reboot back into the standard user session to continue with daily usage.&lt;/p&gt;
&lt;p&gt;Having booted with sysmaint, I install Etckeeper from the Debian repositories:&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 etckeeper
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;3-etckeeper-basics&#34;&gt;3. Etckeeper Basics&lt;/h2&gt;
&lt;p&gt;Do the standard git stuff:&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;git config --global user.email &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;person@email.com&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;git config --global user.name &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;I&amp;#39;m a person with a name!&amp;#34;&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&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;I start by initializing the repository:
&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:#e6db74&#34;&gt;```&lt;/span&gt;bash
&lt;/span&gt;&lt;/span&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;etckeeper init
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And making my initial commit:&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;etckeeper commit &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Initial configuration snapshot&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Etckeeper hooks into apt. When I install or remove packages, it automatically commits them to the git repo. So I don&amp;rsquo;t need to worry about doing a commit myself! I only need to commit after I make manual changes in /etc.&lt;/p&gt;
&lt;p&gt;For configuration changes I make outside of apt:&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;# within /etc&lt;/span&gt;
&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;Descriptive message about what changed&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;4-viewing-history&#34;&gt;4. Viewing History&lt;/h2&gt;
&lt;p&gt;Standard git commands work:&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 log --oneline
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;View a specific change:&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;git show COMMIT_HASH
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Compare two states:&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;git diff COMMIT_HASH1 COMMIT_HASH2
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;5-the-gitignore-file&#34;&gt;5. The .gitignore File&lt;/h2&gt;
&lt;p&gt;Some files in &lt;code&gt;/etc&lt;/code&gt; shouldn&amp;rsquo;t be tracked: temporary files, secrets, or auto-generated content.&lt;/p&gt;
&lt;p&gt;On my server I ended up configuring a ZFS RAID. Here&amp;rsquo;s an example of excluding tracking of my ZFS cache with a few other things in &lt;code&gt;/etc/.gitignore&lt;/code&gt;:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;# Ignore auto-generated ZFS cache
/zfs/zfs-list.cache/

# Example: ignore temporary files
*.tmp
*.swp
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;After editing:&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;git add .gitignore
&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;Updated gitignore to exclude my auto-generated ZFS cache&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-backups&#34;&gt;6. Backups&lt;/h2&gt;
&lt;p&gt;Always back up your changes so you have access to these records if your system breaks! In my case, I back up to a local &amp;lsquo;bare repository&amp;rsquo; on my BTRFS backup drive. I explain how I set up this drive on Kicksecure in a &lt;a href=&#34;../kicksecure-btrfs-storage/&#34;&gt;later post&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Additionally, because I&amp;rsquo;d like to be able to identify known-good states of my configs even if an attacker compromises my server or network, I sanatize + compress + back up my data (or just the hashes for big files) to an air-gapped system regularly using something similar to a &lt;a href=&#34;https://en.wikipedia.org/wiki/Unidirectional_network&#34;&gt;data diode&lt;/a&gt;. The air gap setup is beyond the scope of this post, but I thought the suggestion might interest readers!&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s how I back up my /etc repo:&lt;/p&gt;
&lt;h3 id=&#34;61-create-a-bare-repository&#34;&gt;6.1. Create a Bare Repository&lt;/h3&gt;
&lt;p&gt;On my already mounted BTRFS storage:&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;mkdir -p /mnt/storage_name/host_vault/kicksecure-etckeeper.git
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;git init --bare /mnt/storage_name/host_vault/kicksecure-etckeeper.git
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;62-add-as-remote&#34;&gt;6.2. Add as Remote&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;cd /etc
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;git remote add localvault /mnt/storage_name/host_vault/kicksecure-etckeeper.git
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;63-push-changes&#34;&gt;6.3. Push 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;git push -u localvault main
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Future pushes:&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;git push localvault
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;64-push-after-every-commit&#34;&gt;6.4. Push After Every Commit&lt;/h3&gt;
&lt;p&gt;This became a habit:&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;etckeeper commit &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Made a change&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; git push localvault
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;7-what-to-track&#34;&gt;7. What to Track&lt;/h2&gt;
&lt;p&gt;Here&amp;rsquo;s what I have tracked so far in &lt;code&gt;/etc&lt;/code&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;/etc/fstab&lt;/code&gt; - Mount point config&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/etc/crypttab&lt;/code&gt; - Encrypted drive config&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/etc/ufw/&lt;/code&gt; - Firewall rules&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/etc/ssh/&lt;/code&gt; - SSH configuration&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/etc/permission-hardener.d/&lt;/code&gt; - Kicksecure permission whitelists&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/etc/systemd/system/&lt;/code&gt; - Custom systemd units&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/etc/modprobe.d/&lt;/code&gt; - Kernel module configuration&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;8-whats-next&#34;&gt;8. What&amp;rsquo;s Next&lt;/h2&gt;
&lt;p&gt;&lt;a href=&#34;../kicksecure-btrfs-storage/&#34;&gt;Encrypted BTRFS Storage on Kicksecure&lt;/a&gt;&lt;/p&gt;
</content>
    </item>
    
    <item>
      <title>Encrypted BTRFS Storage on Kicksecure</title>
      <link>https://mattyspangler.codeberg.page/posts/kicksecure-btrfs-storage/</link>
      <pubDate>Sat, 07 Mar 2026 00:00:00 +0000</pubDate>
      
      <guid>https://mattyspangler.codeberg.page/posts/kicksecure-btrfs-storage/</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. It assumes you&amp;rsquo;ve already set up &lt;a href=&#34;../kicksecure-etckeeper/&#34;&gt;Etckeeper GitOps on Kicksecure&lt;/a&gt; so all configuration changes are tracked from the beginning.&lt;/p&gt;
&lt;h2 id=&#34;1-introduction&#34;&gt;1. Introduction&lt;/h2&gt;
&lt;p&gt;I needed encrypted storage for my container data. I decided to use a 4TB SSD with BTRFS for system backups and services that don&amp;rsquo;t require huge amounts of data. For services that require a lot like Nextcloud, I put them on a ZFS RAID 10 array with 4x 8TB HDDs.&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. It assumes you&amp;rsquo;ve already set up &lt;a href=&#34;../kicksecure-etckeeper/&#34;&gt;Etckeeper GitOps on Kicksecure&lt;/a&gt; so all configuration changes are tracked from the beginning.&lt;/p&gt;
&lt;h2 id=&#34;1-introduction&#34;&gt;1. Introduction&lt;/h2&gt;
&lt;p&gt;I needed encrypted storage for my container data. I decided to use a 4TB SSD with BTRFS for system backups and services that don&amp;rsquo;t require huge amounts of data. For services that require a lot like Nextcloud, I put them on a ZFS RAID 10 array with 4x 8TB HDDs.&lt;/p&gt;
&lt;p&gt;My requirements for the BTRFS storage were:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Encryption at rest&lt;/strong&gt; - If someone steals the drive, they can&amp;rsquo;t read it&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Automount at boot&lt;/strong&gt; - Using a keyfile so the server starts unattended&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Subvolumes&lt;/strong&gt; - Separate container data for easier snapshots&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Compression&lt;/strong&gt; - Save space where possible&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;2-identifying-the-drive&#34;&gt;2. Identifying the Drive&lt;/h2&gt;
&lt;p&gt;First, I identified my target drive by reviewing the size column from the &lt;code&gt;lsblk&lt;/code&gt; command. In this case, my device was sdb.&lt;/p&gt;
&lt;p&gt;Then got its device ID for reliable reference across reboots:&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 /dev/disk/by-id/ | grep sdb
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;I saw entries like:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;ata-CT4000BX500SSD1_XXXXXXXXXXXX -&amp;gt; ../../sdb
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;I used the &lt;code&gt;ata-&lt;/code&gt; prefixed ID—it&amp;rsquo;s more reliable than &lt;code&gt;/dev/sdb&lt;/code&gt; since the latter mapping can be subject to change.&lt;/p&gt;
&lt;h2 id=&#34;3-creating-luks-encryption&#34;&gt;3. Creating LUKS Encryption&lt;/h2&gt;
&lt;p&gt;Create the LUKS 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;cryptsetup luksFormat --type luks2 /dev/sdb
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;It prompted me to create a passphrase.&lt;/p&gt;
&lt;p&gt;Then I unlocked the encrypted 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;cryptsetup open /dev/sdb storage_crypt
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This created &lt;code&gt;/dev/mapper/storage_crypt&lt;/code&gt;, a path that allows for accessing the unlocked container.&lt;/p&gt;
&lt;h2 id=&#34;4-creating-btrfs-filesystem&#34;&gt;4. Creating BTRFS Filesystem&lt;/h2&gt;
&lt;p&gt;I created a BTRFS filesystem on the encrypted 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;mkfs.btrfs -L &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;StorageName&amp;#34;&lt;/span&gt; /dev/mapper/storage_crypt
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The output showed filesystem details including UUID and features like &lt;code&gt;extref&lt;/code&gt;, &lt;code&gt;skinny-metadata&lt;/code&gt;, &lt;code&gt;no-holes&lt;/code&gt;, and &lt;code&gt;free-space-tree&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id=&#34;5-creating-subvolumes&#34;&gt;5. Creating Subvolumes&lt;/h2&gt;
&lt;p&gt;I mounted temporarily to create subvolumes:&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;mkdir /mnt/storage_name
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;mount /dev/mapper/storage_crypt /mnt/storage_name
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Created a subvolume for 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;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;BTRFS allows you to perform rollbacks on subvolumes by creating snapshots. When you snapshot a subvolume, BTRFS doesn&amp;rsquo;t copy all the data, it only records new changes. You can snapshot terabytes of data in seconds, and the snapshot only takes up space for the actual differences.&lt;/p&gt;
&lt;p&gt;By organizing data into separate subvolumes like &lt;code&gt;@jellyfin_data&lt;/code&gt;, &lt;code&gt;@nextcloud_data&lt;/code&gt;, &lt;code&gt;@homeassistant_data&lt;/code&gt;, and &lt;code&gt;@gitea_data&lt;/code&gt;, I can make snapshots that act independently for my different podman services. This makes it easy to roll back a single container&amp;rsquo;s data without affecting others.&lt;/p&gt;
&lt;p&gt;I unmounted when I was done with the mount:&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;umount /mnt/storage_name
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;6-keyfile-for-automount&#34;&gt;6. Keyfile for Automount&lt;/h2&gt;
&lt;p&gt;I want to unlock the drive automatically at boot rather than always entering the passphrase. I start by creating a keyfile with random data in it:&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;dd &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;/dev/urandom of&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;/root/storage.key bs&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt; count&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;32&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;chmod &lt;span style=&#34;color:#ae81ff&#34;&gt;400&lt;/span&gt; /root/storage.key
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;I added it to LUKS so that it can be used to unlock:&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;cryptsetup luksAddKey /dev/sdb /root/storage.key
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;I entered my original passphrase when prompted.&lt;/p&gt;
&lt;h2 id=&#34;7-configuring-crypttab&#34;&gt;7. Configuring crypttab&lt;/h2&gt;
&lt;p&gt;Got the LUKS UUID:&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;cryptsetup luksUUID /dev/sdb
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Backed up the existing crypttab:&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;cp /etc/crypttab /etc/crypttab.old
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Added the encrypted drive to &lt;code&gt;/etc/crypttab&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;echo &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;storage_crypt UUID=YOUR_DISK_UUID /root/storage.key luks,nofail,discard&amp;#34;&lt;/span&gt; &amp;gt;&amp;gt; /etc/crypttab
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The options explained:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;luks&lt;/code&gt; - Use LUKS encryption&lt;/li&gt;
&lt;li&gt;&lt;code&gt;nofail&lt;/code&gt; - Boot continues if the drive fails to unlock&lt;/li&gt;
&lt;li&gt;&lt;code&gt;discard&lt;/code&gt; - Enable TRIM for SSDs&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;9-configuring-fstab&#34;&gt;9. Configuring fstab&lt;/h2&gt;
&lt;p&gt;Added the BTRFS mount to &lt;code&gt;/etc/fstab&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;echo &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;/dev/mapper/storage_crypt /mnt/storage_name btrfs defaults,nofail,compress=zstd,noatime,subvol=@data 0 0&amp;#34;&lt;/span&gt; &amp;gt;&amp;gt; /etc/fstab
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The options:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;defaults&lt;/code&gt; - Standard mount options&lt;/li&gt;
&lt;li&gt;&lt;code&gt;nofail&lt;/code&gt; - Boot continues if mount fails&lt;/li&gt;
&lt;li&gt;&lt;code&gt;compress=zstd&lt;/code&gt; - Transparent compression (good balance of speed and ratio)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;noatime&lt;/code&gt; - Don&amp;rsquo;t update access times (improves performance)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;subvol=@data&lt;/code&gt; - Mount the @data subvolume I created&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;10-testing&#34;&gt;10. Testing&lt;/h2&gt;
&lt;p&gt;Reloaded systemd and tested the mount:&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 daemon-reload
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;mount -a
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Verified it mounted:&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;lsblk
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;I saw my encrypted container mounted at &lt;code&gt;/mnt/storage_name&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id=&#34;11-permissions&#34;&gt;11. Permissions&lt;/h2&gt;
&lt;p&gt;Set ownership for the container user:&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;chown -R user:user /mnt/storage_name
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;chmod &lt;span style=&#34;color:#ae81ff&#34;&gt;770&lt;/span&gt; /mnt/storage_name
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;12-creating-additional-subvolumes&#34;&gt;12. Creating Additional Subvolumes&lt;/h2&gt;
&lt;p&gt;As I added more services, I created separate subvolumes:&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;mount /dev/mapper/storage_crypt /mnt/storage_name
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;btrfs subvolume create /mnt/storage_name/@gitea_data
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;btrfs subvolume create /mnt/storage_name/@cache
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;umount /mnt/storage_name
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Then added additional fstab entries or adjusted container volume mounts.&lt;/p&gt;
&lt;h2 id=&#34;13-btrfs-snapshots&#34;&gt;13. BTRFS Snapshots&lt;/h2&gt;
&lt;p&gt;One advantage of BTRFS is easy snapshots. I create snapshots before upgrades:&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;btrfs subvolume snapshot /mnt/storage_name/@data /mnt/storage_name/@data-backup-&lt;span style=&#34;color:#66d9ef&#34;&gt;$(&lt;/span&gt;date +%Y%m%d&lt;span style=&#34;color:#66d9ef&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;14-troubleshooting&#34;&gt;14. Troubleshooting&lt;/h2&gt;
&lt;h3 id=&#34;drive-doesnt-mount-at-boot&#34;&gt;Drive Doesn&amp;rsquo;t Mount at Boot&lt;/h3&gt;
&lt;p&gt;Check if the keyfile path is correct in &lt;code&gt;/etc/crypttab&lt;/code&gt;. It must be accessible during early boot.&lt;/p&gt;
&lt;p&gt;Check journal 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 -xe
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;wrong-subvolume-mounted&#34;&gt;Wrong Subvolume Mounted&lt;/h3&gt;
&lt;p&gt;If you&amp;rsquo;re not seeing your data, check that &lt;code&gt;/etc/fstab&lt;/code&gt; includes &lt;code&gt;subvol=@data&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id=&#34;permission-denied-for-containers&#34;&gt;Permission Denied for Containers&lt;/h3&gt;
&lt;p&gt;If containers can&amp;rsquo;t write, check ownership:&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 /mnt/storage_name
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;For Podman containers, I use &lt;code&gt;podman unshare chown&lt;/code&gt; to set container-internal UIDs.&lt;/p&gt;
&lt;h2 id=&#34;15-whats-next&#34;&gt;15. What&amp;rsquo;s Next&lt;/h2&gt;
&lt;p&gt;&lt;a href=&#34;../kicksecure-podman-permissions/&#34;&gt;Podman Permissions 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>
