<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Peter Lee - Blog</title><link>https://blog.peterlee.app/</link><description>Recent content on Peter Lee - Blog</description><generator>Hugo</generator><language>en</language><managingEditor>peterlee0127@gmail.com (peterlee)</managingEditor><webMaster>peterlee0127@gmail.com (peterlee)</webMaster><lastBuildDate>Sat, 22 Aug 2026 00:00:00 +0800</lastBuildDate><atom:link href="https://blog.peterlee.app/index.xml" rel="self" type="application/rss+xml"/><item><title>Replace Caddy with cloudflared: expose Kubernetes without opening ports 80 and 443</title><link>https://blog.peterlee.app/cloud/cloudflare-tunnel-kubernetes-without-open-ports/</link><pubDate>Sat, 22 Aug 2026 00:00:00 +0800</pubDate><author>peterlee0127@gmail.com (peterlee)</author><guid>https://blog.peterlee.app/cloud/cloudflare-tunnel-kubernetes-without-open-ports/</guid><description>&lt;p>My &lt;code>k8s_infra&lt;/code> setup originally used Caddy as a local web reverse proxy.
External requests reached Cloudflare first, entered my home network through
forwarded HTTP or HTTPS ports, arrived at Caddy, and were finally sent to
Kubernetes.&lt;/p></description></item><item><title>Remove an accidentally committed secret from Git—locally and remotely</title><link>https://blog.peterlee.app/cloud/remove-committed-secret-from-git-history/</link><pubDate>Thu, 30 Jul 2026 00:00:00 +0800</pubDate><author>peterlee0127@gmail.com (peterlee)</author><guid>https://blog.peterlee.app/cloud/remove-committed-secret-from-git-history/</guid><description>&lt;p>This incident started with an ordinary side-project task. The project consumed
several CSV files from external sources, and some of those files contained
confidential data that should never have been versioned. While preparing the
data for the project, I accidentally added one of the sensitive CSV files to
the Git repository without noticing.&lt;/p></description></item><item><title>Building a Native Encrypted DNS App for macOS with AI Coding: DNS Security Pro</title><link>https://blog.peterlee.app/ios/dns-security-pro-native-macos-ai-coding/</link><pubDate>Sat, 18 Jul 2026 00:00:00 +0800</pubDate><author>peterlee0127@gmail.com (peterlee)</author><guid>https://blog.peterlee.app/ios/dns-security-pro-native-macos-ai-coding/</guid><description>&lt;p>I recently used AI-assisted coding to complete a new open-source project:
&lt;a href="https://github.com/peterlee0127/DNS-Security-Pro-macOS">DNS Security Pro for macOS&lt;/a>.&lt;/p>
&lt;p>It is an independent, native macOS app built with SwiftUI, the AppKit lifecycle,
and Apple&amp;rsquo;s NetworkExtension APIs. It lets users switch between DNS over HTTPS
(DoH) and DNS over TLS (DoT) on their Mac. This is neither a direct port of the
iOS interface nor a Mac Catalyst app. It was redesigned around the way people
use macOS.&lt;/p></description></item><item><title>Use Mozilla SOPS with GitOps for encrypted Kubernetes Secrets</title><link>https://blog.peterlee.app/cloud/gitops-sops-encrypted-secrets/</link><pubDate>Tue, 09 Jun 2026 00:00:00 +0800</pubDate><author>peterlee0127@gmail.com (peterlee)</author><guid>https://blog.peterlee.app/cloud/gitops-sops-encrypted-secrets/</guid><description>&lt;p>This post records another way to manage Kubernetes Secrets in a GitOps repo:
encrypt the secret files before committing them.&lt;/p>
&lt;p>In the Vault and External Secrets post, my rule was:&lt;/p></description></item><item><title>Run Airflow on Kubernetes with GitOps-managed values</title><link>https://blog.peterlee.app/cloud/airflow-on-kubernetes-with-gitops-values/</link><pubDate>Wed, 03 Jun 2026 00:00:00 +0800</pubDate><author>peterlee0127@gmail.com (peterlee)</author><guid>https://blog.peterlee.app/cloud/airflow-on-kubernetes-with-gitops-values/</guid><description>&lt;p>This post records how I run &lt;code>Apache Airflow&lt;/code> on Kubernetes with Argo CD.&lt;/p>
&lt;p>Airflow is a good test for a GitOps cluster because it has both normal Helm
values and a lot of sensitive runtime values: fernet key, webserver secret key,
metadata database connection, broker URL, Redis password, the default admin
user, and sometimes a private DAG repository SSH key.&lt;/p></description></item><item><title>Run Istio ambient mode with waypoint proxies</title><link>https://blog.peterlee.app/cloud/istio-ambient-waypoint-proxies/</link><pubDate>Wed, 03 Jun 2026 00:00:00 +0800</pubDate><author>peterlee0127@gmail.com (peterlee)</author><guid>https://blog.peterlee.app/cloud/istio-ambient-waypoint-proxies/</guid><description>&lt;p>This post records how I use &lt;code>Istio ambient mode&lt;/code> in the same GitOps-managed
Kubernetes cluster from the previous posts.&lt;/p>
&lt;p>Sidecars are powerful, but for a home cluster I like ambient mode because the
first step is much smaller: enroll a namespace, let ztunnel handle secure L4
traffic, then add a waypoint only where I actually need L7 behavior.&lt;/p></description></item><item><title>Bootstrap a new RKE cluster for GitOps</title><link>https://blog.peterlee.app/cloud/bootstrap-rke-cluster-for-gitops/</link><pubDate>Tue, 02 Jun 2026 00:00:00 +0800</pubDate><author>peterlee0127@gmail.com (peterlee)</author><guid>https://blog.peterlee.app/cloud/bootstrap-rke-cluster-for-gitops/</guid><description>&lt;p>This post records how I prepare a new &lt;code>RKE&lt;/code> Kubernetes cluster before letting
Argo CD reconcile the applications.&lt;/p>
&lt;p>In my setup, Argo CD can manage most of the cluster after the root Application
is applied. But a new cluster still needs a few manual pieces first. If those
pieces are missing, the first sync looks noisy: CRDs are missing, ExternalSecrets
cannot read Vault, workloads start before their Secrets exist, or PVC users fail
because storage is not ready.&lt;/p></description></item><item><title>Build an OpenTelemetry stack for Kubernetes apps</title><link>https://blog.peterlee.app/cloud/opentelemetry-stack-for-kubernetes-apps/</link><pubDate>Tue, 02 Jun 2026 00:00:00 +0800</pubDate><author>peterlee0127@gmail.com (peterlee)</author><guid>https://blog.peterlee.app/cloud/opentelemetry-stack-for-kubernetes-apps/</guid><description>&lt;p>This post records how I built an &lt;code>OpenTelemetry&lt;/code> stack for Kubernetes apps.&lt;/p>
&lt;p>I started with Docker Compose instead of moving the whole observability backend
into Kubernetes immediately. That gave me a smaller blast radius: application
pods can export OTLP data to one host, while Prometheus, Loki, Tempo, and Grafana
run as a separate backend.&lt;/p></description></item><item><title>Expose Kubernetes services with Istio Gateway API</title><link>https://blog.peterlee.app/cloud/istio-gateway-api-ingress/</link><pubDate>Tue, 02 Jun 2026 00:00:00 +0800</pubDate><author>peterlee0127@gmail.com (peterlee)</author><guid>https://blog.peterlee.app/cloud/istio-gateway-api-ingress/</guid><description>&lt;p>This post records how I expose services from my Kubernetes cluster with &lt;code>Istio&lt;/code>
and &lt;code>Gateway API&lt;/code>.&lt;/p>
&lt;p>The goal is not to make Istio terminate public TLS directly. In my home setup,
the outside edge can still be handled by a reverse proxy. The Kubernetes side
only needs a predictable Gateway entry point and clean routes to Services.&lt;/p></description></item><item><title>Use Argo CD to manage my home Kubernetes cluster</title><link>https://blog.peterlee.app/cloud/argocd-gitops-for-home-kubernetes/</link><pubDate>Tue, 02 Jun 2026 00:00:00 +0800</pubDate><author>peterlee0127@gmail.com (peterlee)</author><guid>https://blog.peterlee.app/cloud/argocd-gitops-for-home-kubernetes/</guid><description>&lt;p>This post records how I use &lt;code>Argo CD&lt;/code> to manage my home Kubernetes cluster.&lt;/p>
&lt;p>Before this setup, most Kubernetes resources were applied manually. That is fine
when the cluster is small, but after adding application workloads, Redis,
Longhorn, Istio, monitoring, and ingress resources, I wanted the cluster to be
rebuilt from Git as much as possible.&lt;/p></description></item><item><title>Use Vault and External Secrets in Kubernetes</title><link>https://blog.peterlee.app/cloud/vault-kubernetes-external-secrets/</link><pubDate>Tue, 02 Jun 2026 00:00:00 +0800</pubDate><author>peterlee0127@gmail.com (peterlee)</author><guid>https://blog.peterlee.app/cloud/vault-kubernetes-external-secrets/</guid><description>&lt;p>This post records how I use &lt;code>Vault&lt;/code> and &lt;code>External Secrets Operator&lt;/code> in my home
Kubernetes cluster.&lt;/p>
&lt;p>The main idea is:&lt;/p>
&lt;p>&lt;code>Vault KV v2 -&amp;gt; ClusterSecretStore -&amp;gt; ExternalSecret -&amp;gt; Kubernetes Secret -&amp;gt; Pod&lt;/code>&lt;/p></description></item><item><title>Deploy Apache Airflow on Kubernetes</title><link>https://blog.peterlee.app/cloud/kubernetes-airflow/</link><pubDate>Mon, 11 Oct 2021 00:00:00 +0800</pubDate><author>peterlee0127@gmail.com (peterlee)</author><guid>https://blog.peterlee.app/cloud/kubernetes-airflow/</guid><description>&lt;p>Apache Airflow is useful when you need to orchestrate scheduled data pipelines, operational tasks, or repeatable workflows. Running it on Kubernetes keeps the scheduler, webserver, workers, and supporting services easier to scale and isolate.&lt;/p></description></item><item><title>Deploy Rancher Kubernetes Engine</title><link>https://blog.peterlee.app/cloud/deploy-rancher-kubernetes/</link><pubDate>Sun, 10 Oct 2021 00:00:00 +0800</pubDate><author>peterlee0127@gmail.com (peterlee)</author><guid>https://blog.peterlee.app/cloud/deploy-rancher-kubernetes/</guid><description>&lt;h3 id="why-use-rancher-to-deploy-a-kubernetes-cluster">Why use Rancher to deploy a Kubernetes cluster?&lt;/h3>
&lt;p>RKE is more configurable than kubeadm for my environment because several components, such as kube-dns, CoreDNS, Flannel, and StorageClass behavior, can be managed from a single cluster configuration. Upgrades can also be challenging in mixed-OS environments like mine, which includes Ubuntu 20.04.3 LTS and CentOS 7 servers. Rancher makes these differences easier to manage because the Kubernetes services run in Docker.&lt;/p></description></item><item><title>DNS Security for iOS/macOS</title><link>https://blog.peterlee.app/ios/dns_security_ios/</link><pubDate>Fri, 20 Nov 2020 00:00:00 +0800</pubDate><author>peterlee0127@gmail.com (peterlee)</author><guid>https://blog.peterlee.app/ios/dns_security_ios/</guid><description>&lt;h1 id="dns-security">DNS Security&lt;/h1>
&lt;div style="text-align:center">&lt;a href='https://apps.apple.com/us/app/id1533938029'>&lt;img src="https://raw.githubusercontent.com/peterlee0127/DNS-Security-iOS/main/mac_logo.png" width='200px' style='alignment:center'>&lt;/a>&lt;/div>
&lt;h4 id="one-of-the-first-apps-to-use-ios-14-encrypted-dns-profiles-without-requiring-a-vpn-configuration">One of the first apps to use iOS 14 encrypted DNS profiles without requiring a VPN configuration.&lt;/h4>
&lt;p>Secure your DNS queries without routing all network traffic through a VPN.&lt;/p></description></item><item><title>Host your website on GitHub Pages</title><link>https://blog.peterlee.app/web/host-your-website-on-github-pages/</link><pubDate>Thu, 24 Oct 2019 00:00:00 +0800</pubDate><author>peterlee0127@gmail.com (peterlee)</author><guid>https://blog.peterlee.app/web/host-your-website-on-github-pages/</guid><description>&lt;p>Host your website on GitHub Pages.&lt;/p>
&lt;h3 id="requirements">Requirements&lt;/h3>
&lt;ol>
&lt;li>A domain name from a registrar such as Namecheap or GoDaddy.&lt;/li>
&lt;li>A GitHub account.&lt;/li>
&lt;li>Cloudflare for DNS management.&lt;/li>
&lt;/ol>
&lt;h2 id="steps">Steps&lt;/h2>
&lt;p>Create a &lt;code>CNAME&lt;/code> file in your Git project:&lt;/p></description></item><item><title>Deploy NFS StorageClass for Kubernetes</title><link>https://blog.peterlee.app/cloud/deploy-storage-class-for-kubernetes/</link><pubDate>Tue, 27 Aug 2019 00:00:00 +0800</pubDate><author>peterlee0127@gmail.com (peterlee)</author><guid>https://blog.peterlee.app/cloud/deploy-storage-class-for-kubernetes/</guid><description>&lt;p>This post shows how to deploy an NFS-backed &lt;code>StorageClass&lt;/code> for Kubernetes.&lt;/p>
&lt;h4 id="related-post-start-a-kubernetes-cluster-with-kubeadm">Related post: &lt;a href="https://blog.peterlee.app/cloud/start_a_kubernetes_with_kubeadm/">&lt;code>Start a Kubernetes cluster with kubeadm&lt;/code>&lt;/a>&lt;/h4>
&lt;p>With the private Kubernetes cluster from the previous post, pods can be recreated on different nodes, so application data needs persistent storage.
I chose NFS as a simple storage option for this lab environment. First, I set up an NFS server.&lt;/p></description></item><item><title>Start a Kubernetes cluster with kubeadm</title><link>https://blog.peterlee.app/cloud/start_a_kubernetes_with_kubeadm/</link><pubDate>Sun, 25 Aug 2019 00:00:00 +0800</pubDate><author>peterlee0127@gmail.com (peterlee)</author><guid>https://blog.peterlee.app/cloud/start_a_kubernetes_with_kubeadm/</guid><description>&lt;p>In this post, I share how I built a private Kubernetes cluster on my own servers.&lt;/p>
&lt;p>&lt;img src="https://blog.peterlee.app/assets/images/posts/2019-08-25-start-a-kubernetes-cluster-with-kubeadm/kube-dashboard.jpg" alt="Kubernetes dashboard">&lt;/p>
&lt;p>For this tutorial, I will create a &lt;code>private Kubernetes cluster&lt;/code>. You can use multiple computers, or a powerful single machine that can run several virtual nodes.&lt;/p></description></item></channel></rss>