Calibre-Web on GKE Autopilot — Lab Guide
Overview
Estimated time: 45–90 minutes
Calibre-Web is a self-hosted web app for browsing, reading, and downloading ebooks from a Calibre library — it serves an in-browser reader, an OPDS feed, and Kobo sync on top of the upstream LinuxServer.io image. This lab takes you through the full operational lifecycle of the Calibre-Web on GKE Autopilot module on Google Cloud: deploy it, access and verify it, run it day-to-day, observe it, diagnose common problems, and tear it down.
The lab focuses on operating the GKE module and the Google Cloud platform, not on Calibre-Web product features. For the complete list of provisioned services and every configuration input (organised by group), see the Configuration Guide — this lab deliberately does not duplicate that detail so it stays accurate over time.
Objectives
By the end of this lab you will be able to:
- Deploy the module from the RAD platform and locate the resources it provisions.
- Connect to the GKE cluster and access the running workload.
- Perform day-2 operations — inspect the StatefulSet and PVC, and manage the admin login.
- Observe the workload with Cloud Logging and Cloud Monitoring.
- Diagnose and resolve the most common deployment and runtime issues.
- Tear the deployment down cleanly.
Prerequisites
- Services_GCP (provides the VPC, GKE Autopilot cluster, Artifact Registry, and shared service accounts this module depends on). You do not need to deploy this yourself first — the platform automatically detects whether it already exists in the target project and provisions it before this module if not (see Task 1).
- A Google Cloud project with billing enabled.
- gcloud CLI and kubectl installed;
gcloud auth loginandgcloud auth application-default logincompleted. - Project Owner (or equivalent) IAM on the project.
- RAD platform access with permission to deploy modules into the project.
Set these shell variables once; every task below reuses them:
export PROJECT="<your-gcp-project-id>"
export REGION="us-central1" # the region you deploy into
Task 1 — Deploy the module [Automated]
-
Click Deploy in the RAD platform top navigation, open Calibre-Web (GKE) from the Platform Modules list to start configuration, set
project_id, and review the inputs. Configure only what you need — the Configuration Guide documents every input by group, with defaults. The web UI is reachable externally out of the box —service_typealready defaults toLoadBalancer(see Task 2). Setapplication_domainsonly if you want a custom hostname served through the Gateway instead of the raw LoadBalancer IP. Review the estimated cost (if credits are enabled) and click Deploy, which opens the deployment status page with real-time logs. -
The platform builds and mirrors the container image (pinned to a known-good
0.6.24tag whenapplication_version = "latest"), deploys a StatefulSet into the GKE Autopilot cluster (auto-selected becausestateful_pvc_enabled = true), provisions a per-pod block Persistent Volume Claim mounted at/config, and a Secret Manager secret (CALIBRE_ADMIN_PASSWORD). There is no database and no initialisation job — Calibre-Web manages its own SQLite storage on first boot. First deploys typically take 5–15 minutes (mostly the container build and PVC provisioning). -
Connect to the cluster and discover the namespace with name-agnostic filters:
CLUSTER=$(gcloud container clusters list --project="$PROJECT" --format="value(name)" --limit=1)
gcloud container clusters get-credentials "$CLUSTER" --region="$REGION" --project="$PROJECT"
NS=$(kubectl get ns -o name | grep calibreweb | head -1 | cut -d/ -f2)
echo "Cluster: $CLUSTER Namespace: $NS"
kubectl get statefulset,pods,svc,pvc -n "$NS"
Task 2 — Access & verify [Manual]
-
Confirm the workload is running:
kubectl get statefulset,pods -n "$NS" -
Find how to reach it.
service_typedefaults toLoadBalancer, so an external IP is provisioned out of the box.enable_custom_domain = truealso provisions a Gateway API resource, but the default emptyapplication_domains = []leaves it with no hostname to route — so the LoadBalancer IP is the route in unless you setapplication_domainsor switchedservice_typetoLoadBalancerbefore deploying:kubectl get svc,gateway,httproute -n "$NS"
# If service_type = LoadBalancer:
EXTERNAL_IP=$(kubectl get svc -n "$NS" \
-o jsonpath='{.items[?(@.spec.type=="LoadBalancer")].status.loadBalancer.ingress[0].ip}')
echo "External IP: $EXTERNAL_IP"
# If ClusterIP only, port-forward for this lab:
kubectl port-forward -n "$NS" svc/<service-name> 8083:8083 -
Confirm the service is healthy. Calibre-Web's startup and liveness probes both target the root path, which serves the login page unauthenticated:
curl -s -o /dev/null -w "%{http_code}\n" "http://${EXTERNAL_IP:-localhost}:8083/" # expect 200 -
Open the address in a browser. Sign in with the upstream image's built-in default credentials —
admin/admin123— the auto-generatedCALIBRE_ADMIN_PASSWORDsecret in Secret Manager is not wired into the container's login flow. Immediately after first sign-in, change the admin password in the Calibre-Web UI (Admin → Edit User); optionally use the generated secret's value as the new password:SECRET=$(gcloud secrets list --project="$PROJECT" --filter="name~admin-password" \
--format="value(name)" --limit=1)
gcloud secrets versions access latest --secret="$SECRET" --project="$PROJECT" -
Point Calibre-Web at your ebook library: use the in-app setup wizard to set the library location to
/books(empty on first run — upload or sync ebooks into it afterwards).
Task 3 — Operate & keep it running (Day-2) [Manual]
-
Inspect the workload, its PVC, and the PodDisruptionBudget:
kubectl get statefulset,pods,pvc,pdb -n "$NS"
kubectl describe statefulset -n "$NS" -
Do not raise
max_instance_countabove1. The StatefulSet'svolumeClaimTemplatesgive each replica its own, independent PVC — scaling up does not share/configacross pods; it silently forks the library and config into separate, unsynchronised copies per pod. Scaling is a configuration change in the RAD platform (change the min/max instance inputs and click Update), not a manualkubectl scale— a manual edit would be reverted on the next apply. -
Update the application version by changing the version input in the RAD platform and applying it via Update; a new image builds (pinned via the app-specific
CALIBREWEB_VERSIONbuild ARG) and a rolling update replaces the pod. -
Inspect the
/configPVC and its backing disk:kubectl describe pvc -n "$NS" -l app=<service-name>
gcloud compute disks list --project="$PROJECT" --filter="name~calibreweb"The default StorageClass (
standard-rwo) is SSD-backed and draws the tight regionalSSD_TOTAL_GBquota; considerstateful_pvc_storage_class = "standard"(HDD) if you are running a campaign of stateful GKE apps in the same project. Scaling the workload to zero does not release the PVC — only deleting it (or the namespace) does. -
Manage secrets and jobs:
kubectl get secrets -n "$NS"
gcloud secrets list --project="$PROJECT" --filter="name~calibreweb"
kubectl get jobs -n "$NS" # only user-supplied jobs, if any
Task 4 — Observe: Logging & Monitoring [Manual]
-
Logs — from
kubectlor the Logs Explorer:kubectl logs -n "$NS" statefulset/<service-name> --tail=50Logs Explorer filter:
resource.type="k8s_container" AND resource.labels.namespace_name="<namespace>". -
Monitoring — open the GKE / Kubernetes dashboards and review pod CPU and memory utilisation, restart counts, and PVC usage. Uptime checks are disabled by default (
uptime_check_config.enabled = false) — enable one in the RAD platform if you want automated availability alerting (requires external access to be configured, per Task 2).
Task 5 — Troubleshoot & debug [Manual]
Durable techniques for the failure modes you are most likely to hit. These are platform-level diagnostics and do not change with Calibre-Web releases.
- Pod not Ready / CrashLoopBackOff: inspect events and logs. The startup probe
targets
/(the login page,200, no authentication required) with a generousfailure_threshold=10atperiod=10s, so a slow-starting container still has time to pass.kubectl describe pod -n "$NS" <pod> # Events section shows scheduling/probe/mount errors
kubectl logs -n "$NS" <pod> --previous # logs from the crashed container - Login fails with the built-in credentials: confirm you are using
admin/admin123(the upstream default), not theCALIBRE_ADMIN_PASSWORDSecret Manager value — that secret is provisioned but not applied to the container's actual login flow. - No external access at all:
service_typedefaults toLoadBalancer, so check whether an external IP has been assigned yet (it can take a minute or two). Emptyapplication_domainsonly means the custom-domain path has no hostname to route — the LoadBalancer IP is still the route in (see Task 2). - PVC stuck
Pending/SSD_TOTAL_GBquota exceeded: check current disk usage and consider switchingstateful_pvc_storage_classtostandard(HDD) on a quota-constrained project; remember scale-to-zero does not free existing PVCs.kubectl describe pvc -n "$NS"
gcloud compute disks list --project="$PROJECT" - Suspected data-fork after a scale-up: if
max_instance_countwas ever raised above1, each pod holds an independent, unsynchronised/config— checkkubectl get pvc -n "$NS"for multiple PVCs and reconcile by choosing one pod's data as authoritative before scaling back to1. - Image pull errors: confirm the image exists in Artifact Registry and the node service account can pull it.
See the Configuration Guide's Configuration Pitfalls section for setting-specific
gotchas. Note two confirmed documentation-vs-source mismatches to be aware of
while troubleshooting: the liveness_probe variable's description text (inherited
from the shared foundation) mentions a /health endpoint that Calibre-Web does not
expose (the actual configured path is / — do not change it to /health), and the
Cloud Run sibling module's calibreweb_url output description contains an
unrelated stale copy-paste reference to a "REST API (port 6333)" — not applicable
to the GKE variant's service_url output, which is simply the normal address to
reach the workload per service_type.
Task 6 — Tear down [Automated]
On the Deployments page, open the deployment and click the Trash icon (Delete). Delete runs terraform destroy and is irreversible (the deployment record is retained for history). If a deployment is stuck and the RAD platform can no longer manage it (for example after manual changes that conflict with the Terraform state), use Purge instead — it removes the deployment from RAD's records without destroying the cloud resources (it makes RAD forget the project). This removes everything the module created — the Kubernetes workload
and namespace, the per-pod PVC (and its underlying disk, along with your ebook
library and Calibre-Web's SQLite databases, since they live only on that volume),
the CALIBRE_ADMIN_PASSWORD secret, and Artifact Registry images. Resources owned
by Services_GCP (the VPC, GKE cluster, registry) are managed separately and are
not removed here.
Summary
| Task | Type | Outcome |
|---|---|---|
| 1 — Deploy | Automated | Module deploys a StatefulSet with a per-pod block PVC at /config, a Secret Manager admin-password secret; no database |
| 2 — Access & verify | Manual | Connect to the cluster; confirm/configure external access; sign in with admin/admin123 and change the password immediately |
| 3 — Operate | Manual | Inspect the StatefulSet/PVC, keep max_instance_count=1, update version, watch SSD quota |
| 4 — Observe | Manual | Query Cloud Logging; review Cloud Monitoring metrics (uptime check optional, off by default) |
| 5 — Troubleshoot | Manual | Diagnose pod, login, ingress, PVC/quota, and image-pull issues; two known doc/source mismatches noted |
| 6 — Tear down | Automated | Delete (Trash) removes all module resources, including the PVC and its disk with the ebook library and SQLite state |