{"id":1312,"date":"2026-03-07T09:46:37","date_gmt":"2026-03-07T08:46:37","guid":{"rendered":"https:\/\/simon-frey.com\/blog\/?p=1312"},"modified":"2026-03-09T10:17:34","modified_gmt":"2026-03-09T09:17:34","slug":"this-website-runs-on-a-self-hosted-kubernetes-cluster","status":"publish","type":"post","link":"https:\/\/simon-frey.com\/blog\/this-website-runs-on-a-self-hosted-kubernetes-cluster\/","title":{"rendered":"This Website runs on an autoscaling, european, self-hosted Kubernetes cluster"},"content":{"rendered":"\n<p>My personal website, simon-frey.com, now runs on a self-managed Kubernetes cluster on Hetzner Cloud. This is, by any reasonable measure, complete overkill for a personal website that gets a few hundred visitors a day. But hey, sometimes you got to do things just for the fun of it.<\/p>\n\n\n\n<p>For years, simon-frey.com lived on <a href=\"https:\/\/uberspace.de\">Uberspace<\/a>, a pay-what-you-want shared hosting provider from Germany. I paid 5 euros a month, SSH&#8217;d into a box when I needed to change something, and it just worked. Uberspace is genuinely great at what they do and I have nothing bad to say about them. If you want simple, reliable hosting and you don&#8217;t need to overcomplicate things, they&#8217;re an excellent choice.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Why I Moved<\/h2>\n\n\n\n<p>The honest answer is that I wanted to dogfood my own infrastructure. I had already built a Kubernetes cluster on Hetzner Cloud (you can read about that setup <a href=\"https:\/\/simon-frey.com\/blog\/kubernetes-on-hetzner-cloud\/\">here<\/a>), and running my actual website on it felt like the natural next step. There is a big difference between running tutorial workloads on a cluster and running something you actually care about.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">How It Works<\/h2>\n\n\n\n<p>The request flow for simon-frey.com goes through quite a few layers now, which is both the beauty and the absurdity of this setup.<\/p>\n\n\n\n<div class=\"wp-block-merpress-mermaidjs diagram-source-mermaid\"><pre class=\"mermaid\">graph LR\n    User([User]) --> Cloudflare[Cloudflare DNS]\n    Cloudflare --> LB[Hetzner Load Balancer]\n    LB --> Traefik[Traefik Ingress]\n    Traefik --> Cache[Nginx Cache]\n    Traefik -->|\/files| MinIO[MinIO]\n    Cache -->|\/| PHP[PHP + git-sync]\n    Cache -->|\/blog| WP[WordPress]\n    WP --> MySQL[(MySQL)]\n    PHP -.->|pulls every 60s| GitHub([GitHub Repo])<\/pre><\/div>\n\n\n\n<p>DNS is currently handled by Cloudflare, though I&#8217;m planning to move to a European provider like Bunny CDN at some point because I&#8217;d prefer to keep things closer to home. Cloudflare points to a Hetzner Load Balancer, which distributes traffic across the Kubernetes worker nodes. From there, Traefik acts as the ingress controller. It terminates TLS with certificates from Let&#8217;s Encrypt, which are automatically managed by cert-manager.<\/p>\n\n\n\n<p>The Nginx cache sits in front of everything and is honestly the most important piece of the whole setup. It&#8217;s configured with <code>proxy_cache_use_stale<\/code>, which means it will serve cached content even when the backends are slow, throwing errors, or completely down. The cache also does background updates, so the first visitor after a cache expiry doesn&#8217;t have to wait for the backend response.<\/p>\n\n\n\n<p>Behind the cache, the site is split into two backends. The main site at simon-frey.com is a custom PHP application that renders markdown files into HTML. The content lives in a GitHub repository and gets synced into the running pod via git-sync, a Kubernetes-native sidecar that polls every 60 seconds. When I push a commit to GitHub, the site updates within a minute, and git-sync automatically fires a webhook that purges the Nginx cache so visitors see the new content immediately.<\/p>\n\n\n\n<p>There&#8217;s also a MinIO instance serving static files under <a href=\"https:\/\/simon-frey.com\/files\">simon-frey.com\/files<\/a>. This handles larger assets that don&#8217;t belong in a git repository.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Autoscaling<\/h2>\n\n\n\n<p>The cluster has a cluster autoscaler that can spin up additional Hetzner worker nodes when resource pressure increases. In theory, this means the site can handle traffic spikes by scaling out. In practice, provisioning a new Hetzner server takes around 5 minutes, which is an eternity if your site just got posted on Hacker News. This is fine since the Nginx cache can absorb a lot of traffic on its own, but it&#8217;s worth being honest about the limitations.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Monitoring and Alerting<\/h2>\n\n\n\n<p>The cluster runs Victoria Metrics for metrics collection, with KWatch monitoring pod states and sending alerts via Pushover directly to my phone. I also have UpDown.io doing external HTTP checks, so I get notified if the site is unreachable from the outside, not just from the cluster&#8217;s perspective.<\/p>\n\n\n<div class=\"lazyblock-ad-Z1HYEPd wp-block-lazyblock-ad\"><div style=\"display:block;font-family:sans-serif; border:2px solid #00000020;padding: 0.5em;margin-top:1em;margin-bottom:1em;\">\n  <div style=\"display:flex;justify-content:center;align-items:center;gap:10px;\">\n  <div style=\"line-height: 1.3em;text-align:left;\"><h3>Highly skilled DevOps\/SRE Freelancer<\/h3>\n  <p>I am Simon, the author of this blog. And I have great news: <b style=\"font-weight:bold;\">You can work with me<\/b><\/p>\n  <p>As DevOps and Infrastructure freelancer, I will help you choose the right Infrastructure technology for your company, fix your cloud problems and support your team in building scalable products.<\/p>\n  <p>I work with Golang, Docker, Kubernetes, Google Cloud, AWS and Terraform.<\/p>\n  <p>Checkout my <a href=\"https:\/\/simon-frey.com\/cv\" target=\"_blank\">CV<\/a> to learn more or directly contact me via the button below.<\/p>\n  <\/div>  \n  <img decoding=\"async\" data-src=\"https:\/\/simon-frey.com\/cv\/img\/simon-frey.jpg\" alt=\"Simon Frey Header image\" style=\"height:10em;\" src=\"data:image\/svg+xml;base64,PHN2ZyB3aWR0aD0iMSIgaGVpZ2h0PSIxIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjwvc3ZnPg==\" class=\"lazyload\">\n\n  <\/div>\n  \n  <a href=\"mailto:contact@simon-frey.com\" style=\"display:block;text-align:center;color:black;text-decoration:none;border:solid 2px black;padding:10px;border-radius:5px;margin-top:1em;\">Let&#8217;s work together!<\/a>\n<\/div><\/div>\n\n\n<h2 class=\"wp-block-heading\">Cost<\/h2>\n\n\n\n<p>I went from paying 5 euros a month on Uberspace to roughly 14 euros a month for the Hetzner infrastructure. That&#8217;s nearly three times the cost for hosting a personal website, which is hard to justify on pure economics. But the cluster doesn&#8217;t just run my website. It also runs monitoring, a few other small services, and serves as a general playground for testing Kubernetes features and tools. The marginal cost of adding my website to the existing cluster is essentially zero since the resources it consumes are tiny, so the real comparison is more like &#8220;5 euros for hosting only my website&#8221; versus &#8220;14 euros for a whole platform that also hosts my website.&#8221;<\/p>\n\n\n\n<p><\/p>\n","protected":false},"excerpt":{"rendered":"<p>My personal website, simon-frey.com, now runs on a self-managed Kubernetes cluster on Hetzner Cloud. This is, by any reasonable measure, complete overkill for a personal website that gets a few hundred visitors a day. But&hellip;<\/p>\n<p><a href=\"https:\/\/simon-frey.com\/blog\/this-website-runs-on-a-self-hosted-kubernetes-cluster\/\" class=\"more-link\">Read more<span class=\"screen-reader-text\"> of This Website runs on an autoscaling, european, self-hosted Kubernetes cluster<\/span><span aria-hidden=\"true\"> &rarr;<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":"","_links_to":"","_links_to_target":""},"categories":[232,375,374,231],"tags":[],"class_list":["post-1312","post","type-post","status-publish","format-standard","hentry","category-devops","category-homelab","category-kubernetes-k8s","category-sre"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v27.4 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>This Website runs on an autoscaling, european, self-hosted Kubernetes cluster - Blog by Simon Frey<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/simon-frey.com\/blog\/this-website-runs-on-a-self-hosted-kubernetes-cluster\/\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Simon Frey\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"4 minutes\" \/>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"This Website runs on an autoscaling, european, self-hosted Kubernetes cluster - Blog by Simon Frey","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/simon-frey.com\/blog\/this-website-runs-on-a-self-hosted-kubernetes-cluster\/","twitter_misc":{"Written by":"Simon Frey","Est. reading time":"4 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/simon-frey.com\/blog\/this-website-runs-on-a-self-hosted-kubernetes-cluster\/#article","isPartOf":{"@id":"https:\/\/simon-frey.com\/blog\/this-website-runs-on-a-self-hosted-kubernetes-cluster\/"},"author":{"name":"Simon Frey","@id":"https:\/\/simon-frey.com\/blog\/#\/schema\/person\/34753982b648415636ee7a079f3e19a3"},"headline":"This Website runs on an autoscaling, european, self-hosted Kubernetes cluster","datePublished":"2026-03-07T08:46:37+00:00","dateModified":"2026-03-09T09:17:34+00:00","mainEntityOfPage":{"@id":"https:\/\/simon-frey.com\/blog\/this-website-runs-on-a-self-hosted-kubernetes-cluster\/"},"wordCount":707,"publisher":{"@id":"https:\/\/simon-frey.com\/blog\/#\/schema\/person\/34753982b648415636ee7a079f3e19a3"},"articleSection":["DevOps","Homelab","Kubernetes (k8s)","SRE"],"inLanguage":"en-US"},{"@type":"WebPage","@id":"https:\/\/simon-frey.com\/blog\/this-website-runs-on-a-self-hosted-kubernetes-cluster\/","url":"https:\/\/simon-frey.com\/blog\/this-website-runs-on-a-self-hosted-kubernetes-cluster\/","name":"This Website runs on an autoscaling, european, self-hosted Kubernetes cluster - Blog by Simon Frey","isPartOf":{"@id":"https:\/\/simon-frey.com\/blog\/#website"},"datePublished":"2026-03-07T08:46:37+00:00","dateModified":"2026-03-09T09:17:34+00:00","breadcrumb":{"@id":"https:\/\/simon-frey.com\/blog\/this-website-runs-on-a-self-hosted-kubernetes-cluster\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/simon-frey.com\/blog\/this-website-runs-on-a-self-hosted-kubernetes-cluster\/"]}]},{"@type":"BreadcrumbList","@id":"https:\/\/simon-frey.com\/blog\/this-website-runs-on-a-self-hosted-kubernetes-cluster\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/simon-frey.com\/blog\/"},{"@type":"ListItem","position":2,"name":"This Website runs on an autoscaling, european, self-hosted Kubernetes cluster"}]},{"@type":"WebSite","@id":"https:\/\/simon-frey.com\/blog\/#website","url":"https:\/\/simon-frey.com\/blog\/","name":"Blog by Simon Frey","description":"","publisher":{"@id":"https:\/\/simon-frey.com\/blog\/#\/schema\/person\/34753982b648415636ee7a079f3e19a3"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/simon-frey.com\/blog\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":["Person","Organization"],"@id":"https:\/\/simon-frey.com\/blog\/#\/schema\/person\/34753982b648415636ee7a079f3e19a3","name":"Simon Frey","logo":{"@id":"https:\/\/simon-frey.com\/blog\/#\/schema\/person\/image\/"},"sameAs":["https:\/\/simon-frey.com","https:\/\/www.linkedin.com\/in\/simonfrey\/","https:\/\/x.com\/eu_frey"]}]}},"_links":{"self":[{"href":"https:\/\/simon-frey.com\/blog\/wp-json\/wp\/v2\/posts\/1312","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/simon-frey.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/simon-frey.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/simon-frey.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/simon-frey.com\/blog\/wp-json\/wp\/v2\/comments?post=1312"}],"version-history":[{"count":6,"href":"https:\/\/simon-frey.com\/blog\/wp-json\/wp\/v2\/posts\/1312\/revisions"}],"predecessor-version":[{"id":1318,"href":"https:\/\/simon-frey.com\/blog\/wp-json\/wp\/v2\/posts\/1312\/revisions\/1318"}],"wp:attachment":[{"href":"https:\/\/simon-frey.com\/blog\/wp-json\/wp\/v2\/media?parent=1312"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/simon-frey.com\/blog\/wp-json\/wp\/v2\/categories?post=1312"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/simon-frey.com\/blog\/wp-json\/wp\/v2\/tags?post=1312"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}