Claude Code Plugins

Community-maintained marketplace

Feedback

deploying-public-apps

@saavy1/sb
0
0

Deploys public-facing apps that handle their own authentication (like Jellyfin, game servers) without Authelia forward auth

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

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:

  1. Create app manifests in flux/clusters/superbloom/apps/<app>/
  2. Add app to flux/clusters/superbloom/apps/kustomization.yaml
  3. Add domain to DDNS in flux/clusters/superbloom/infra/ddns/release.yaml
  4. Add Caddy route in flux/clusters/superbloom/infra/caddy/caddyfile.yaml
  5. 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/dri hostPath

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:

  1. Check DDNS logs: kubectl logs deployment/ddns -n ddns
  2. Verify DNS points to correct IP: dig +short <subdomain>.saavylab.dev
  3. 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.