sissbruecker

Stars and mentions

Last 30 days, Oct 5 – Nov 3

GitHub stars0
mentions1
01234Oct 5Oct 19Nov 3
UTC days · repositories sissbruecker owns0 repositories

Package downloads

No package download history available yet.

Top repositories

sissbruecker’s repositories ranked by new stars, as on trending.

Loading

Contributes to

Repositories outside sissbruecker’s account where sissbruecker opened or reviewed pull requests in the past 365 days.

Who noticed sissbruecker

Everyone who starred or forked sissbruecker’s repositories since the archive began in 2011.

Notable accounts

Ordered by the stars their own repositories have received.

Biggest fans

Accounts that starred the most of these repositories.

Breakout weeks

Each repository’s best week of new stars, with the release published around it.

Mentions

1post
Nov 40 posts
  1. Building a Declarative Homelab: Deploying Linkding & Mealie to Kubernetes with Flux CD GitOps > A practical walkthrough of deploying self-hosted Linkding and Mealie on local Kubernetes using Flux CD v2, Kustomize overlays, and automated GitOps continuous reconciliation. If you have ever spent a weekend setting up self-hosted services using docker compose or ad-hoc kubectl apply commands, you know the inevitable pain: configuration drift, forgotten secrets, missing volume permissions, and that nagging feeling of "wait, where did I write down that config change?" Recently, I decided to overhaul my homelab workflow and do things properly: 100% declarative GitOps on Kubernetes, powered by Flux CD (v2), Kustomize, and a reproducible development environment via DevPod on Rancher Desktop (k3s). To validate this pipeline end-to-end, I deployed two of my favorite self-hosted services: 1. Linkding: A fast, minimalist bookmark manager with full-text search, tagging, and automated webpage archiving. 2. Mealie: A recipe manager and meal planner that can scrape recipes from virtually any URL. In this post, I will share the architectural setup, manifest breakdown, real-world gotchas (especially with SQLite on Kubernetes!), and how GitOps keeps everything running in perfect sync. --- 🛠 The Homelab Tech Stack Here is what powers the underlying lab: - Local Kubernetes Runtime: Rancher Desktop running k3s, exposing the native Docker socket and utilizing the local-path-provisioner CSI for storage. - GitOps Engine: Flux CD (v2) continuously watching our GitHub repository and reconciling state with zero manual intervention. - Packaging & Templating: Kustomize using a clean Base + Environment Overlay pattern. - Developer Environment: DevPod running inside an isolated devcontainer, keeping the local host clean while managing CLI tools via Mise. --- 🏗 GitOps Architecture: How It Works Instead of manually deploying manifests with kubectl, the Git repository is the single source of truth. GitHub Repository (main branch) │ ├── 1. clusters/staging/flux-system/gotk-sync.yaml │ └── Flux pulls repo every 1m -> Kustomization (flux-system) │ └── 2. clusters/staging/apps.yaml └── Kustomization (apps) -> points to ./apps/staging (prune: true) │ ├── apps/staging/linkding/ ──► apps/base/linkding/ └── apps/staging/mealie/ ──► apps/base/mealie/ The Two-Tier Synchronization Flow Flux uses a two-tier reconciliation model: 1. Root Cluster Sync: Watches clusters/staging/ to keep core cluster controllers and CRDs synchronized. 2. Application Workload Sync: A dedicated Kustomization resource (apps.yaml) watches ./apps/staging. Flux’s kustomize-controller scans the directory tree. Each subfolder containing a kustomization.yaml is built, validated, and applied automatically. If any manifest in Git is removed, prune: true ensures Kubernetes garbage-collects the decommissioned resource. --- 📁 Repository Layout: Base + Overlays To keep things modular and DRY (Don't Repeat Yourself), the repo is organized cleanly: . ├── apps/ │ ├── base/ │ │ ├── linkding/ # Reusable base manifests │ │ │ ├── deployment.yaml │ │ │ ├── service.yaml │ │ │ ├── storage.yaml │ │ │ ├── namespace.yaml │ │ │ └── kustomization.yaml │ │ └── mealie/ # Reusable base manifests │ │ ├── deployment.yaml │ │ ├── service.yaml │ │ ├── storage.yaml │ │ ├── namespace.yaml │ │ └── kustomization.yaml │ └── staging/ # Environment overlays │ ├── linkding/kustomization.yaml # References ../../base/linkding/ │ └── mealie/kustomization.yaml # References ../../base/mealie/ ├── clusters/ │ └── staging/ │ ├── apps.yaml # Workload entrypoint for Flux │ └── flux-system/ # Core Flux Toolkit manifests --- 📦 App 1: Deploying Linkding Linkding is a self-hosted Django application that saves bookmarks and metadata in a database. 1. Storage (apps/base/linkding/storage.yaml) We provision persistent storage using a standard PersistentVolumeClaim: apiVersion: v1 kind: PersistentVolumeClaim metadata: name: linkding-data-pvc namespace: linkding spec: accessModes: - ReadWriteOnce resources: requests: storage: 1Gi 2. Deployment (apps/base/linkding/deployment.yaml) Notice the security parameters: we enforce non-root execution (UID 33 for www-data) and disallow privilege escalation: apiVersion: apps/v1 kind: Deployment metadata: name: linkding namespace: linkding spec: replicas: 1 selector: matchLabels: app: linkding template: metadata: labels: app: linkding spec: securityContext: fsGroup: 33 runAsUser: 33 runAsGroup: 33 containers: - name: linkding image: sissbruecker/linkding:1.31.0 ports: - containerPort: 9090 securityContext: allowPrivilegeEscalation: false volumeMounts: - name: linkding-data mountPath: etc/linkding/data volumes: - name: linkding-data persistentVolumeClaim: claimName: linkding-data-pvc 3. Service (apps/base/linkding/service.yaml) An internal ClusterIP exposes the container port on 9090: apiVersion: v1 kind: Service metadata: name: linkding namespace: linkding spec: type: ClusterIP selector: app: linkding ports: - port: 9090 --- 🍲 App 2: Deploying Mealie Mealie is built with FastAPI and Vue.js. It features automatic recipe parsing using Schema.org metadata. 1. Storage & Namespace (apps/base/mealie/storage.yaml) apiVersion: v1 kind: PersistentVolumeClaim metadata: name: mealie-data-pvc namespace: mealie spec: accessModes: - ReadWriteOnce resources: requests: storage: 500Mi 2. Deployment & Service (apps/base/mealie/deployment.yaml) Similar to Linkding, Mealie runs unprivileged with restricted permissions, listening on port 9000: apiVersion: apps/v1 kind: Deployment metadata: name: mealie namespace: mealie spec: replicas: 1 selector: matchLabels: app: mealie template: metadata: labels: app: mealie spec: securityContext: fsGroup: 33 runAsUser: 33 runAsGroup: 33 containers: - name: mealie image: ghcr.io/mealie-recipes/mealie:v1.3.0 ports: - containerPort: 9000 securityContext: allowPrivilegeEscalation: false volumeMounts: - mountPath: /app/data name: mealie-data volumes: - name: mealie-data persistentVolumeClaim: claimName: mealie-data-pvc --- apiVersion: v1 kind: Service metadata: name: mealie namespace: mealie spec: type: ClusterIP selector: app: mealie ports: - port: 9000 --- 💡 The Real-World Gotcha: The SQLite Replica Trap When first authoring Kubernetes manifests, it is tempting to set replicas: 2 on your deployments out of habit for high availability. However, both Linkding and Mealie run embedded SQLite databases by default: - Linkding writes to /etc/linkding/data/db.sqlite3 - Mealie writes to /app/data/mealie.db Because SQLite uses local filesystem locking and the storage backend is mounted via a ReadWriteOnce (RWO) PVC: 1. Two pods attempting to read/write simultaneously to the same SQLite file will trigger database is locked or synchronization errors. 2. Standard RWO volumes on local storage provisioners cannot be concurrently attached across differing nodes or pod boundaries. The Solution: For homelab use with embedded SQLite databases, set replicas: 1. If you genuinely need multi-replica horizontal scaling, the right design pattern is to plug both applications into an external PostgreSQL cluster via environment variables (e.g. LD_DB_ENGINE: postgres and DB_ENGINE: postgres). Updating this in GitOps was effortless: updating replicas: 1 in deployment.yaml and pushing to Git caused Flux to scale the cluster down cleanly within 60 seconds! --- 🚀 Accessing the Services Since the services are currently exposed as ClusterIP, we can access them locally via kubectl port-forward: 1. Accessing Linkding kubectl port-forward -n linkding svc/linkding 9090:9090 Open http://localhost:9090. Because Linkding does not provide a default password, create your admin user using Django's management command: kubectl exec -it -n linkding deploy/linkding -- python manage.py createsuperuser 2. Accessing Mealie kubectl port-forward -n mealie svc/mealie 9000:9000 Open http://localhost:9000. Mealie initializes with default credentials: - Email: changeme@example.com - Password: MyPassword (Make sure to update these immediately in your profile settings!) --- 🔍 Checking Health with Flux CLI One of the best parts about Flux CD is its diagnostic CLI: # View all synchronized resources across the homelab flux get all -A # Check git pull status flux get sources git # Trigger an immediate forced sync from Git flux reconcile kustomization apps --with-source Output: NAMESPACE NAME REVISION SUSPENDED READY MESSAGE flux-system gitrepository/... main@sha1:c6f48ffc False True stored artifact for revision 'main@sha1:c6f48ffc' flux-system kustomization/apps main@sha1:c6f48ffc False True Applied revision: main@sha1:c6f48ffc --- 🎯 Key Takeaways 1. Zero-Drift Homelab: Any manual edits in Kubernetes get automatically reverted by Flux to match Git. 2. Kustomize Keeps Things Clean: Splitting base and staging ensures we can easily add a production cloud cluster (like AKS) later without rewriting our app definitions. 3. Know Your State: Always align replica counts with your database architecture—embedded SQLite requires single-pod deployments, while distributed databases unlock horizontal scale. What's Next? Next on the roadmap: - Exposing apps via Traefik Ingress with local .homelab DNS. - Automated local SSL certs using cert-manager. - Encrypted Git secrets management with SOPS or Sealed Secrets. --- What self-hosted services are you running on Kubernetes? How do you structure your GitOps repositories? Let me know in the comments below!

    2 reactions · 2 comments

Achievements

Awards earned by the repositories sissbruecker owns. Select an award to embed its badge.

No awards yet

Repositories earn awards by reaching the top three in a completed trending day or week.

A repository earns an award for finishing first, second or third in a completed trending day or week, ranked by new stars, new forks or pull request authors. Awards follow a repository through renames and transfers and count toward its current owner. Weeks run Monday to Sunday, UTC.