This document outlines the steps to deploy Audiobookshelf using a GitOps workflow with FluxCD, including persistence and public access via Cloudflare Tunnel. The directory structure follows the established pattern for this cluster.
- A Kubernetes cluster managed by FluxCD.
- A configured Git repository connected to Flux.
- A persistent storage solution (e.g., Longhorn, Ceph) with a default or named StorageClass.
- A Cloudflare account and the
cloudflaredCLI tool. - SOPS configured with an AGE key for secret encryption within Flux.
- The directory structure matching the project's layout (e.g.,
clusters/pi-cluster/apps/staging/,clusters/pi-cluster/apps/base/,clusters/pi-cluster/infrastructure/base/).
This stage sets up the core application components without persistence.
- Manifests Location:
clusters/pi-cluster/apps/staging/audiobookshelf/base/ - Create Files:
namespace.yaml: Defines theaudiobookshelfnamespace.configmap.yaml: Createsaudiobookshelf-configwithABS_PORT="3005"andTZ.deployment.yaml: Defines the Audiobookshelf deployment, referencing the ConfigMap and setting the container port to3005. Initially has emptyvolumeMountsandvolumes.service.yaml: Creates aClusterIPservice namedaudiobookshelfexposing port3005.kustomization.yaml: Lists all the above resources.
- Flux Kustomization Definition:
- Create
clusters/pi-cluster/apps/base/audiobookshelf/kustomization.yaml. - This file defines the
Kustomizationresource for Flux (metadata.name: audiobookshelf-app). - Sets
spec.pathto./clusters/pi-cluster/apps/staging/audiobookshelf/base. - Sets
spec.targetNamespacetoaudiobookshelf. - References your GitRepository source (
spec.sourceRef).
- Create
- Update Cluster App Aggregator:
- Edit
clusters/pi-cluster/apps/base/kustomization.yaml. - Add
./audiobookshelfto theresourceslist. This tells the main app Kustomization (e.g.,cluster-apps) to apply the Flux Kustomization defined in the previous step.
- Edit
- Commit & Push: Commit all new/modified files and push to your Git repository.
- Verify:
flux get kustomizations audiobookshelf-app -n flux-systemkubectl get pods -n audiobookshelfkubectl port-forward svc/audiobookshelf -n audiobookshelf 3005:3005- Access
http://localhost:3005, complete initial setup.
This stage configures persistent storage for configuration and media.
- Manifests Location:
clusters/pi-cluster/apps/staging/audiobookshelf/base/ - Create
pvc.yaml:- Define
PersistentVolumeClaimresources for:audiobookshelf-config-pvc(e.g., 1Gi)audiobookshelf-metadata-pvc(e.g., 2Gi)audiobookshelf-audiobooks-pvc(e.g., 50Gi, adjust size)audiobookshelf-podcasts-pvc(e.g., 20Gi, adjust size)
- Important: Ensure
spec.storageClassNamematches an available StorageClass in your cluster or is omitted to use the default.
- Define
- Update
deployment.yaml:- Add
volumeMountsto the container for/config,/metadata,/audiobooks, and/podcasts. - Add
volumesto the pod spec, linking each name to the correspondingpersistentVolumeClaim.claimName.
- Add
- Update
kustomization.yaml(inbase):- Add
pvc.yamlto theresourceslist.
- Add
- Commit & Push: Commit changes and push.
- Verify:
- Wait for Flux reconciliation (deployment will restart).
kubectl get pvc -n audiobookshelf(Check status isBound).- Port-forward, upload/configure data, delete the pod (
kubectl delete pod -l app.kubernetes.io/name=audiobookshelf -n audiobookshelf), wait for the new pod, port-forward again, and verify data persists.
This stage enhances security by running as a non-root user and exposes the application publicly via Cloudflare Tunnel.
- Create Tunnel:
cloudflared tunnel create audiobookshelf
- Note the Tunnel ID and locate the generated
TUNNEL-ID.jsoncredentials file (usually in~/.cloudflared/).
- Note the Tunnel ID and locate the generated
- Create Secret Manifest:
- Generate a base64-encoded secret manifest using
kubectl create secret generic --dry-run:kubectl create secret generic tunnel-credentials \ --from-file=credentials.json=/path/to/your/TUNNEL-ID.json \ --namespace=cloudflare # Or the namespace cloudflared will run in --dry-run=client -o yaml > clusters/pi-cluster/infrastructure/base/cloudflare-secret.yaml
- Ensure the secret
metadata.name(tunnel-credentials) matches thevolumes.secret.secretNamein yourcloudflaredDeployment. - Ensure the key inside
dataiscredentials.json(due to--from-file=credentials.json=...).
- Generate a base64-encoded secret manifest using
- Encrypt Secret:
- Encrypt
cloudflare-secret.yamlusing SOPS and your AGE key:# Ensure AGE_PUBLIC_KEY env var is set or use --age flag directly sops --encrypt --age $(cat clusters/pi-cluster/.agekey | grep -o 'age1[a-z0-9]*') \ --encrypted-regex '^(data|stringData)$' \ --in-place clusters/pi-cluster/infrastructure/base/cloudflare-secret.yaml
- Rename to
cloudflare-secret.sops.yaml.
- Encrypt
- Add Secret to Infrastructure:
- Add
cloudflare-secret.sops.yamlto theresourcesinclusters/pi-cluster/infrastructure/base/kustomization.yaml. - Verify the main infrastructure Flux Kustomization (e.g.,
infrastructure.yaml) enables SOPSdecryptionusing yoursops-agesecret.
- Add
- Configure DNS: Point your desired public hostname (e.g.,
audiobookshelf.your-domain.com) to your tunnel using a CNAME record in Cloudflare DNS or thecloudflaredcommand:cloudflared tunnel route dns audiobookshelf audiobookshelf.your-domain.com # Use your actual tunnel name/ID and desired hostname
- Ensure your
cloudflaredDeployment manifest (e.g.,clusters/pi-cluster/infrastructure/base/cloudflared.yaml) exists and is included in the infrastructure Kustomization. - The Deployment should mount the
tunnel-credentialssecret into/etc/cloudflared/creds. - The associated
cloudflaredConfigMap (config.yamldata) should referencecredentials-file: /etc/cloudflared/creds/credentials.json. - Crucially, the
ingressrules in theconfig.yamldata must route traffic correctly:ingress: - hostname: audiobookshelf.your-domain.com # Your public hostname service: [http://audiobookshelf.audiobookshelf:3005](http://audiobookshelf.audiobookshelf:3005) # K8s Service FQDN (service.namespace:port) # Optional catch-all rule - service: http_status:404
- Run As Non-Root:
- Modify
clusters/pi-cluster/apps/staging/audiobookshelf/base/deployment.yaml. - Add a
securityContextblock at thespec.template.speclevel (runAsUser: 1000,runAsGroup: 1000,fsGroup: 1000,runAsNonRoot: true). Adjust UID/GID if needed. - Add a
securityContextblock inside the container spec (allowPrivilegeEscalation: false,capabilities: { drop: ["ALL"] }).
- Modify
- Add Ingress (Optional but Recommended):
- Create
clusters/pi-cluster/apps/staging/audiobookshelf/base/ingress.yaml. - Set the
hostto your public domain (e.g.,audiobookshelf.your-domain.com). - Set the
backend.service.nametoaudiobookshelfandport.numberto3005. - Crucially, set the appropriate
annotations(likekubernetes.io/ingress.class: "none"or specific Cloudflare annotations) based exactly on how yourcloudflareddeployment discovers services. If yourcloudflareddoesn't use Ingress objects, you might skip this file. - Add
ingress.yamlto theresourcesinclusters/pi-cluster/apps/staging/audiobookshelf/base/kustomization.yaml.
- Create
- Add Dependency:
- Edit the Flux Kustomization definition file
clusters/pi-cluster/apps/base/audiobookshelf/kustomization.yaml. - Add a
dependsOnsection to ensure infrastructure (including the secret) is applied first:spec: # ... other spec fields ... dependsOn: - name: cluster-infrastructure # Use the name of your infrastructure Flux Kustomization
- Edit the Flux Kustomization definition file
- Add all modified/new files (
deployment.yaml, secret, ingress, kustomizations). - Commit and push.
- Monitor Flux:
flux get kustomizations --all-namespaces. Checkcluster-infrastructureandaudiobookshelf-app. - Check Pods:
kubectl get pods -n audiobookshelfandkubectl get pods -n cloudflare(or wherever cloudflared runs). - Check Secret:
kubectl get secret tunnel-credentials -n cloudflare -o yaml(verify it exists, data is encrypted). - Check Logs:
kubectl logs -l app=cloudflared -n cloudflare(look for tunnel connection messages). - Test DNS:
nslookup audiobookshelf.your-domain.com. - Access: Browse to
https://audiobookshelf.your-domain.com.