<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>DevOps on Tom Meurs</title>
    <link>https://tommeurs.nl/en/tags/devops/</link>
    <description>Recent content in DevOps on Tom Meurs</description>
    <generator>Hugo</generator>
    <language>en</language>
    <copyright>© Tom Meurs</copyright>
    <lastBuildDate>Tue, 29 Sep 2026 09:00:00 +0200</lastBuildDate>
    <atom:link href="https://tommeurs.nl/en/tags/devops/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Supply Chain Security in GitLab CI: SBOM, cosign, and the Policy That Blocks</title>
      <link>https://tommeurs.nl/en/posts/supply-chain-security-gitlab-ci/</link>
      <pubDate>Tue, 29 Sep 2026 09:00:00 +0200</pubDate>
      <guid>https://tommeurs.nl/en/posts/supply-chain-security-gitlab-ci/</guid>
      <description>&lt;blockquote&gt;&#xA;&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt; A pipeline that scans images and writes an SBOM, with nothing checking either at admission, had been green for a year and had stopped nothing. Four steps below: an SBOM Trivy can read back, a cosign signature on the digest with the key in Vault, the SBOM attached as an attestation, and a Kyverno policy that refuses the rest. Then a table of which SLSA Build L3 requirements a self-hosted GitLab runner meets, and the two it doesn&amp;rsquo;t.&lt;/p&gt;</description>
    </item>
    <item>
      <title>NIS2 for Platform Teams: Which Control Satisfies Which Article</title>
      <link>https://tommeurs.nl/en/posts/nis2-for-platform-teams/</link>
      <pubDate>Sun, 20 Sep 2026 21:00:00 +0200</pubDate>
      <guid>https://tommeurs.nl/en/posts/nis2-for-platform-teams/</guid>
      <description>&lt;blockquote&gt;&#xA;&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt; The Cyberbeveiligingswet has applied since 15 August 2026. Below, the ten measures of &lt;span class=&#34;def&#34; tabindex=&#34;0&#34; data-def=&#34;Cbw art. 21: the duty of care. Appropriate and proportionate measures, of which paragraph 3 names ten.&#34;&gt;article 21&lt;/span&gt;&#xA; mapped onto controls that actually run, a Kyverno policy that proves one of them, the reporting deadlines as three Prometheus alerting rules, and the four duties no YAML covers. The first run of that policy found seven pods in my own cluster I&amp;rsquo;d have sworn were mirrored.&lt;/p&gt;</description>
    </item>
    <item>
      <title>Infrastructure as Code for People Who Need to Understand</title>
      <link>https://tommeurs.nl/en/posts/infrastructure-as-code-control/</link>
      <pubDate>Sat, 30 May 2026 19:45:00 +0100</pubDate>
      <guid>https://tommeurs.nl/en/posts/infrastructure-as-code-control/</guid>
      <description>&lt;p&gt;Someone opens a cloud console, clicks through a wizard, accepts a few defaults, and a resource appears. The dashboard goes green and everyone moves on. That&amp;rsquo;s still how a lot of infrastructure gets built in 2026, and I&amp;rsquo;ve built some of it that way myself.&lt;/p&gt;&#xA;&lt;p&gt;I can&amp;rsquo;t work like that for long. When I click a button and infrastructure appears, it feels borrowed. I want the configuration written down somewhere I can read it back, and I want to know why something exists, not only that it exists. The console gives me a green tick. It doesn&amp;rsquo;t give me any of that.&lt;/p&gt;</description>
    </item>
    <item>
      <title>Building an Internal Developer Platform: Where to Start</title>
      <link>https://tommeurs.nl/en/posts/internal-developer-platform/</link>
      <pubDate>Wed, 06 May 2026 19:30:00 +0100</pubDate>
      <guid>https://tommeurs.nl/en/posts/internal-developer-platform/</guid>
      <description>&lt;blockquote&gt;&#xA;&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt; An Internal Developer Platform is a thin layer between what a developer wants and what the cluster demands. Mine is a Helm base chart, ArgoCD and a handful of Kyverno policies, about 500 lines of my own code. Start with those three, and add observability and a portal only when the tickets tell you to.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;Should you build an Internal Developer Platform? Usually, yes. It&amp;rsquo;s the how that wrecks teams.&lt;/p&gt;</description>
    </item>
    <item>
      <title>Arch vs NixOS as a Workstation: Professional and Personal Use</title>
      <link>https://tommeurs.nl/en/posts/arch-vs-nixos-workstation/</link>
      <pubDate>Tue, 03 Mar 2026 21:28:00 +0100</pubDate>
      <guid>https://tommeurs.nl/en/posts/arch-vs-nixos-workstation/</guid>
      <description>&lt;blockquote&gt;&#xA;&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt; I&amp;rsquo;ve run Arch and NixOS as my daily machine, and today it&amp;rsquo;s Arch on both desktops and NixOS on the servers. Arch&amp;rsquo;s reputation for breaking is mostly a missing snapshot. With btrfs, Snapper, dotfiles in git and a package list, a rollback takes 2 minutes and the fear of updates goes away. NixOS earns its learning curve when several machines have to stay identical, and on a server I wouldn&amp;rsquo;t run anything else.&lt;/p&gt;</description>
    </item>
    <item>
      <title>CTF and Forensics Skills That Make You a Better DevOps Engineer</title>
      <link>https://tommeurs.nl/en/posts/ctf-skills-for-devops/</link>
      <pubDate>Fri, 27 Feb 2026 19:52:00 +0100</pubDate>
      <guid>https://tommeurs.nl/en/posts/ctf-skills-for-devops/</guid>
      <description>&lt;blockquote&gt;&#xA;&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt; I spend my evenings on Hack The Box and it&amp;rsquo;s made me better at my day job, which is platform engineering and has nothing to do with pentesting. The first ten minutes on a &lt;span class=&#34;def&#34; tabindex=&#34;0&#34; data-def=&#34;Capture The Flag: a security exercise where you break into a deliberately vulnerable system and prove it by retrieving a planted token, the flag.&#34;&gt;CTF&lt;/span&gt;&#xA; box and the first ten minutes on a broken production server are the same loop, with &amp;ldquo;flag&amp;rdquo; swapped for &amp;ldquo;incident&amp;rdquo;. Below is the toolkit that loop runs on, from one host up to a Kubernetes pod, with the CTF use and the work use side by side for every command.&lt;/p&gt;</description>
    </item>
    <item>
      <title>Data-Driven Kubernetes Migration: Why You Need Metrics Before You Move</title>
      <link>https://tommeurs.nl/en/posts/kubernetes-resource-sizing/</link>
      <pubDate>Sun, 08 Feb 2026 14:00:00 +0100</pubDate>
      <guid>https://tommeurs.nl/en/posts/kubernetes-resource-sizing/</guid>
      <description>&lt;blockquote&gt;&#xA;&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt; An e-commerce client moved a PHP monolith to EKS in October, two weeks before Black Friday, with resource limits guessed from the old server specs. Mid-morning the pods hit their memory limit, died in a loop for four hours, and the estimate was €240,000 in lost revenue. The second half is how to get those numbers from load tests and VPA recommendations instead of guessing them.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;&amp;ldquo;We want to migrate to Kubernetes by November.&amp;rdquo;&lt;/p&gt;</description>
    </item>
    <item>
      <title>Kubernetes RBAC: Least Privilege in Practice</title>
      <link>https://tommeurs.nl/en/posts/kubernetes-rbac-least-privilege/</link>
      <pubDate>Tue, 19 Aug 2025 10:00:00 +0200</pubDate>
      <guid>https://tommeurs.nl/en/posts/kubernetes-rbac-least-privilege/</guid>
      <description>&lt;blockquote&gt;&#xA;&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt; Kubernetes ships with a permission model that hands every pod a token and lets the default service account collect rights nobody remembers granting. Least privilege means a service account per workload, no token unless the app talks to the API, and roles that name the namespace, the resource and the verb. Below are the four RBAC resources, the five settings that cap the blast radius, the bindings I copy most often, and how I check and enforce all of it.&lt;/p&gt;</description>
    </item>
    <item>
      <title>Kyverno Policies: Governance as Code for Kubernetes</title>
      <link>https://tommeurs.nl/en/posts/kyverno-policies-governance/</link>
      <pubDate>Mon, 14 Jul 2025 10:00:00 +0200</pubDate>
      <guid>https://tommeurs.nl/en/posts/kyverno-policies-governance/</guid>
      <description>&lt;p&gt;I used to keep a wiki page titled &amp;ldquo;Cluster conventions&amp;rdquo;. Resource limits on everything. No &lt;code&gt;:latest&lt;/code&gt; tags. No deploys in the &lt;code&gt;default&lt;/code&gt; namespace. It was a good page. Nobody read it. Six months in, half the cluster broke those rules and I only found out when something fell over.&lt;/p&gt;&#xA;&lt;p&gt;A rule that lives in a doc is a suggestion. A rule the API server refuses to accept is governance. That gap is the whole reason this post exists.&lt;/p&gt;</description>
    </item>
    <item>
      <title>Progressive Delivery with Argo Rollouts: Canary and Blue-Green Deployments</title>
      <link>https://tommeurs.nl/en/posts/argo-rollouts-progressive-delivery/</link>
      <pubDate>Fri, 20 Jun 2025 10:00:00 +0200</pubDate>
      <guid>https://tommeurs.nl/en/posts/argo-rollouts-progressive-delivery/</guid>
      <description>&lt;p&gt;A standard Kubernetes Deployment had served me well for a long time. Push a new image tag, watch the pods roll, done. It was simple, it was declarative, and most of the time nothing went wrong. The rolling update even gave me a warm feeling of safety: old pods only get torn down once new ones are ready.&lt;/p&gt;&#xA;&lt;p&gt;That feeling is a lie. A rolling update protects you from pods that fail to start. It does nothing to protect you from pods that start perfectly and then serve broken responses. The container is healthy, the readiness probe is green, and your new code is quietly returning 500s to every single user. Within seconds, 100% of your traffic is hitting code that nobody validated under real load.&lt;/p&gt;</description>
    </item>
    <item>
      <title>Container Image Scanning with Trivy in Your CI Pipeline</title>
      <link>https://tommeurs.nl/en/posts/trivy-container-scanning/</link>
      <pubDate>Sun, 08 Jun 2025 10:00:00 +0200</pubDate>
      <guid>https://tommeurs.nl/en/posts/trivy-container-scanning/</guid>
      <description>&lt;p&gt;Pull a base image, copy in your code, push it to production. That&amp;rsquo;s the loop most of us run on autopilot. The part nobody looks at is the base image itself, which quietly drags in a few hundred packages you never chose. Any one of them could carry a known &lt;span class=&#34;def&#34; tabindex=&#34;0&#34; data-def=&#34;Common Vulnerabilities and Exposures: the public identifier for one specific vulnerability, in the form CVE-2026-1234. The identifier says nothing about severity.&#34;&gt;CVE&lt;/span&gt;&#xA;, and you&amp;rsquo;d have no idea.&lt;/p&gt;</description>
    </item>
    <item>
      <title>Automating Semantic Versioning with GitLab CI</title>
      <link>https://tommeurs.nl/en/posts/semantic-versioning-gitlab/</link>
      <pubDate>Tue, 27 May 2025 19:44:00 +0200</pubDate>
      <guid>https://tommeurs.nl/en/posts/semantic-versioning-gitlab/</guid>
      <description>&lt;p&gt;Picture the end state first. You merge a branch to main, walk away to get coffee, and by the time you&amp;rsquo;re back the project has a new version tag, a fresh changelog entry, and a GitLab release with notes written from your commits. Nobody decided &amp;ldquo;is this 1.4.0 or 1.3.5?&amp;rdquo; because nobody had to. The version fell out of the work itself.&lt;/p&gt;&#xA;&lt;p&gt;That&amp;rsquo;s where I want to get to in this post. I run a self-hosted GitLab and I do not want versioning to be a thing I think about. A version number that someone picks by hand is a tiny black box: it depends on whether that person remembered the rules, was paying attention, and agreed with everyone else about what counts as a breaking change. I would rather the number be a consequence of the commits, something I can inspect and reproduce.&lt;/p&gt;</description>
    </item>
    <item>
      <title>GitLab CI for Kubernetes: From Commit to Deployment</title>
      <link>https://tommeurs.nl/en/posts/gitlab-ci-kubernetes/</link>
      <pubDate>Thu, 15 May 2025 20:33:00 +0200</pubDate>
      <guid>https://tommeurs.nl/en/posts/gitlab-ci-kubernetes/</guid>
      <description>&lt;p&gt;For a long time my CI lived on someone else&amp;rsquo;s servers. Push code, wait for a hosted runner, watch the minutes tick down against a monthly quota, hope the provider doesn&amp;rsquo;t change the rules next quarter. It worked, right up until I started asking where my source code actually sat while it was being built, and who else could see it.&lt;/p&gt;&#xA;&lt;p&gt;So I moved GitLab in-house. I run it self-hosted now, on my own hardware, next to the clusters it deploys to. That&amp;rsquo;s &lt;a href=&#34;https://tommeurs.nl/en/posts/sovereign-infrastructure/&#34;&gt;sovereignty&lt;/a&gt; applied to CI/CD: my code, my builds, my artifacts, no vendor that can change pricing, deprecate a feature I depend on, or read my repositories without me knowing.&lt;/p&gt;</description>
    </item>
    <item>
      <title>ArgoCD for Beginners: Your First GitOps Deployment</title>
      <link>https://tommeurs.nl/en/posts/argocd-beginners/</link>
      <pubDate>Sun, 16 Mar 2025 19:15:00 +0100</pubDate>
      <guid>https://tommeurs.nl/en/posts/argocd-beginners/</guid>
      <description>&lt;p&gt;Here is the thing I never want to do again: ask a cluster what it is running and not trust the answer. For years that was normal. Someone ran &lt;code&gt;kubectl apply&lt;/code&gt;, maybe from a laptop, maybe from a CI job nobody could find anymore, and the live state slowly drifted away from anything written down. When I switched to GitOps, that whole category of uncertainty disappeared. I push to Git, and the cluster converges to match. If I want to know what is deployed, I read a file.&lt;/p&gt;</description>
    </item>
  </channel>
</rss>
