Running your own photo gallery might sound like overkill when Google Photos exists, but there are compelling reasons to self-host: full ownership of your data, no subscription fees, no AI training on your family photos, and the satisfaction of building something yourself.
In this post, I’ll walk through the architecture of a self-hosted photo gallery I built on a 3-node Kubernetes cluster, serving over 10,000 photos and videos from a NAS, protected by enterprise-grade authentication, and accessible from anywhere in the world.
The Stack
- Immich – A Google Photos alternative with face detection, smart search, timeline view, and mobile apps
- Authentik – An open-source identity provider handling OAuth2/OIDC, user management, and TOTP MFA
- Cloudflare Tunnel – Zero-trust ingress with no open ports on the home network
- UGreen NAS – Network-attached storage serving photos via NFS
- Kubernetes – Orchestrating everything across 3 mini-PCs
Architecture
The design follows a simple principle: keep all data on the home network, expose nothing directly to the internet, and make authentication mandatory.
Internet → Cloudflare Edge (TLS + CDN) → Cloudflare Tunnel → K8s Cluster → NAS
Users access photos.example.com which hits Cloudflare’s edge network. Cloudflare routes the request through an encrypted tunnel to a cloudflared pod running inside the Kubernetes cluster. The pod forwards traffic to either the Immich server or Authentik, depending on the hostname.
The key insight is that the tunnel is outbound-only. The cloudflared pod initiates a QUIC connection to Cloudflare – no ports are opened on the home router. The NAS sits entirely on the local network, accessible only by Kubernetes pods via NFS.
Why Not Just Use Nginx Ingress?
For a setup with only two services (Immich and Authentik), a full ingress controller is unnecessary overhead. Cloudflare Tunnel’s built-in routing handles hostname-based routing natively. Each service gets a public hostname mapped to an internal Kubernetes service:
photos.example.com→immich-server.photo-gallery.svc:2283auth.example.com→authentik-server.photo-gallery.svc:80
This eliminates an entire component from the stack.
Authentication Flow
Every user must authenticate through Authentik before accessing photos. The login flow:
- User opens the photo gallery URL
- Clicks “Login with Authentik” (the only login option – Immich’s built-in password login is disabled)
- Enters username and password at the Authentik login page
- If first login: forced to change their temporary password
- If TOTP not enrolled: shown a QR code to scan with Google Authenticator
- Enters the 6-digit TOTP code
- Redirected back to Immich with an OIDC token
- Immich auto-creates their account on first login
Only the admin can create user accounts. There is no self-registration. This means the only people who can access the gallery are family members who were explicitly invited.
NFS for Photo Storage
The UGreen NAS exports its photos folder via NFSv3 to the Kubernetes cluster. Immich mounts this as a read-only external library at /mnt/nas/Photos. This means:
- Immich can index and display all photos but cannot modify the originals
- New photos added to the NAS are picked up on the next library scan
- The NAS remains the single source of truth for media files
Immich also has a separate writable volume for its own data – thumbnails, encoded videos, ML model cache, and any photos uploaded directly through the Immich app.
The Cloudflare CDN Bonus
An unexpected benefit of using Cloudflare Tunnel: static assets like photos get cached at Cloudflare’s edge. After the first request, subsequent views of the same photo are served from Cloudflare’s CDN rather than traversing the tunnel to the home server. This makes the gallery surprisingly fast for family members accessing it from different countries.
Resource Usage
The entire stack runs comfortably on three mini-PCs with 16 cores and 28-32GB RAM each. Current utilization is roughly 3% CPU and 3% RAM across the cluster, leaving ample room for additional workloads.
Key Takeaways
- Cloudflare Tunnel eliminates the need for port forwarding – zero attack surface on the home network
- Authentik provides enterprise-grade auth for free – OIDC, MFA, user management with an admin panel
- Immich is a legitimate Google Photos replacement – face detection, search, mobile apps, and external library support
- NFSv3 works reliably for media serving – just make sure to test the NFS version your NAS supports before configuring Kubernetes PVs
- Keep secrets out of git – use Kubernetes Secrets and
.gitignorefrom day one