Homelab infrastructure and applications management using Kubernetes, Proxmox, and various self-hosted services.
This repository contains the infrastructure as code, application deployments, and configuration management for a homelab environment.
- Infrastructure: Proxmox VMs serves as Kubernetes clusters
- Storage: Synology NAS with NFS, Ceph distributed storage
- Networking: pfSense, Pi-hole, Tailscale/WireGuard VPN
- Applications: Plex, Immich, Google Photos Sync, Home Assistant, monitoring stack, etc.
- AI/ML: Inference with GPU support, distributed computing
- Clone the repository
- Review documents at https://mi-homes.org/docs/
- Follow deployment guides for specific components
pve- Proxmox VEpnode- Proxmox nodepvm- Proxmox VMplxc- Proxmox LXC containerkcluster- Kubernetes clusterknode- Kubernetes node
This is based on https://github.com/techno-tim/k3s-ansible
./install_ansible.sh- Follow the guide to deploy the k3s cluster (TODO: write directly here)
curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"
chmod +x kubectl
sudo mv kubectl /usr/local/bin/sudo apt update && sudo apt install -y ansible-core- Make sure you have the SSH key
~/.ssh/pvm-ubuntu-cloudavailable
mkdir -p ~/.kube
scp -i ~/.ssh/pvm-ubuntu-cloud ansibleuser@192.168.68.201:~/.kube/config ~/.kube/configkubectl get pods --all-namespacesecho 'alias k="kubectl"' >> ~/.bashrc
source ~/.bashrcInstall the NFS CSI Driver to enable NFS volume support in Kubernetes:
cd kubernetes/nfs-csi
./install.shConfigure the following values in the deployment files:
SYNOLOGY_NAS_IP: Your Synology NAS IP addressSYNOLOGY_NAS_SHARE: NFS share path on your NASPLEX_CLAIM_TOKEN: Get from https://www.plex.tv/claim/ (valid for 4 minutes)PLEX_LOADBALANCER_IP: Your desired LoadBalancer IPPLEX_SERVER_NAME: Your Plex server name
-
Create Plex namespace:
kubectl create namespace plex
-
Apply Plex PVCs:
kubectl apply -f plex/pvc.yaml
-
Deploy Plex:
kubectl apply -f plex/deployment.yaml
Immich is a self-hosted photo and video backup solution. This directory contains a reusable template-based deployment for Immich that allows easy deployment of multiple instances with different namespaces, domains, and NFS paths.
- Kubernetes cluster with NFS CSI driver support
- Helm 3.x
- NFS server accessible from the cluster
- Domain name for ingress (optional)
Required Variables:
INSTANCE_NAME: Unique identifier for this instanceNAMESPACE: Kubernetes namespaceDOMAIN_NAME: Domain name for ingressNFS_SERVER_IP: IP address of the NFS serverNFS_BASE_PATH: Base path on NFS serverDB_NAME: PostgreSQL database name (typicallyimmich)DB_PASSWORD: PostgreSQL database passwordJWT_SECRET: JWT secret for authentication
-
Create NFS directories on the NAS first:
Before deploying, you must create the required directories on your NFS server. The PVCs will mount these directories, but they must exist beforehand.
On your NAS, create the following directories under
${NFS_BASE_PATH}:library/- User library storageml-cache/- Machine learning model cachephotos/- Photo storage (read-only mount)redis/- Redis data
Note: The
postgres/directory is not needed as PostgreSQL currently uses local-path storage, not NFS. -
Create instance configuration directory:
mkdir -p immich/instances/<instance-name>
-
Create
immich/instances/<instance-name>/instance.envwith the following variables:INSTANCE_NAME=<instance-name> NAMESPACE=immich-<instance-name> DOMAIN_NAME=<your-domain> NFS_SERVER_IP=<nfs-server-ip> NFS_BASE_PATH=/volume2/immich/<nfs-path> DB_NAME=immich DB_PASSWORD=<strong-password-or-empty> JWT_SECRET=<jwt-secret-or-empty>
-
Deploy the instance:
cd immich ./deploy-instance.sh <instance-name>
The script will:
- Load configuration from
instances/<instance-name>/instance.env - Validate required variables
- Generate secrets if not provided
- Generate instance-specific manifests from templates using
envsubst - Create Kubernetes namespace
- Apply secrets
- Apply PVCs
- Deploy PostgreSQL
- Wait for PostgreSQL to be ready
- Deploy Immich via Helm
- Apply volume patch
- Load configuration from
To upgrade an Immich instance:
cd immich
# Regenerate manifests and upgrade
./deploy-instance.sh <instance-name>
### Removal Steps
1. Uninstall Helm release:
```bash
helm -n <namespace> uninstall <instance-name>-
Remove PVCs:
kubectl -n <namespace> delete pvc --all
-
Delete released PVs if needed
-
(Optional) Remove namespace:
kubectl delete namespace <namespace>
This section contains Kubernetes manifests to deploy Cloudflare Tunnel (cloudflared) to expose your Immich application via a custom domain.
- Kubernetes cluster with kubectl access
- Cloudflare account (free tier works)
- Domain
yourdomain.compurchased from Cloudflare - Immich deployed and running in the
immichnamespace
- Go to https://dash.cloudflare.com/sign-up
- Sign up for a free Cloudflare account
- Verify your email address
Since your domain is purchased from Cloudflare, it should already be configured in your Cloudflare account. Verify the setup:
- Log in to Cloudflare Dashboard
- Ensure
yourdomain.comis listed in your domains - Verify the domain is active and using Cloudflare nameservers (this is automatic for Cloudflare-purchased domains)
- The
immich.yourdomain.comsubdomain will be automatically created when you configure the Cloudflare Tunnel in Step 4
- In Cloudflare Dashboard, go to Zero Trust (or visit https://one.dash.cloudflare.com)
- If prompted, select the Free plan for Zero Trust
- Navigate to Networks → Tunnels
- Click Create a tunnel
- Select Cloudflared as the connector type
- Give your tunnel a name (e.g.,
immich-tunnel) - Click Save tunnel
- After creating the tunnel, chose a
TokenforDocker- COPY THIS TOKEN (you'll need it in the next step) - Click Next
- In the
Connectors:- Subdomain:
immich - Domain:
yourdomain.com - Type:
HTTP - URL:
immich-server.immich.svc.cluster.local:2283
- Subdomain:
- Click Complete setup
-
Create the namespace:
kubectl apply -f cloudflare/namespace.yaml
-
Create the secret with your tunnel token:
# Copy the template cp cloudflare/secret.yaml.template cloudflare/secret.yaml # Edit the secret file and replace CHANGE_ME_TUNNEL_TOKEN with the token from Step 4 nano cloudflare/secret.yaml
-
Apply the secret:
kubectl apply -f cloudflare/secret.yaml
-
Deploy the tunnel:
kubectl apply -f cloudflare/deployment.yaml
-
Verify the deployment:
kubectl -n cloudflare-tunnel get pods kubectl -n cloudflare-tunnel logs -f deployment/cloudflared
- Wait a few minutes for DNS propagation (usually instant for Cloudflare domains)
- Check DNS resolution:
dig immich.yourdomain.com nslookup immich.yourdomain.com
- Visit
https://immich.yourdomain.com/in your browser - You should see your Immich login page
These paths are referenced by Argo CD Applications in homelabs-private (private repo). Secret management (Vault, External Secrets Operator) is documented at https://mi-homes.org/docs/; only non-secret wiring lives here.
| Path | Purpose |
|---|---|
apps/pihole/ |
Pi-hole manifests including namespace.yaml (Git-managed Namespace; use with Argo CreateNamespace=true and prune as usual) |
plex/ |
Plex Helm values.yaml; manifests/ for Namespace, config PVC (plex-config-pvc; often local-path to match existing k3s claims), ExternalSecret, and kustomization.yaml |
Per-cluster overrides for Plex live in homelabs-private at clusters/home-prod/overlays/plex/values.yaml. The website Namespace lives in homelabs-private at clusters/home-prod/overlays/website/namespace.yaml alongside the overlay secret.yaml.
Namespaces in Git: Each app keeps a Namespace object in Git so Argo CD prune stays predictable and the namespace is not only created by CreateNamespace=true. Keep CreateNamespace=true on Applications so the namespace is still created if needed during sync.
The home cluster runs k3s v1.30.2+k3s2 (Kubernetes 1.30). Pin and upgrade chart versions in homelabs-private Argo Application manifests; check each chart’s kubeVersion before bumping:
| Chart | Chart version (pinned) | App version | kubeVersion in Chart.yaml |
|---|---|---|---|
plex/plex-media-server |
1.5.0 | 1.43.0 | (not set by upstream) |
Kubernetes 1.30 satisfies the published lower bounds. Vault and External Secrets Operator chart compatibility is covered in the site docs. To re-check after changing versions:
helm show chart plex/plex-media-server --version <chart-version>This is a personal homelab project. For issues and improvements, please create GitHub issues.