Radicale on GKE Autopilot — Lab Guide
Overview
Estimated time: 30–60 minutes
Radicale is an open-source, self-hosted CalDAV/CardDAV server for calendar and contacts sync. This lab takes you through the full operational lifecycle of the Radicale 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 Radicale 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 (with a production-style block PVC) and locate the resources it provisions.
- Access and verify the running workload, retrieve the generated admin credential, and connect a CalDAV/CardDAV client.
- Create a NEW calendar on GKE (which works natively, unlike Cloud Run) and understand where the pre-seeded defaults land when a PVC is used.
- Perform day-2 operations — inspect, scale, and update.
- 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 deployed in the target project (provides the VPC, GKE Autopilot cluster, Artifact Registry, and shared service accounts this module depends on).
- A Google Cloud project with billing enabled.
- gcloud CLI authenticated:
gcloud auth loginandgcloud auth application-default login. - Project Owner (or equivalent) IAM on the project.
- RAD platform access with permission to deploy modules into the project.
- (Optional) A CalDAV/CardDAV client to verify end-to-end sync — e.g. Thunderbird, Apple Calendar/Contacts, or DAVx5.
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
export NAMESPACE="<deployment-namespace>" # reported in the deployment Outputs
gcloud container clusters get-credentials <cluster-name> --region "$REGION" --project "$PROJECT"
Task 1 — Deploy the module [Automated]
-
In the RAD platform, open Radicale (GKE), set
project_id, and review the inputs. Configure only what you need — the Configuration Guide documents every input by group, with defaults. Setapplication_display_name = "Radicale"explicitly (the module's default currently carries a stale value inherited from its clone source), and setstateful_pvc_enabled = truefor a production-style deployment with real block storage. Review the estimated cost (if credits are enabled) and click Deploy, which opens the deployment status page with real-time logs. -
The platform provisions the Kubernetes workload (a StatefulSet when
stateful_pvc_enabled = true, otherwise a Deployment backed by GCS FUSE), a Secret Manager secret holding a generatedADMIN_PASSWORD, and runs theseed-default-collectionsinitialization Job. First deploys typically take 5–10 minutes. -
When it completes, discover the resources with name-agnostic filters:
SERVICE=$(kubectl get svc -n "$NAMESPACE" -o name | grep radicale | head -1 | cut -d/ -f2)
EXTERNAL_IP=$(kubectl get svc "$SERVICE" -n "$NAMESPACE" -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
echo "Service: $SERVICE"
echo "IP: $EXTERNAL_IP"
Task 2 — Access & verify [Manual]
-
Confirm the pod is healthy and serving:
kubectl get pods -n "$NAMESPACE" -l app="$SERVICE" # expect N/N Running, 0 restarts
curl -s "http://$EXTERNAL_IP/" -o /dev/null -w '%{http_code}\n' # expect 302 -
Radicale ships with no built-in default admin account — retrieve the generated credential from Secret Manager:
SECRET=$(gcloud secrets list --project="$PROJECT" --filter="name~radicale-admin-password" \
--format="value(name)" --limit=1)
ADMIN_PASSWORD=$(gcloud secrets versions access latest --secret="$SECRET" --project="$PROJECT")
echo "Username: admin"
echo "Password: $ADMIN_PASSWORD" -
Confirm authenticated access works with a
PROPFINDagainst the admin's principal (expect207 Multi-Status):curl -s -u "admin:$ADMIN_PASSWORD" -X PROPFIND "http://$EXTERNAL_IP/admin/" \
-H "Depth: 1" -o /dev/null -w '%{http_code}\n' -
If you deployed with
stateful_pvc_enabled = true, the pre-seeded Default Calendar/Default Address Book will not appear (the seed job cannot mount the StatefulSet's PVC — see Task 5). Create your first calendar directly — this works natively on GKE, unlike Cloud Run:curl -s -u "admin:$ADMIN_PASSWORD" -X MKCOL "http://$EXTERNAL_IP/admin/my-calendar/" \
-o /dev/null -w '%{http_code}\n' # expect 201 CreatedIf you deployed without a PVC, connect a CalDAV/CardDAV client (Thunderbird, Apple Calendar, DAVx5) to
http://$EXTERNAL_IP/admin/and you should see the pre-seeded Default Calendar and Default Address Book.
Task 3 — Operate & keep it running (Day-2) [Manual]
-
Inspect the workload and its rollout history:
kubectl get statefulset "$SERVICE" -n "$NAMESPACE" # if stateful_pvc_enabled = true
kubectl get deploy "$SERVICE" -n "$NAMESPACE" # otherwise
kubectl rollout status statefulset/"$SERVICE" -n "$NAMESPACE" -
Scale —
max_instance_countis pinned to1and should not be raised: Radicale's storage backend is not designed for concurrent multi-instance access.min_instance_countcan be raised to1via the RAD platform's Update flow if you want to avoid cold starts. -
Update the application version tag via the RAD platform's Update flow. Remember: Radicale's container registry tags have no
vprefix (e.g.3.7.7, notv3.7.7). -
Manage secrets and the PVC:
gcloud secrets list --project="$PROJECT" --filter="name~radicale"
kubectl get jobs -n "$NAMESPACE"
kubectl get pvc -n "$NAMESPACE" # only when stateful_pvc_enabled = true
Task 4 — Observe: Logging & Monitoring [Manual]
-
Logs:
kubectl logs -n "$NAMESPACE" statefulset/"$SERVICE" --tail=100 -
Monitoring — open the GKE Workloads dashboard for the workload and review CPU/memory utilisation and replica count.
Task 5 — Troubleshoot & debug [Manual]
- Pod unhealthy / CrashLoopBackOff: inspect pod events and logs. The
startup probe targets
/.kubectl describe pod -n "$NAMESPACE" -l app="$SERVICE"
kubectl logs -n "$NAMESPACE" statefulset/"$SERVICE" --tail=200 seed-default-collectionssucceeded, but no default calendar shows up on a PVC-backed deployment. This is expected, not a bug, whenstateful_pvc_enabled = true. The seed job (a shared Cloud-Run/GKE Common-module Job) only mounts the shared GCSstoragebucket — it cannot attach to the StatefulSet'sReadWriteOnceblock PVC, which is already held by the running pod. The job's writes land harmlessly in the unused GCS bucket. Create your first calendar viacurl -X MKCOLor a real CalDAV client instead (see Task 2, step 4) — this works natively on GKE.kubectl get jobs -n "$NAMESPACE"
kubectl logs -n "$NAMESPACE" job/<seed-default-collections-job-name>- PVC stuck
Pending: check the StorageClass and regional quota.stateful_pvc_storage_classdefaults tostandard(HDD) specifically to avoid the tightSSD_TOTAL_GBquota — if you overrode it tostandard-rwo/premium-rwo(SSD), verify you have SSD quota headroom.kubectl describe pvc -n "$NAMESPACE" - 401 Unauthorized on every request, even with the right-looking
credential: double-check you retrieved the current
ADMIN_PASSWORDfrom Secret Manager, and that you're usingadmin(or your configuredADMIN_USERNAME), not an email address. - 403 / permission errors: verify the Workload Identity binding.
See the Configuration Guide's Configuration Pitfalls section for setting-specific gotchas.
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. If a
deployment is stuck and the RAD platform can no longer manage it, use
Purge instead — it removes the deployment from RAD's records without
destroying the cloud resources. This removes everything the module created —
the Kubernetes workload, Service, PVC (if used), Secret Manager secrets, and
the GCS bucket. 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 provisions the GKE workload (StatefulSet + PVC recommended), a generated admin secret, and runs the default-collection seed job |
| 2 — Access & verify | Manual | Pod Ready 0 restarts; retrieve the generated admin password; create/verify a calendar (MKCOL works natively on GKE) |
| 3 — Operate | Manual | Inspect rollout, understand the max=1 scaling limit, update version, manage PVC |
| 4 — Observe | Manual | Query Cloud Logging; review Cloud Monitoring metrics |
| 5 — Troubleshoot | Manual | Diagnose pod, seed-job/PVC-mount, and auth issues |
| 6 — Tear down | Automated | Delete (Trash) removes all module resources, including the PVC and every stored collection |