Skip to main content

Navidrome on GKE Autopilot

Navidrome on GKE Autopilot

Navidrome is a free, open-source, self-hosted music streaming server written in Go. It exposes a Subsonic/OpenSubsonic-compatible API (so any Subsonic client — DSub, Symfonium, Sublime Music, play:Sub, etc. — can browse and stream your library) plus its own web UI, and stores its state in an embedded SQLite database rather than a managed SQL backend. This module deploys Navidrome on GKE Autopilot on top of the App_GKE foundation, which provisions and manages the shared Google Cloud and Kubernetes infrastructure.

This guide focuses on the cloud services Navidrome uses and how to explore and operate them from the Google Cloud Console and the command line. For the mechanics that are common to every GKE application — Workload Identity, ingress, autoscaling, CI/CD, Cloud Armor, IAP, Binary Authorization, VPC Service Controls, backups, and the deployment lifecycle — refer to the App_GKE foundation guide rather than repeating them here.


What Navidrome costs on RAD, and how that compares​

Navidrome on RAD's GKE Autopilot module costs about US$42–115 a month, with the module fee charged once in RAD credits. GKE Autopilot suits Navidrome that must stay up, scale across pods or run beside other Kubernetes workloads; for the lowest cost, the Cloud Run module runs the same Navidrome for about US$33 a month. If you only need it occasionally — a demo, a seasonal project, something you're evaluating — a RAD-managed project can be deleted and restored within 30 days for a handful of credits (under US$1) instead of paying for it to sit idle. See Pause it for free, below. Figures are as at 8 October 2026; sources are listed at the end of this section.

What you pay on RAD​

RAD deploys Navidrome, which is open source, so there is no licence fee per user. You pay for three things:

In a Google Cloud project you ownIn a project RAD manages for you
Module fee, once per deployment75 credits (US$8 at the top-up price)67.5 credits (10% lower)
Build timeAbout 3-6 credits per buildThe same
Google Cloud running costBilled by Google to your own billing account, about US$42 a month for the default configuration (table below) — more if this isn't your only Autopilot clusterMetered hourly in credits; RAD publishes 67 credits a day, about 2,010 a month (about US$201 at the top-up price, US$161 at the Scale plan's rate)
Who owns whatYou own the project and its billing; RAD deploys and updates itRAD owns the project, with guardrails, quotas and budget alerts; you get console access, and a minimum purchased balance is held in reserve

10 credits cost US$1 on a one-off top-up, and less on a monthly plan. In a project RAD manages, the database, file server and network are shared by every application in the project, so a second application does not add a second set of them.

Default running cost in your own project (us-central1, Google list prices):

Resource (module default)Per month
GKE Autopilot pod, 1x vCPU / 1 GiBUS$36.08
Cloud NAT and networkingUS$5
Cloud Storage (add-ons, backups)US$1
GKE cluster management feeshared across every GKE app in the project; $0 if this is your only cluster, else ~73
Totalabout US$42 (about US$115 if this isn't your only Autopilot cluster)

How it compares​

  • No turnkey managed SaaS was verified for Navidrome itself, so the honest comparison is a self-managed server: a Hetzner CPX22 (2 vCPU/4 GB) costs about $24/month, a DigitalOcean 2 vCPU/4 GB Droplet about $24/month, and a GCP Compute Engine e2-standard-2 (2 vCPU/8 GB) about $49/month.
  • This is a light workload — a cheaper, smaller VPS class than the reference boxes below would also do the job; the figures are shown for a like-for-like comparison.
  • This is a personal music server, not a team product with a licensed SaaS competitor — the comparison that matters is self-hosting it yourself versus letting RAD manage the project, not RAD versus a streaming subscription (a streaming service doesn't host your own library).
  • The VPS price is not the whole cost. On a bare VPS you are also the one patching the OS, rotating backups and keeping the database (if any) healthy. RAD's managed Cloud SQL, Secret Manager and monitoring cover that for you, which is the real argument for RAD over a cheaper box — not the sticker price.

Pause it for free: delete a RAD-managed project, restore it when you need it​

If Navidrome runs in a project RAD manages for you, you have a second option that goes well beyond scaling to zero: delete the whole project, and restore it within 30 days for close to nothing. This suits Navidrome you only need occasionally — a demo, an evaluation, a seasonal project — far better than running it continuously.

  • How it works. Deleting a RAD-managed project unlinks its billing first, then asks Google to delete the project. Google does not remove the project immediately: it keeps it, recoverable, for 30 days. Because billing is already unlinked, nothing is charged while it waits. Unlike deleting one module, this does not tear down the cluster or the compute resource one by one — the whole project, and everything in it, simply stops.
  • Restoring costs a few credits, not a rebuild. Within 30 days, the project's owner can restore it. RAD asks Google to undelete the project and reattaches its billing account, then asks you to run Update on each deployment to confirm everything came back. Because nothing was individually destroyed, that Update finds the same resources already there — it is a check, not a rebuild, and an Update never charges the module fee again. That costs roughly a handful of credits (under US$1) in total for a typical 2-3-deployment chain.
  • So a month of occasional use can cost a few dollars, not US$42. Deploy Navidrome, use it for a while, delete the project. Restore it next time you want it, confirm with Update, and delete it again when you're done. You pay only for the module fee once, the builds, and whatever time Navidrome was actually live.
  • What this needs. You must own the project (not a bring-your-own one RAD only manages billing for), and you restore it yourself within the 30 days — after that, Google deletes it for good. Restoring is admitted like creating a new project: your purchased credit balance must still clear the tier's floor (100 credits for the sandbox tier most study and demo use fits). Google says most services are fully working again within 36 hours of a restore.
  • One real gap: nightly backups don't survive. Backups are written to a bucket inside the project, and that bucket is not protected by Cloud Storage's soft-delete, so it is very likely gone as soon as you delete the project — even though the project itself is recoverable for 30 days. If you've customised Navidrome and want to keep that work, copy a backup out (to Google Drive, or a bucket outside the project) before you delete. For a default installation with nothing irreplaceable in it, this does not matter.

Pay only while you use it, the other way: delete and redeploy​

The option above only applies to a RAD-managed project; in your own project, or once the 30-day window has passed, the way to stop paying is to delete the deployment and deploy it again when you need it.

  • What a redeploy costs. The module fee again plus the builds: about 80 credits (US$8) in your own project, or 72 credits in a RAD-managed one. Deleting saves money once Navidrome would otherwise sit unused for about 6 days or more in your own project (about US$1.40 a day), or about 1 days or more in a RAD-managed one (67 credits a day).
  • Most of the running cost is usually the database and any shared file/cache VM. They stop only when nothing else in the project uses them, so deleting Navidrome while something else shares the project saves only this app's own compute part.
  • Keep your data first. Nightly backups go to a bucket inside the deployment and are deleted with it, so copy the latest backup out before deleting if you want to keep it.

Lab sessions and Managed Environments​

  • Lab sessions, for training. A trainer runs a session for a class. Each participant gets Navidrome in their own Google Cloud project for 15 minutes to 24 hours, within an allowance the trainer sets. Either the trainer funds every place, or each participant pays for their own. Everything is deleted when the session ends and unused credits go back to the trainer.
  • Managed Environments, for consultancies. A partner runs Navidrome for a client from a ring-fenced wallet it funds, and settles with the client directly. If the wallet runs low, billing pauses and the data is kept, so nobody receives an unexpected charge. At the end the partner hands the project over and the deployments become the client's own.
  • Credits can be bought in more than 20 currencies, including XAF, XOF, NGN, GHS, KES and ZAR, by card, bank transfer or mobile money.

Sources (8 October 2026): Google Cloud list prices from the Cloud Billing Catalog API; Delete and restore projects for the 30-day recovery window and what it says about Cloud Storage objects without soft delete; RAD fees and the daily-credit estimates from radmodules.dev/pricing. Prices change; check each source before relying on a figure.

1. Overview​

Navidrome runs as a single Go binary serving HTTP on one port. Because it has no external database, the deployment wires together a narrower set of Google Cloud services than a typical database-backed app:

CapabilityGoogle Cloud serviceNotes
ComputeGKE AutopilotA StatefulSet pod on port 4533, 1 vCPU / 1 GiB by default
Application dataBlock-storage PVC (Persistent Disk, via stateful_pvc_enabled)Backs the embedded SQLite DB, artwork cache, and search index at /data — not Cloud SQL
Music libraryOperator-supplied GCS FUSE volume or Cloud Filestore (NFS) at /musicRead-only source audio files; not provisioned automatically
Object storageCloud StorageA storage bucket is always created but sits unmounted while the default block PVC handles /data
SecretsSecret ManagerAn auto-generated admin password (ND_DEVAUTOCREATEADMINPASSWORD), injected as a native Kubernetes Secret
IngressKubernetes Gateway API / ClusterIPNo LoadBalancer by default — internal-only until a custom domain or service_type change is configured

Sensible defaults worth knowing up front:

  • No SQL database. database_type is fixed to NONE by Navidrome_Common; every db_*/database_*/sql_instance_* variable in this module is forwarded to the foundation only for structural compatibility and has no effect.
  • SQLite needs a real block PVC, not gcsfuse. Per this repository's storage convention, gcsfuse cannot safely back SQLite's write-locking model. GKE gives Navidrome a genuine advantage over the Cloud Run variant here: stateful_pvc_enabled defaults to true, provisioning a per-pod Persistent Disk mounted at /data (ND_DATAFOLDER) that holds the SQLite database, artwork cache, and search index. This auto-resolves the workload to a StatefulSet.
  • The PVC storage class defaults to standard-rwo (SSD Balanced PD). This draws the tight SSD_TOTAL_GB regional quota documented in this repo's storage conventions. Navidrome's data directory is metadata/index-sized, not bulk-media-sized, so SSD is reasonable here — but on a quota-constrained project consider -var stateful_pvc_storage_class=standard (HDD pd-standard) if you hit Quota 'SSD_TOTAL_GB' exceeded across a wider campaign of stateful apps.
  • The music library is not mounted automatically. ND_MUSICFOLDER=/music is set as an env var, but no volume is attached to /music out of the box — you must add a read-only GCS FUSE volume (gcs_volumes) or an NFS mount (enable_nfs = true, nfs_mount_path = "/music") pointing at your source audio files. Unlike /data, /music is read-only and holds no SQLite state, so gcsfuse is fine for it.
  • Single replica by default. min_instance_count = 1, max_instance_count = 1. Navidrome serves one shared SQLite library from one PVC; there is no multi-writer/clustering mode, so do not scale beyond 1.
  • No external ingress by default. service_type = "ClusterIP" and enable_custom_domain = true but with an empty application_domains list — so out of the box Navidrome is reachable only inside the cluster/VPC. Set application_domains (Gateway + managed cert) or switch service_type to LoadBalancer to expose it externally.
  • Admin account is auto-created. enable_admin_password = true generates a random password, stores it in Secret Manager, and injects it as ND_DEVAUTOCREATEADMINPASSWORD so Navidrome creates the admin user on first boot. Set it to false to use the first-run web setup wizard instead.
  • Health probes hit the public GET /ping endpoint (returns {"status":"ok"}, no auth required).

2. Google Cloud Services & How to Explore Them​

All commands assume you have run gcloud container clusters get-credentials <cluster> --region "$REGION" --project "$PROJECT" and that PROJECT, REGION, and NAMESPACE are set. The namespace and other identifiers are reported in the deployment Outputs.

A. GKE Autopilot — the Navidrome StatefulSet​

With the default stateful_pvc_enabled = true, Navidrome deploys as a StatefulSet (not a Deployment) so its single pod keeps a stable identity and a PVC that survives reschedules. Autopilot bills for the CPU/memory the pod actually requests.

  • Console: Kubernetes Engine → Workloads → select the Navidrome workload for pods, revisions, and events. Confirm the workload type reads StatefulSet.
  • CLI:
    kubectl get statefulset,pods,svc -n "$NAMESPACE"
    kubectl logs -n "$NAMESPACE" statefulset/<service-name> --tail=100
    kubectl describe pod -n "$NAMESPACE" -l app=<service-name>

See App_GKE for how Autopilot scheduling and the StatefulSet-vs-Deployment auto-resolution work in general.

B. Block-storage PVC — the SQLite data volume​

The per-pod PersistentVolumeClaim at /data is the single source of truth for Navidrome: the SQLite database, the metadata/artwork cache, transcode temp files (if enabled), and the search index all live here. Losing this PVC loses your library metadata, playlists, ratings, and users (the source audio itself is unaffected, since /music is a separate, operator-supplied mount).

  • Console: Kubernetes Engine → Storage → PersistentVolumeClaims and PersistentVolumes; Compute Engine → Disks to see the underlying Persistent Disk.
  • CLI:
    kubectl get pvc -n "$NAMESPACE"
    kubectl describe pvc -n "$NAMESPACE" -l app=<service-name>
    gcloud compute disks list --project "$PROJECT" --filter="name~<service-name>"

See App_GKE Group 7 for the full stateful_pvc_* mechanics (storage class, pod management policy, update strategy).

C. Music library storage (GCS FUSE or NFS)​

/music is where Navidrome scans for audio files. Nothing is mounted there by default — wire up either a read-only GCS FUSE volume (gcs_volumes, CSI driver-backed, fine here since /music holds no SQLite state) pointing at a bucket of uploaded audio, or a Cloud Filestore (NFS) share (enable_nfs = true, nfs_mount_path = "/music") if you need write access from another host to add files. A separate, always-created storage GCS bucket exists for compatibility with the Cloud Run variant but sits unmounted while the block PVC serves /data.

  • Console: Cloud Storage → Buckets; Filestore → Instances.
  • CLI:
    gcloud storage buckets list --project "$PROJECT" --filter="name~<service-name>"
    gcloud filestore instances list --project "$PROJECT"
    kubectl exec -n "$NAMESPACE" statefulset/<service-name> -- ls /music

See App_GKE Groups 13–14 for NFS and GCS Fuse volume mechanics.

D. Secret Manager​

One secret is generated automatically: the admin password, stored under secret-<prefix>-navidrome-admin-password and injected into the pod as a native Kubernetes Secret (not via the Secret Store CSI driver) for the env var ND_DEVAUTOCREATEADMINPASSWORD, which Navidrome reads on first boot to create the admin account.

  • Console: Security → Secret Manager.
  • CLI:
    gcloud secrets list --project "$PROJECT" --filter="name~navidrome-admin-password"
    gcloud secrets versions access latest --secret=<admin-password-secret-name> --project "$PROJECT"

See App_GKE for the general Secret Manager / Secret Store CSI integration used by other apps' secrets.

E. Networking & ingress​

By default service_type = "ClusterIP" and application_domains is empty, so Navidrome is reachable only from inside the VPC/cluster (http://<service>.<namespace>.svc.cluster.local). Set application_domains (with enable_custom_domain = true, already the module default) to provision a Kubernetes Gateway with a managed TLS certificate, or switch service_type to LoadBalancer for a plain external IP.

  • Console: Kubernetes Engine → Services & Ingress; Network services → Load balancing; VPC network → IP addresses.
  • CLI:
    kubectl get svc,gateway -n "$NAMESPACE"
    gcloud compute addresses list --project "$PROJECT"

See App_GKE Group 19 for the Gateway API, static IP, and default nip.io hostname mechanics.

F. Cloud Logging & Monitoring​

Pod stdout/stderr flow to Cloud Logging; GKE metrics flow to Cloud Monitoring. Optional uptime checks and alert policies are available (uptime_check_config defaults to false).

  • Console: Logging → Logs Explorer; Monitoring → Dashboards / Alerting.
  • CLI:
    gcloud logging read 'resource.type="k8s_container" AND resource.labels.namespace_name="'"$NAMESPACE"'"' \
    --project "$PROJECT" --limit 50

3. Navidrome Application Behaviour​

  • No init/migration job. Navidrome has no external database, so there is no db-init or migration Job — the SQLite schema is created by the binary itself on first start against the empty /data PVC. initialization_jobs defaults to [] and is only for custom, operator-supplied tasks.
  • Admin bootstrap. With enable_admin_password = true (default), the container starts with ND_DEVAUTOCREATEADMINPASSWORD set from the generated secret, and Navidrome auto-creates the admin user with that password on first boot. With enable_admin_password = false, the first person to open the web UI completes the setup wizard and chooses their own admin credentials — recommended only for cluster-internal, trusted-access deployments.
  • Immutable-ish PVC identity. Because the pod is a StatefulSet, the PVC is bound to the pod's stable identity (<service-name>-0) and survives pod reschedules/restarts. Scaling max_instance_count beyond 1 is not supported — Navidrome has no multi-writer SQLite mode.
  • Health path. Both the startup probe and the liveness probe are HTTP GET /ping, an unauthenticated endpoint returning {"status":"ok"}. Allow a few minutes on first boot while the library scan runs (large libraries take longer).
  • Library scan. Navidrome scans /music on startup and on a periodic schedule thereafter; scan progress and results are visible in pod logs and the web UI.
  • Verify the running config and library mount:
    kubectl exec -n "$NAMESPACE" statefulset/<service-name> -- env | grep ^ND_
    kubectl exec -n "$NAMESPACE" statefulset/<service-name> -- ls /music
    kubectl logs -n "$NAMESPACE" statefulset/<service-name> --tail=50 | grep -i scan

4. Configuration Variables​

Variables are grouped exactly as they appear on the deployment platform. Only settings specific to or notable for Navidrome are listed; every other input is inherited from App_GKE with its standard behaviour and defaults.

Group 3 — Application Identity​

VariableDefaultDescription
application_namenavidromeBase name for resources. Do not change after first deploy.
application_versionlatestdeluan/navidrome image tag used as the custom-build base; latest is pinned to a known-good tag (0.54.3) at build time.
application_display_nameNavidrome Music ServerHuman-readable name shown in the platform UI.
enable_admin_passwordtrueAuto-generates the admin password and creates the admin user on first boot via ND_DEVAUTOCREATEADMINPASSWORD. Set false to use the first-run web wizard instead.

Group 4 — Runtime & Scaling​

VariableDefaultDescription
cpu_limit1000m1 vCPU; raise for on-the-fly transcoding, which is CPU-bound.
memory_limit1GiNavidrome holds its search index in memory; size to library scale.
min_instance_count1Keep at 1 — avoids cold starts during index/library loading.
max_instance_count1Keep at 1. No multi-writer SQLite support.
enable_cloudsql_volumefalseNavidrome has no Cloud SQL database — keep false.

Group 6 — GKE Backend & Cluster​

VariableDefaultDescription
workload_typenull → StatefulSetAuto-resolves to StatefulSet because stateful_pvc_enabled = true.
service_typeLoadBalancerExternally reachable by default — an external IP is provisioned. Set ClusterIP for internal-only access, in which case reach it via a custom domain or kubectl port-forward.
session_affinityNoneNo sticky routing needed with a single replica.

Group 7 — StatefulSet / PVC​

VariableDefaultDescription
stateful_pvc_enabledtrueProvisions a real block PVC at /data — required so gcsfuse never backs the embedded SQLite DB.
stateful_pvc_size20GiSized for the SQLite DB, metadata cache, and search index (not the music library, which mounts separately).
stateful_pvc_mount_path/dataMust match Navidrome's ND_DATAFOLDER.
stateful_pvc_storage_classstandard-rwoSSD Balanced PD by default; switch to standard (HDD) if SSD_TOTAL_GB quota is constrained.
stateful_fs_group3000Matches the Navidrome Helm chart's UID 1000/GID 2000 convention, ensuring the PVC is group-writable.

Group 13 — NFS Storage​

VariableDefaultDescription
enable_nfsfalseEnable and set nfs_mount_path = "/music" to mount a writable, shared music library via Cloud Filestore.
nfs_mount_path/mnt/nfsNot /music by default — must be overridden if using NFS for the library.

Group 14 — Cloud Storage​

VariableDefaultDescription
create_cloud_storagetrueAlways creates the storage bucket; it stays unmounted while the default block PVC serves /data.
gcs_volumes[]Add a read-only entry with mount_path = "/music" to source your library from GCS instead of NFS.

Group 16 — Database Configuration​

VariableDefaultDescription
database_typeNONEFixed by Navidrome_Common — Navidrome has no SQL database; every other database_*/sql_* variable is inert.

Group 19 — Custom Domain, Static IP & Networking​

VariableDefaultDescription
enable_custom_domaintrueProvisions the Gateway resource, but has no effect until application_domains is non-empty.
application_domains[]Set to expose Navidrome externally via Gateway + managed certificate.
reserve_static_iptrueStable IP once external ingress (LoadBalancer or Gateway) is configured.

All other inputs follow standard App_GKE behaviour.


5. Outputs​

These values are returned on a successful deployment and are the quickest way to locate and explore the running resources.

OutputDescription
service_nameKubernetes Service name.
namespaceNamespace the workload runs in.
service_cluster_ipIn-cluster ClusterIP.
stage_service_cluster_ipsMap of ClusterIPs for stage-specific services.
service_external_ipExternal LoadBalancer IP (when service_type = LoadBalancer and a static IP is reserved).
service_urlURL to reach Navidrome.
navidrome_admin_password_secret_idSecret Manager secret ID for the generated admin password (empty when enable_admin_password = false).
storage_bucketsCreated Cloud Storage buckets.
network_name / network_exists / regionsVPC network, presence, available regions.
container_image / container_registryDeployed image and Artifact Registry repo.
monitoring_enabled / monitoring_notification_channelsMonitoring status and channels.
deployment_id / tenant_id / resource_prefixNaming identifiers.
project_id / project_numberProject identifiers.
initialization_jobsNames of any operator-supplied initialization jobs.
statefulset_nameName of the StatefulSet.
cicd_enabled / cicd_configurationCI/CD status and details (repo, trigger, registry).
github_repository_url / github_repository_owner / github_repository_nameCI/CD GitHub details.
artifact_registry_repository / cloudbuild_trigger_name / cloudbuild_trigger_idRegistry and build trigger.
kubernetes_readyWhether the cluster/workload is ready.
vpc_sc_enabled / vpc_sc_perimeter_name / vpc_sc_dry_run_modeVPC-SC status.
audit_logging_enabled / artifact_registry_cmek_enabledAudit logging and CMEK status.

6. Configuration Pitfalls & Sensible Defaults​

Risk: Critical (data loss / outage / security) — High (service degraded) — Medium (cost or partial degradation) — Low (minor).

Inherited plan-time validation. This module passes its configuration through the App_GKE foundation engine, which validates values and combinations at plan time — a StatefulSet forced alongside a stateless setting, IAP with no authorized identities, quota_memory_* given as bare integers, an out-of-range container_port/backup_retention_days. Invalid configuration fails the plan with a clear, named error before any resource is created, so most mistakes below are caught up front rather than at apply or runtime.

SettingSensible valueRiskConsequence if wrong
stateful_pvc_enabledtrueCriticalDisabling it (or otherwise backing /data with gcsfuse) risks corrupting SQLite's write-locking model — DB corruption, lost library metadata.
stateful_pvc_storage_classstandard-rwo (or standard under quota pressure)HighSSD draws the tight SSD_TOTAL_GB regional quota; a wide campaign of stateful apps can exhaust it — switch to HDD standard if so.
max_instance_count1CriticalNavidrome has no multi-writer SQLite mode; scaling beyond 1 risks concurrent writes to the same PVC and DB corruption.
/music volume (gcs_volumes or enable_nfs)Configure explicitlyHighNothing is mounted at /music by default — the library scan finds no files and Navidrome serves an empty catalogue until a volume is added.
enable_admin_passwordtrue for any externally reachable deploymentHighfalse leaves the first-run wizard exposed to whoever reaches the URL first — they become admin.
application_domains / service_typeSet one to expose externallyMediumThe default ClusterIP + empty application_domains leaves Navidrome reachable only inside the VPC — expected for internal use, surprising if you wanted public access.
stateful_pvc_size20Gi (raise for very large libraries)MediumUndersizing risks the SQLite DB/cache/index filling the PVC on large libraries; the pod does not auto-expand storage.
quota_cpu_requests / quota_memory_requests / etc.N/ALowThese quota_* variables are declared but not forwarded to the foundation by this module — setting them has no effect; only enable_resource_quota is wired through (falling back to App_GKE's own quota defaults).
memory_limit1GiMediumBelow ~512Mi the pod risks OOM while holding the search index in memory during a large library scan.
stateful_fs_group3000MediumA mismatched fsGroup can leave the PVC unwritable by Navidrome's non-root UID, blocking DB writes at boot.
backup_retention_days7 (raise for prod)LowToo short for compliance retention of /data backups.

For the foundation behaviour referenced throughout — IAM and Workload Identity, autoscaling, ingress and certificates, CI/CD, Cloud Armor, IAP, Binary Authorization, VPC-SC, backups, and image mirroring — see App_GKE. Navidrome-specific application configuration shared with the Cloud Run variant is described in Navidrome_Common.

Need RAD to do something it does not do yet? Request it on the roadmap, or vote on what is already there.