[{"content":" Hi, I\u0026rsquo;m Lea # Sr. Software Engineer and Systems Architect with 8+ years across Data Engineering, Data Science, and Software Engineering. Currently at c-sense GmbH — working on two tracks: designing layered software frameworks for SPM instrument control, and leading the company\u0026rsquo;s internal AI integration initiative end-to-end.\nWhat I work on # Now — At c-sense I work across two tracks: hardware/software architecture for scientific instruments, and leading the company\u0026rsquo;s internal AI automation initiative. For the latter, I introduced BDD/SDD as the team\u0026rsquo;s specification workflow, then designed and am implementing an end-to-end pipeline — multi-source data ingestion, LLM-based extraction and classification, a queryable data layer, and a RAG-powered knowledge base for internal Q\u0026amp;A. Outside of work, I run my own Kubernetes infrastructure (lyeh-infra) and explore DevOps/GitOps/MLOps patterns end-to-end.\nOn AI — I build LLM applications where they solve real operational problems. The pattern I keep returning to: treat AI as a processing layer with explicit schemas and confidence signals, so the rest of the system can reason about what the model produced.\nAlways sharpening — I develop lib/api/service projects through real, complex systems to build genuine architecture intuition. Right now I\u0026rsquo;m working through Linux kernel internals, implementing KFS from scratch — the most complex real-world architecture I know, and the most interesting challenge.\nSkills # Current: Systems Architecture · Data Engineering · MLOps \u0026amp; DevOps \u0026amp; GitOps · LLM Application Development · RAG Pipelines · Structured Information Extraction · Python · C/C++ · SQL · Kubernetes · Docker · ArgoCD · Linux Kernel Internals\nExploring: LLM Platform Infrastructure · Agentic Workflows · AI-assisted DevOps\nThis site # Self-hosted on a Hetzner CX23 VM running k3s, deployed via ArgoCD GitOps. Source: lyeh-infra Blog: here\nContact # Email: lea.yeh.ml@gmail.com GitHub: LeaYeh LinkedIn: lea-yeh ","date":"27 April 2026","externalUrl":null,"permalink":"/about/","section":"Lea Yeh","summary":"Hi, I’m Lea # Sr. Software Engineer and Systems Architect with 8+ years across Data Engineering, Data Science, and Software Engineering. Currently at c-sense GmbH — working on two tracks: designing layered software frameworks for SPM instrument control, and leading the company’s internal AI integration initiative end-to-end.\n","title":"About","type":"page"},{"content":"","date":"27 April 2026","externalUrl":null,"permalink":"/tags/argocd/","section":"Tags","summary":"","title":"ArgoCD","type":"tags"},{"content":"","date":"27 April 2026","externalUrl":null,"permalink":"/posts/","section":"Blog","summary":"","title":"Blog","type":"posts"},{"content":" Why not GitHub Pages or Vercel? # I needed three things a static hosting service can\u0026rsquo;t give me:\nA version-controlled resume that I can update with a git push and track over time A place to demo side projects — not just link to code, but actually run them with a real URL An architecture showcase — somewhere I can embed live demos and document design decisions in context GitHub is too scattered for this. WordPress isn\u0026rsquo;t flexible enough. Vercel and Netlify only serve static files or serverless functions — they can\u0026rsquo;t run a real service with its own subdomain.\nThe key insight: if I self-host on Kubernetes, the infrastructure itself becomes part of the portfolio. Any side project gets its own subdomain and a real deployment pipeline. The infra I built to host the portfolio demonstrates the same skills I\u0026rsquo;m trying to show.\nGitHub Pages Vercel / Netlify Self-host k3s Version-controlled resume ✅ ✅ ✅ Run real services ❌ ❌ ✅ Live demo per subdomain ❌ ❌ ✅ Architecture as a portfolio piece ❌ ❌ ✅ Long-term side project hosting ❌ ❌ ✅ Cost Free Free–$20 €6/mo Why k3s on a single VM # Full Kubernetes is designed for multi-node clusters. The control plane alone consumes significant resources — on a single €6/month VM, that overhead doesn\u0026rsquo;t make sense. k3s ships as a single ~70 MB binary with Traefik and a storage provisioner included. On one VM, it\u0026rsquo;s the obvious choice.\nMinikube and kind are local-only tools for development — they can\u0026rsquo;t serve real traffic.\nThe stack # Hetzner CX23 — 3 vCPU, 4 GB RAM, €6/month k3s — lightweight Kubernetes, single binary, ships with Traefik Traefik — ingress controller, routes traffic by hostname to the right service cert-manager — requests and renews Let\u0026rsquo;s Encrypt certificates automatically ArgoCD — watches the Git repo and keeps the cluster in sync Cloudflare — DNS + CDN in front of the VM (orange cloud, Full SSL mode) The GitOps design: ApplicationSet # The core architectural decision was using ArgoCD\u0026rsquo;s ApplicationSet with a Git directory generator instead of managing Applications manually.\nEvery subdirectory under apps/ is automatically discovered and deployed:\napps/ ├── portal/ → auto-deployed as \u0026#34;portal\u0026#34; app to namespace \u0026#34;portal\u0026#34; ├── monitoring/ → auto-deployed as \u0026#34;monitoring\u0026#34; app └── argocd/ → self-managed Adding a new service:\nmkdir apps/my-service # add kustomization.yaml, deployment.yaml, service.yaml, ingress.yaml git push # ArgoCD picks it up within ~3 minutes, namespace created automatically The cluster self-heals: if anything is changed manually, ArgoCD reverts it to match Git. Git is the only source of truth.\nHow traffic flows # Browser → Cloudflare (CDN, Full SSL) → Hetzner VM :443 → Traefik (routes by hostname) → Service (ClusterIP) → Pod Traefik reads Ingress resources to know which hostname maps to which service. cert-manager watches those same resources and requests TLS certificates from Let\u0026rsquo;s Encrypt when it sees the letsencrypt-prod annotation.\nBootstrap: from blank VM to running cluster # The entire setup is scripted and runs in four steps:\n1. Provision VM + open firewall ports (80, 443, 6443) Configure DNS A records: lyeh.dev and *.lyeh.dev → VM IP 2. Install k3s curl -sfL https://get.k3s.io | sh - 3. Install cert-manager + ClusterIssuer 4. Run bootstrap/install-argocd.sh (once) → installs ArgoCD, loads SSH deploy key, applies ApplicationSet → GitOps mode active, cluster is now self-managing After step 4, I never need to run kubectl apply for a new service again.\nThe hardest debugging session # After getting everything running — k3s healthy, ArgoCD synced, nginx responding — the site still showed the old page. I checked the VM, the pods, the ingress. Everything looked correct.\nThe problem was Cloudflare. With orange cloud enabled, Cloudflare caches responses and serves them directly without hitting the origin. The cluster was fine; the traffic never reached it.\nFix: Purge Cache from the Cloudflare dashboard. The correct content appeared immediately.\nLesson: when Cloudflare is in proxy mode, always purge cache before debugging the origin. After this, cache stays on — that\u0026rsquo;s the whole point of running through Cloudflare.\nSource # github.com/LeaYeh/lyeh-infra\n","date":"27 April 2026","externalUrl":null,"permalink":"/posts/building-your-own-infra/","section":"Blog","summary":"Why I built my own Kubernetes infrastructure instead of using GitHub Pages or Vercel — and how the infra itself became part of the portfolio.","title":"Building Your Own Infrastructure for the Portfolio: Self-hosting on k3s with ArgoCD GitOps","type":"posts"},{"content":"","date":"27 April 2026","externalUrl":null,"permalink":"/tags/cert-manager/","section":"Tags","summary":"","title":"Cert-Manager","type":"tags"},{"content":"","date":"27 April 2026","externalUrl":null,"permalink":"/tags/gitops/","section":"Tags","summary":"","title":"GitOps","type":"tags"},{"content":"","date":"27 April 2026","externalUrl":null,"permalink":"/tags/hetzner/","section":"Tags","summary":"","title":"Hetzner","type":"tags"},{"content":"","date":"27 April 2026","externalUrl":null,"permalink":"/tags/k3s/","section":"Tags","summary":"","title":"K3s","type":"tags"},{"content":"","date":"27 April 2026","externalUrl":null,"permalink":"/tags/kubernetes/","section":"Tags","summary":"","title":"Kubernetes","type":"tags"},{"content":"","date":"27 April 2026","externalUrl":null,"permalink":"/","section":"Lea Yeh","summary":"","title":"Lea Yeh","type":"page"},{"content":"","date":"27 April 2026","externalUrl":null,"permalink":"/tags/","section":"Tags","summary":"","title":"Tags","type":"tags"},{"content":"","date":"27 April 2026","externalUrl":null,"permalink":"/tags/traefik/","section":"Tags","summary":"","title":"Traefik","type":"tags"},{"content":"","date":"1 April 2026","externalUrl":null,"permalink":"/tags/grafana/","section":"Tags","summary":"","title":"Grafana","type":"tags"},{"content":" Overview # Personal cloud infrastructure for running self-hosted services. Everything is declared in Git — adding a new service means creating a directory and pushing; ArgoCD handles the rest automatically.\nLive services:\nlyeh.dev — this portfolio site (Hugo + nginx) argocd.lyeh.dev — ArgoCD UI monitoring.lyeh.dev — Grafana dashboards (Prometheus stack) Architecture # Traffic flow # Browser │ HTTPS ▼ Cloudflare (orange cloud — CDN + DDoS protection, Full SSL mode) │ HTTPS → origin ▼ Hetzner CX23 :443 │ Traefik (Ingress Controller — routes by hostname) │ lyeh.dev → portal Service │ argocd.lyeh.dev → argocd Service │ ... ▼ Service (ClusterIP) │ Pod Traefik runs as part of k3s and binds directly to host ports 80/443. It reads Ingress resources to know which hostname maps to which service — no manual routing config needed when adding new apps.\nTLS: cert-manager watches Ingress resources for the letsencrypt-prod annotation, requests certificates from Let\u0026rsquo;s Encrypt automatically, and stores them as Kubernetes Secrets. Traefik reads those Secrets to terminate TLS.\nGitOps with ArgoCD ApplicationSet # The core design is an ArgoCD ApplicationSet using a Git directory generator:\napps/ ├── portal/ ← ArgoCD discovers this → deploys as \u0026#34;portal\u0026#34; app ├── monitoring/ ← ArgoCD discovers this → deploys as \u0026#34;monitoring\u0026#34; app ├── argocd/ ← self-managed └── _template/ ← excluded Every subdirectory under apps/ becomes an ArgoCD Application automatically. The template:\nname: \u0026#39;{{path.basename}}\u0026#39; # app name = directory name path: \u0026#39;{{path}}\u0026#39; # reads kustomization.yaml from that dir namespace: \u0026#39;{{path.basename}}\u0026#39; # deploys into same-named namespace syncPolicy: automated: prune: true # resources deleted from Git get deleted from cluster selfHeal: true # manual cluster changes get reverted to match Git syncOptions: - CreateNamespace=true AppProject defines the trust boundary — ArgoCD only accepts sources from git@github.com:LeaYeh/lyeh-infra.git and only deploys to the local cluster.\nBootstrap sequence # A fresh VM goes from zero to fully running in four scripted steps:\n1. Provision VM ├── Hetzner CX23 (3 vCPU / 4 GB RAM / €6 per month) ├── Firewall: open 80 (HTTP), 443 (HTTPS), 6443 (k8s API) └── DNS: A records for lyeh.dev and *.lyeh.dev → VM IP 2. Install k3s └── curl -sfL https://get.k3s.io | sh - → ships with Traefik + local-path provisioner, ready immediately 3. Install cert-manager └── kubectl apply -f cert-manager.yaml → apply ClusterIssuer pointing to Let\u0026#39;s Encrypt production 4. Bootstrap ArgoCD (run once) ├── Install ArgoCD ├── Load SSH deploy key → ArgoCD can read the private repo ├── Apply AppProject + ApplicationSet └── ArgoCD takes over — GitOps mode active After step 4, the cluster is self-managing. Every subsequent change goes through Git.\nCI/CD: how a content change reaches the site # The portal site (this site) has a fully automated pipeline. Every push to apps/portal/src/ triggers GitHub Actions:\ngit push (content change in apps/portal/src/) │ ▼ GitHub Actions ├── checkout repo (with --recurse-submodules for Blowfish theme) ├── install Hugo 0.160.1 extended ├── hugo --minify → builds static HTML into public/ ├── docker build → nginx:alpine + public/ ├── docker push → ghcr.io/leayeh/portal:sha-{GITHUB_SHA} ├── sed -i → update image tag in apps/portal/deployment.yaml └── git commit + push → \u0026#34;chore(portal): bump image to sha-abc1234\u0026#34; │ ▼ ArgoCD detects deployment.yaml changed │ ▼ k8s rolling update → new pod up, old pod down │ ▼ lyeh.dev serves new content The key step is the sed + git commit — GitHub Actions writes the new image SHA directly back into the repo. ArgoCD then picks it up like any other Git change.\nWhy SHA, not a tag # Docker image tags — including latest and versioned tags like v1.0.0 — are mutable. Any tag can be overwritten by pushing a new image with the same name. There is nothing in the Docker protocol that prevents this.\n# Both of these can silently change over time: ghcr.io/leayeh/portal:latest → image A (Monday) ghcr.io/leayeh/portal:latest → image B (Tuesday, same tag, different content) ghcr.io/leayeh/portal:v1.0.0 → image A (original release) ghcr.io/leayeh/portal:v1.0.0 → image C (re-tagged after a hotfix, same version string) # SHA is the only identifier that cannot be overwritten: ghcr.io/leayeh/portal:sha-abc123 → always image A, forever ghcr.io/leayeh/portal:sha-def456 → always image B, forever A SHA is a cryptographic hash of the image content. It is computed from what\u0026rsquo;s inside the image — two different images will always have different SHAs, and the same image will always produce the same SHA. It cannot be reassigned.\nIn a GitOps setup this matters for two reasons:\n1. Git is the source of truth. The SHA pinned in deployment.yaml tells you exactly what is running — not approximately, not \u0026ldquo;probably v1.0.0\u0026rdquo;, exactly. If you read the file six months from now you can pull that exact image and reproduce what was running.\n2. ArgoCD detects changes by diffing Git vs cluster state. If deployment.yaml says latest and the cluster is already running latest, ArgoCD sees no diff — even if the image content changed. With SHA, every new build produces a new hash, the sed step writes it into deployment.yaml, ArgoCD sees a real diff and deploys.\nThe latest tag is still pushed alongside SHA as a convenience for local debugging — but it is never referenced in deployment.yaml.\nWhy k3s # Single VM, no need for multi-node cluster management. Full Kubernetes control plane alone would consume a significant portion of the available RAM on a CX23. k3s ships as a ~70 MB binary with Traefik and storage provisioner included — everything needed for a single-node setup, nothing that isn\u0026rsquo;t.\nDebugging: the Cloudflare cache trap # The trickiest issue during setup: after everything was working — VM running, ArgoCD synced, nginx responding — the site still showed the old page. The cluster was fine. The problem was Cloudflare had cached the previous response and was serving it directly without reaching the origin.\nFix: Purge Cache from the Cloudflare dashboard. Once the real content loaded correctly, cache was left enabled — that\u0026rsquo;s the whole point of running through Cloudflare.\nLesson: when Cloudflare orange-cloud is active and changes aren\u0026rsquo;t visible, always purge cache before debugging the origin.\nLinks # GitHub ","date":"1 April 2026","externalUrl":null,"permalink":"/projects/lyeh-infra/","section":"Projects","summary":"Personal cloud infrastructure on a single Hetzner VM — k3s, ArgoCD GitOps, Traefik ingress, cert-manager TLS, Prometheus + Grafana monitoring. New services deploy by git push.","title":"lyeh-infra","type":"projects"},{"content":"","date":"1 April 2026","externalUrl":null,"permalink":"/projects/","section":"Projects","summary":"","title":"Projects","type":"projects"},{"content":"","date":"1 April 2026","externalUrl":null,"permalink":"/tags/prometheus/","section":"Tags","summary":"","title":"Prometheus","type":"tags"},{"content":"","date":"1 October 2025","externalUrl":null,"permalink":"/tags/devops/","section":"Tags","summary":"","title":"DevOps","type":"tags"},{"content":"","date":"1 October 2025","externalUrl":null,"permalink":"/tags/docker/","section":"Tags","summary":"","title":"Docker","type":"tags"},{"content":"","date":"1 October 2025","externalUrl":null,"permalink":"/tags/elk/","section":"Tags","summary":"","title":"ELK","type":"tags"},{"content":" Overview # Contributed the DevOps module for a full-stack multiplayer web platform (42 Vienna capstone project). Responsible for designing and deploying the complete observability infrastructure covering metrics, logging, and alerting.\nWhat I built # Deployed Prometheus for metrics collection and configured alerting rules for service health and performance thresholds Built Grafana dashboards for real-time visibility into application and infrastructure metrics across the platform Set up ELK stack (Elasticsearch, Logstash, Kibana) for centralized log aggregation and search across distributed services Containerized the full observability stack with Docker Compose, enabling reproducible deployment and local development parity Design decisions # The key architectural choice was keeping the observability stack decoupled from the application services — each service exposes a /metrics endpoint, and the observability layer scrapes independently. This means the monitoring infrastructure can be updated, replaced, or scaled without touching application code.\nLinks # GitHub ","date":"1 October 2025","externalUrl":null,"permalink":"/projects/ft-transcendence/","section":"Projects","summary":"Designed and deployed a complete observability infrastructure (metrics, logging, alerting) for a full-stack multiplayer web platform — Prometheus, Grafana, ELK stack, containerized with Docker Compose.","title":"ft_transcendence — Observability Stack","type":"projects"},{"content":"","date":"1 October 2025","externalUrl":null,"permalink":"/tags/observability/","section":"Tags","summary":"","title":"Observability","type":"tags"},{"content":"","date":"1 September 2025","externalUrl":null,"permalink":"/tags/c/","section":"Tags","summary":"","title":"C","type":"tags"},{"content":" Overview # Built a fully bootable Linux system from scratch, covering every layer from cross-compilation toolchain to kernel configuration, filesystem hierarchy, init system, and bootloader. Follows LFS/BLFS/ALFS methodology with a custom automation layer.\nWhat I built # Compiled a custom Linux kernel with hand-selected driver and filesystem configuration Built a two-phase cross-compilation toolchain (temporary + final) to produce a host-independent, self-contained Linux system Automated the full build pipeline (14 stages) via an ALFS-style bootstrap script with environment isolation and error recovery Designed partition layout, configured GRUB bootloader, SysV init, and udev for dynamic device management Why it matters # Building Linux from scratch forces you to understand every assumption modern systems make. It\u0026rsquo;s the most direct way to learn what\u0026rsquo;s actually happening beneath the abstractions — and to develop the judgment needed to reason about system-level architecture decisions.\nLinks # GitHub ","date":"1 September 2025","externalUrl":null,"permalink":"/projects/ft-linux/","section":"Projects","summary":"Built a fully bootable Linux system from scratch — cross-compilation toolchain, kernel compilation, filesystem hierarchy, init system, and bootloader. Automated across 14 build stages.","title":"ft_linux — Linux From Scratch","type":"projects"},{"content":"","date":"1 September 2025","externalUrl":null,"permalink":"/tags/kernel/","section":"Tags","summary":"","title":"Kernel","type":"tags"},{"content":"","date":"1 September 2025","externalUrl":null,"permalink":"/tags/linux/","section":"Tags","summary":"","title":"Linux","type":"tags"},{"content":"","date":"1 September 2025","externalUrl":null,"permalink":"/tags/systems-programming/","section":"Tags","summary":"","title":"Systems Programming","type":"tags"},{"content":" Overview # Provisioned and configured a multi-node K3s (lightweight Kubernetes) cluster using Vagrant and VirtualBox, covering cluster networking, ingress, and GitOps-style deployment workflows.\nWhat I built # Deployed a multi-node K3s cluster with Vagrant-provisioned VMs, including nested virtualization setup Configured Kubernetes ingress, service routing, and workload deployment across cluster nodes Applied GitOps principles with ArgoCD for continuous deployment in the bonus track Connection to lyeh-infra # This project was the hands-on foundation for lyeh-infra — the real-world single-node k3s cluster now serving this site. The difference: Inception-of-Things was a controlled learning environment; lyeh-infra is production.\nLinks # GitHub ","date":"1 June 2025","externalUrl":null,"permalink":"/projects/inception-of-things/","section":"Projects","summary":"Provisioned a multi-node K3s cluster using Vagrant and VirtualBox, with ingress routing, GitOps-style deployment via ArgoCD, and full cluster networking.","title":"Inception-of-Things — K3s Kubernetes Cluster","type":"projects"},{"content":"","date":"1 June 2025","externalUrl":null,"permalink":"/tags/vagrant/","section":"Tags","summary":"","title":"Vagrant","type":"tags"},{"content":"","date":"1 March 2025","externalUrl":null,"permalink":"/tags/deep-learning/","section":"Tags","summary":"","title":"Deep Learning","type":"tags"},{"content":" Overview # Three educational ML infrastructure projects, each reimplementing a core part of the modern ML stack from first principles.\nThe three components # litetorch — Neural network framework built from scratch inspired by PyTorch\nImplemented forward/backpropagation, autograd, and layer abstractions from scratch The goal: understand what PyTorch actually does, not just how to call it litetune — Hyperparameter search and experiment tracking, inspired by Ray Tune\nBuilt trial management, search space definition, and result aggregation Mirrors Ray Tune\u0026rsquo;s experiment lifecycle model sklite — ML preprocessing toolkit inspired by scikit-learn\nDesigned preprocessing pipelines and utility functions for educational clarity and extensibility Why build ML frameworks from scratch? # In the AI era, knowing how to call an API is table stakes. Understanding what\u0026rsquo;s happening inside — the gradient flow, the trial scheduling, the data transformation pipeline — is what lets you debug, optimize, and architect ML systems that actually work in production.\nLinks # GitHub ","date":"1 March 2025","externalUrl":null,"permalink":"/projects/litetorch/","section":"Projects","summary":"A trio of educational ML infrastructure projects built from scratch: a neural network framework (inspired by PyTorch), a hyperparameter tuner (inspired by Ray Tune), and an ML preprocessing toolkit (inspired by scikit-learn).","title":"litetorch + litetune + sklite — AI Framework Toolkit","type":"projects"},{"content":"","date":"1 March 2025","externalUrl":null,"permalink":"/tags/machine-learning/","section":"Tags","summary":"","title":"Machine Learning","type":"tags"},{"content":"","date":"1 March 2025","externalUrl":null,"permalink":"/tags/python/","section":"Tags","summary":"","title":"Python","type":"tags"},{"content":"","date":"1 March 2025","externalUrl":null,"permalink":"/tags/pytorch/","section":"Tags","summary":"","title":"PyTorch","type":"tags"},{"content":"","date":"1 March 2025","externalUrl":null,"permalink":"/tags/systems-design/","section":"Tags","summary":"","title":"Systems Design","type":"tags"},{"content":"","date":"1 June 2024","externalUrl":null,"permalink":"/tags/c++/","section":"Tags","summary":"","title":"C++","type":"tags"},{"content":"","date":"1 June 2024","externalUrl":null,"permalink":"/tags/http/","section":"Tags","summary":"","title":"HTTP","type":"tags"},{"content":"","date":"1 June 2024","externalUrl":null,"permalink":"/tags/networking/","section":"Tags","summary":"","title":"Networking","type":"tags"},{"content":" Overview # Implemented a standards-compliant HTTP/1.1 web server in C++ from scratch, handling concurrent connections, request parsing, and static/dynamic content serving.\nWhat I built # Built non-blocking I/O event loop handling concurrent HTTP connections using poll/select Implemented HTTP/1.1 request parsing, routing, and response generation Supported CGI execution, static file serving, and configurable virtual hosts What this taught me # Writing an HTTP server forces you to read RFCs carefully and handle edge cases that higher-level frameworks silently paper over: partial reads, connection timeouts, chunked transfer encoding, CRLF handling. It also makes you think carefully about I/O multiplexing — how to serve hundreds of connections efficiently with a single thread.\nLinks # GitHub ","date":"1 June 2024","externalUrl":null,"permalink":"/projects/webserver/","section":"Projects","summary":"Standards-compliant HTTP/1.1 web server implemented in C++ from scratch — non-blocking I/O event loop, request parsing, CGI execution, and virtual host support.","title":"webserver — HTTP/1.1 Server in C++","type":"projects"},{"content":"","externalUrl":null,"permalink":"/categories/","section":"Categories","summary":"","title":"Categories","type":"categories"}]