<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: JanJaap Driesen</title>
    <description>The latest articles on DEV Community by JanJaap Driesen (@deputynl).</description>
    <link>https://dev.to/deputynl</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3857530%2F1b349f33-b683-45e8-9ec8-e3138d56e759.png</url>
      <title>DEV Community: JanJaap Driesen</title>
      <link>https://dev.to/deputynl</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/deputynl"/>
    <language>en</language>
    <item>
      <title>I was tired of breaking my Traefik mTLS config, so I built a GUI for it</title>
      <dc:creator>JanJaap Driesen</dc:creator>
      <pubDate>Thu, 02 Apr 2026 11:44:42 +0000</pubDate>
      <link>https://dev.to/deputynl/i-was-tired-of-breaking-my-traefik-mtls-config-so-i-built-a-gui-for-it-ld5</link>
      <guid>https://dev.to/deputynl/i-was-tired-of-breaking-my-traefik-mtls-config-so-i-built-a-gui-for-it-ld5</guid>
      <description>&lt;h1&gt;
  
  
  I was tired of breaking my Traefik mTLS config, so I built a GUI for it
&lt;/h1&gt;

&lt;p&gt;If you've ever tried to set up mutual TLS (mTLS) with Traefik, you know the feeling. You get it working. Something changes. You dig back into YAML, reference the docs, check the cert paths, wonder if it's the entrypoint config or the dynamic config or the certificate store. An hour later you're back where you started.&lt;/p&gt;

&lt;p&gt;I run Traefik at home to expose a bunch of self-hosted services — some on my LAN only, some to the internet, some requiring client certificates for extra security. After one too many "why is mTLS broken again" sessions, I decided to build something that would make this manageable once and not have to think about it again.&lt;/p&gt;

&lt;p&gt;The result is &lt;strong&gt;traefik-gui&lt;/strong&gt;: a lightweight web GUI for managing Traefik, packaged as a single Docker container.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it does
&lt;/h2&gt;

&lt;p&gt;The core features:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;mTLS certificate management&lt;/strong&gt; is the reason this exists. You can create and manage client certificates from the UI. When you create a cert for a device or user, you download a zip containing all the formats you'd need (PEM, P12, etc.) plus a README explaining how to install it — on a phone, browser, or system. No more OpenSSL one-liners.&lt;br&gt;
&lt;strong&gt;Access log viewer&lt;/strong&gt; lets you tail and browse Traefik access logs without SSHing into your server. When something's not routing right, this is usually the first place you look.&lt;br&gt;
&lt;strong&gt;Static config editor&lt;/strong&gt; means you can edit &lt;code&gt;traefik.yml&lt;/code&gt; from the browser.&lt;br&gt;
&lt;strong&gt;Dynamic file config&lt;/strong&gt; gives you both simple forms for common use cases and raw YAML editing for everything else.&lt;/p&gt;
&lt;h2&gt;
  
  
  The opinionated part
&lt;/h2&gt;

&lt;p&gt;This tool makes assumptions. It assumes you're:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Using file-based dynamic configuration (not Kubernetes or Consul)&lt;/li&gt;
&lt;li&gt;Wanting to expose services on both your LAN and the internet&lt;/li&gt;
&lt;li&gt;Managing your own certificates (not relying solely on Let's Encrypt ACME)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Those assumptions are baked into the UI — there are two entrypoints, mTLS is supported on one of them, and the forms are designed around that workflow. If your setup matches, it feels polished. If it doesn't, there are more flexible tools out there (Mantrae, Traefikr — both worth looking at).&lt;/p&gt;
&lt;h2&gt;
  
  
  Getting started
&lt;/h2&gt;

&lt;p&gt;Single container, no database, Go backend:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# docker-compose.yml&lt;/span&gt;
&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;traefik-gui&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ghcr.io/deputynl/traefik-gui:latest&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;traefik-gui&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unless-stopped&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;TRAEFIK_CONFIG_PATH&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/etc/traefik/traefik.yml&lt;/span&gt;
      &lt;span class="na"&gt;TRAEFIK_API_URL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;http://traefik:8080&lt;/span&gt;
      &lt;span class="na"&gt;TRAEFIK_GUI_USER&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${TRAEFIK_GUI_USER:-admin}&lt;/span&gt;
      &lt;span class="na"&gt;TRAEFIK_GUI_PASSWORD&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${TRAEFIK_GUI_PASSWORD:-admin}&lt;/span&gt;
      &lt;span class="na"&gt;TRAEFIK_ACME_PATH&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/etc/traefik/acme.json&lt;/span&gt;
      &lt;span class="na"&gt;TRAEFIK_CONTAINER_NAME&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;traefik&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;/etc/traefik:/etc/traefik&lt;/span&gt;          &lt;span class="c1"&gt;# read+write for config changes&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;traefik-mtls:/etc/traefik/mtls&lt;/span&gt;     &lt;span class="c1"&gt;# CA, CA key + client certs, persisted across restarts&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;/var/run/docker.sock:/var/run/docker.sock:ro&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;traefik&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;8888:8888&lt;/span&gt;

&lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;traefik&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;external&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;

&lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;traefik-mtls&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open &lt;code&gt;http://localhost:8888&lt;/code&gt; and you're in.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Go, why single container
&lt;/h2&gt;

&lt;p&gt;Go compiles to a single binary with no runtime dependencies, which made packaging as a minimal container straightforward. The whole thing is one &lt;code&gt;docker run&lt;/code&gt; away from working. No Node, no Python, no database to manage.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;p&gt;It's a hobby project, but I use it daily and intend to keep maintaining it. If you run into issues or have ideas, issues and PRs are welcome on GitHub.&lt;/p&gt;

&lt;h2&gt;
  
  
  → &lt;a href="https://github.com/deputynl/traefik-gui" rel="noopener noreferrer"&gt;github.com/deputynl/traefik-gui&lt;/a&gt; (MIT)
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;If you're running Traefik at home and have questions about mTLS setup, happy to help in the comments.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>traefik</category>
      <category>selfhosted</category>
      <category>docker</category>
      <category>go</category>
    </item>
  </channel>
</rss>
