| name | deploying-public-apps |
| description | Deploys public-facing apps that handle their own authentication (like Jellyfin, game servers) without Authelia forward auth |
Deploying Public-Facing Apps
For apps that handle their own auth (Jellyfin, Minecraft, etc.) and don't need Authelia forward auth.
Checklist
When deploying a new public-facing app, complete these steps:
- Create app manifests in
flux/clusters/superbloom/apps/<app>/ - Add app to
flux/clusters/superbloom/apps/kustomization.yaml - Add domain to DDNS in
flux/clusters/superbloom/infra/ddns/release.yaml - Add Caddy route in
flux/clusters/superbloom/infra/caddy/caddyfile.yaml - Commit, push, and reconcile Flux
1. App Manifests
Create flux/clusters/superbloom/apps/<app>/:
kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: <app>
resources:
- ns.yaml
- release.yaml
ns.yaml
apiVersion: v1
kind: Namespace
metadata:
name: <app>
release.yaml
apiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:
name: <app>
namespace: <app>
spec:
interval: 5m
chart:
spec:
chart: app-template
version: "4.1.1"
sourceRef:
kind: HelmRepository
name: bjw-s
namespace: flux-system
values:
controllers:
main:
containers:
main:
image:
repository: <image>
tag: latest
env:
TZ: America/Denver
probes:
liveness:
enabled: true
# ... probe config
service:
main:
controller: main
ports:
http:
port: <port>
persistence:
# ... as needed
2. Add to Apps Kustomization
Edit flux/clusters/superbloom/apps/kustomization.yaml:
resources:
- ...existing...
- <app>/
3. Add Domain to DDNS
Edit flux/clusters/superbloom/infra/ddns/release.yaml:
env:
DOMAINS: saavylab.dev,...existing...,<subdomain>.saavylab.dev
4. Add Caddy Route (NO forward_auth)
Edit flux/clusters/superbloom/infra/caddy/caddyfile.yaml:
data:
Caddyfile: |
# <App> - no forward_auth (handles own auth)
<subdomain>.saavylab.dev {
reverse_proxy <app>.<app>.svc.cluster.local:<port>
}
Key difference from internal services: No forward_auth block. The app handles its own authentication.
5. Deploy
cd ~/dev/sb
git add -A && git commit -m "Add <app> at <subdomain>.saavylab.dev"
git push
# Reconcile
flux reconcile kustomization flux-system --with-source
# Watch deployment
kubectl get pods -n <app> -w
Examples
Jellyfin (Media Server)
- URL:
watch.saavylab.dev - Port: 8096
- Auth: Built-in (easier for TVs, Xbox, etc.)
- Special: GPU passthrough via
/dev/drihostPath
Minecraft (Game Server)
- URL:
mc.saavylab.dev - Port: 25565
- Auth: Mojang/Microsoft accounts
- Special: Uses LoadBalancer service, not Caddy
Common Patterns
GPU Passthrough
persistence:
dri:
enabled: true
type: hostPath
hostPath: /dev/dri
globalMounts:
- path: /dev/dri
Requires securityContext.privileged: true on the container.
Host Storage (ZFS Pool)
persistence:
media:
enabled: true
type: hostPath
hostPath: /tank/media
globalMounts:
- path: /media
readOnly: true # if app only reads
NVMe Storage (Fast Cache)
persistence:
cache:
enabled: true
type: persistentVolumeClaim
accessMode: ReadWriteOnce
size: 50Gi
storageClass: local-path # provisions on NVMe root
globalMounts:
- path: /cache
Post-Deploy: Certificate Provisioning
After adding a new domain, restart Caddy so it provisions the TLS certificate:
# Restart Caddy to trigger cert provisioning
kubectl rollout restart deployment/caddy -n caddy-system
# Watch logs for certificate acquisition
kubectl logs deployment/caddy -n caddy-system -f | grep -E "(obtain|certificate|watch)"
If cert fails with 520 error:
- Check DDNS logs:
kubectl logs deployment/ddns -n ddns - Verify DNS points to correct IP:
dig +short <subdomain>.saavylab.dev - Restart DDNS if needed:
kubectl rollout restart deployment/ddns -n ddns
Debugging
# Check pod status
kubectl get pods -n <app>
# Check logs
kubectl logs -n <app> -l app.kubernetes.io/name=<app>
# Check service endpoints
kubectl get endpoints -n <app>
# Check HelmRelease status
kubectl get helmrelease -n <app>
flux logs --kind=HelmRelease --name=<app> -n <app>
# Test internal connectivity
kubectl run -it --rm debug --image=curlimages/curl -- curl http://<app>.<app>.svc.cluster.local:<port>/health
Why No Authelia?
Some apps need direct access without SSO:
- Smart TVs / Streaming devices - Can't handle browser redirects
- Game consoles - Xbox, PlayStation native apps
- Mobile apps - Native Jellyfin/Plex apps
- Game clients - Minecraft, etc.
These apps have their own auth mechanisms (app logins, Mojang accounts, etc.) that work better for their use case.