Skip to main content

LimeSurvey on GKE Autopilot

LimeSurvey on GKE Autopilot

LimeSurvey is a free, open-source online survey and questionnaire platform (PHP/Yii). It supports unlimited surveys with dozens of question types, conditional branching, quotas, multi-language surveys, and rich statistics/exports. This module deploys LimeSurvey 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 LimeSurvey 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 LimeSurvey costs on RAD, and how that compares​

LimeSurvey on RAD's GKE Autopilot module costs about US$110 a month in a Google Cloud project you own, plus a one-off 110-credit (US$11.00) module fee. GKE suits LimeSurvey running alongside other Kubernetes workloads; for the lowest cost, the Cloud Run module runs the same LimeSurvey for about US$84 a month. If you only need it occasionally, a RAD-managed project can be deleted and restored within 30 days for a handful of credits, so that US$110 becomes a few dollars a month instead. See Pause it for free, below.

What you pay on RAD​

In a Google Cloud project you ownIn a project RAD manages for you
Module fee, once per deployment110 credits (US$11.00 at the top-up price)99.0 credits (10% lower)
Build timeA few credits per buildThe same
Google Cloud running costBilled by Google to your own billing account, about US$110 a month for the default configuration (table below)Metered hourly in credits; RAD publishes 99 credits a day, about 2970 a month (about US$297 at the top-up price, US$238 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 database.

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

Resource (module default)Per month
GKE Autopilot pod, 1x vCPU / 2 GiBUS$39.68
Cloud SQL for MySQL, 1 vCPU / 3.75 GB, zonal, 10 GB SSDUS$51.02
NFS/cache file server (small VM)US$13.43
Cloud NAT and networkingUS$5.00
Cloud Storage (add-ons, backups)US$1.00
GKE cluster management feeshared across every GKE app in the project; $0 if this is your only cluster, else ~73
Totalabout US$110

How it compares​

  • LimeSurvey Cloud (Basic £66/month, Expert £30/month annually, Business £64/month annually, Corporate custom — limesurvey.org/pricing) is cheaper in cash for capped response volumes; self-hosting earns its cost back once you need more responses, more admins, or no ceiling at all.
  • A GCE e2-standard-2 (2 vCPU/8 GB, about US$49/month) is the closer-sized self-managed comparison for the GKE pod plus its MySQL and file server, minus the managed backups and patching RAD includes.
  • Or see the Cloud Run guide for the lower-cost option.

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

If LimeSurvey 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 LimeSurvey you only need occasionally — a course project, a demo, a short-lived evaluation — 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 Cloud SQL, any VM or LimeSurvey's own compute one by one — the whole project, and everything in it, simply stops.
  • Restoring costs a handful of 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. For LimeSurvey behind a small Services_GCP and Project_GCP, that is a handful of credits (under US$1) in total, against the 115 credits a full redeploy costs.
  • 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: the backup bucket does not survive. LimeSurvey's nightly 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 LimeSurvey and want to keep that work, copy a backup out (to Google Drive, or a bucket outside the project) before you delete, the same as the redeploy workflow below. 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​

GKE keeps at least one pod running, so it never scales to zero on its own. 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 115 credits (US$11.50), because RAD recreates the project and shared services before LimeSurvey. Deleting saves money only once LimeSurvey would otherwise sit unused for about four days or more, both in your own project (about US$3.67 a day) and in a RAD-managed one (99 credits a day).
  • Delete everything LimeSurvey uses. Most of the running cost is usually the database and the shared file/cache VM. They stop only when nothing else in the project uses it, so deleting LimeSurvey while something else shares the project saves only LimeSurvey'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 LimeSurvey session for a class. Each participant gets LimeSurvey in their own Google Cloud project for 15 minutes to 24 hours, within an allowance the trainer sets. Either the trainer funds every place up front, 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 LimeSurvey 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 of the engagement 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; RAD fees and the 99-credit daily estimate from radmodules.dev/pricing; Delete and restore projects for the 30-day recovery window and what it says about Cloud Storage objects without soft delete. LimeSurvey Cloud pricing. Prices change; check each source before relying on a figure.

1. Overview​

LimeSurvey runs as a single PHP/Apache web workload built from the martialblog/limesurvey image. The deployment wires together a focused set of Google Cloud services:

CapabilityGoogle Cloud serviceNotes
ComputeGKE AutopilotPHP/Apache pods on port 8080, 1 vCPU / 2 GiB by default
DatabaseCloud SQL for MySQL 8.0Required — the Common module fixes database_type = "MYSQL_8_0" regardless of the app variable's null default
File persistenceCloud Filestore (NFS)Survey uploads/exports/runtime data persist under /var/www/html/upload, shared across pods
Object storageCloud StorageA generic data bucket (Group 14 default) plus a Common-provisioned limesurvey-uploads bucket; neither is mounted into the pod by default
SecretsSecret ManagerAuto-generated ADMIN_PASSWORD (LimeSurvey super-admin); database password managed by the foundation
IngressCloud Load BalancingExternal LoadBalancer with a reserved static IP; optional custom domain + managed certificate

Sensible defaults worth knowing up front:

  • MySQL 8.0 is mandatory. LimeSurvey_Common's config output hardcodes database_type = "MYSQL_8_0", overriding the app module's database_type default of null; other engines are not supported.
  • Cloud SQL is reached via the Auth Proxy sidecar on loopback. The variant sets enable_cloudsql_volume = true, and limesurvey.tf merges DB_HOST = "127.0.0.1" into the application config so LimeSurvey dials the cloud-sql-proxy sidecar rather than a socket path.
  • InnoDB is forced. The martialblog image's entrypoint defaults the DB engine to MyISAM, which Cloud SQL for MySQL 8.0 disables (disabled_storage_engines=MyISAM). Without an explicit override the console installer's CREATE TABLE ... ENGINE=MyISAM fails, and — because the entrypoint invokes the installer without verbose output — the failure is swallowed: the pod reports healthy while every page 500s with table settings_global not found. The module sets DB_MYSQL_ENGINE=InnoDB and DBENGINE=InnoDB to avoid this.
  • Single replica by default. min_instance_count = 1, max_instance_count = 1. LimeSurvey keeps PHP session state; do not scale beyond 1 without verifying shared-storage/session behaviour.
  • NFS is enabled by default (enable_nfs = true, mounted at /var/www/html/upload) so uploaded survey assets, imports, and exports persist and are shared across pods.
  • Session affinity is ClientIP so a client's requests reach the same pod.
  • First-boot auto-install (no separate migration job). The upstream martialblog/limesurvey entrypoint runs LimeSurvey's console installer / updatedb on first container start once db-init has provisioned the database and user.
  • ADMIN_PASSWORD is generated automatically and stored in Secret Manager. It seeds the first-run super-admin account (fixed username admin, ADMIN_EMAIL = admin@techequity.cloud); the martialblog entrypoint exits if ADMIN_PASSWORD is missing.
  • PUBLIC_URL is not preset on GKE. The limesurvey_app module call in limesurvey.tf does not forward a service_url, so PUBLIC_URL is only injected when you explicitly set it via environment_variables — do so once the external LoadBalancer IP or custom domain is known so absolute links resolve correctly.
  • application_version = "latest" maps to a pinned base tag. The Dockerfile keys the base image on an app-specific LIMESURVEY_VERSION build arg (not the generic APP_VERSION, which the Foundation injects into build_args and would otherwise clobber to "latest"); "latest" resolves to 6-apache.

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 LimeSurvey workload​

LimeSurvey pods are scheduled on Autopilot, which bills for the CPU/memory the pods actually request. The workload runs as a Deployment (no NFS-forced Recreate override is set for this app, so verify the effective strategy before assuming rolling updates are safe with max_instance_count > 1).

  • Console: Kubernetes Engine → Workloads → select the LimeSurvey workload for pods, revisions, and events. Kubernetes Engine → Services & Ingress shows the external IP.
  • CLI:
    kubectl get pods,svc -n "$NAMESPACE"
    kubectl logs -n "$NAMESPACE" deploy/<service-name> --tail=100
    kubectl describe pod -n "$NAMESPACE" -l app=<service-name>

See App_GKE for how Autopilot, scaling, and the workload type (Deployment vs StatefulSet) are managed.

B. Cloud SQL for MySQL 8.0​

LimeSurvey stores all survey data (surveys, questions, responses, users, settings) in a managed Cloud SQL for MySQL 8.0 instance. Pods reach it through the Cloud SQL Auth Proxy sidecar on 127.0.0.1:3306; no public IP is exposed. On first deploy the db-init job creates the application database, user, and grants; the LimeSurvey console installer then creates the schema using the forced InnoDB engine.

  • Console: SQL → select the instance for connections, backups, flags, metrics.
  • CLI:
    gcloud sql instances list --project "$PROJECT"
    gcloud sql instances describe <instance-name> --project "$PROJECT"
    gcloud sql connect <instance-name> --user=<db-user> --database=<db-name> --project "$PROJECT"

The instance name, database, user, and the Secret Manager secret holding the password are all in the Outputs. See App_GKE for the connection model, automated backups, and password rotation.

C. Cloud Storage & NFS file persistence​

Two Cloud Storage buckets are provisioned by default (a generic data bucket from the Group 14 variable default, and a limesurvey-uploads bucket supplied by LimeSurvey_Common); neither is mounted into the pod unless you add an entry to gcs_volumes. The actual survey upload/export tree lives on NFS (Cloud Filestore) at /var/www/html/upload, shared across pods.

  • Console: Cloud Storage → Buckets; Filestore → Instances.
  • CLI:
    gcloud storage buckets list --project "$PROJECT" --filter="name~limesurvey"
    gcloud filestore instances list --project "$PROJECT"
    kubectl get pvc -n "$NAMESPACE"

See App_GKE for CMEK options and GCS Fuse mounts.

D. Secret Manager​

One LimeSurvey-specific secret is generated automatically and stored in Secret Manager: ADMIN_PASSWORD (the first-run super-admin password, 20 characters, no special characters). The database password is managed separately by the foundation. On GKE, secrets are projected into pods via the Secret Store CSI driver.

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

See App_GKE for the Secret Store CSI integration and rotation.

E. Networking & ingress​

By default the workload is exposed through an external Cloud Load Balancing IP (service_type = LoadBalancer, reserve_static_ip = true so the address survives redeploys). A custom domain with a Google-managed certificate can be enabled.

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

See App_GKE for custom domains, Cloud CDN, and static IP details.

F. Cloud Logging & Monitoring​

Pod stdout/stderr flow to Cloud Logging; GKE and Cloud SQL metrics flow to Cloud Monitoring. Optional uptime checks and alert policies are available.

  • 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. LimeSurvey Application Behaviour​

  • First-deploy database setup. The db-init job runs db-init.sh using mysql:8.0-debian. It prefers a Cloud SQL Unix socket if one is present under /cloudsql, otherwise falls back to TCP via DB_IP/DB_HOST, idempotently creates the application database, user, and grants, verifies the app user can connect, then shuts down the proxy sidecar via the quitquitquit admin endpoint (falling back to SIGKILL). The job is safe to re-run (execute_on_apply = true, max_retries = 3).
  • First-boot auto-install (no separate migration job). With the database provisioned, the martialblog image's own entrypoint generates application/config/config.php from the environment and runs LimeSurvey's console installer / updatedb on first pod start, creating the schema in the empty database using the forced InnoDB engine.
  • Admin account. The installer creates a super-admin with the fixed username admin (ADMIN_NAME = "Administrator", ADMIN_EMAIL = admin@techequity.cloud) and the generated ADMIN_PASSWORD secret. Retrieve it before first login.
  • DB env-var aliasing on loopback. The variant sets db_user_env_var_name = "DB_USERNAME", db_password_env_var_name = "DB_PASSWORD", and db_name_env_var_name = "DB_NAME" so the tenant-scoped values the Foundation injects land on the names the martialblog entrypoint expects; limesurvey.tf overrides DB_HOST to 127.0.0.1 (the Auth Proxy sidecar).
  • DB_MYSQL_ENGINE / DBENGINE are forced to InnoDB. This avoids the silent install failure described in Overview — Cloud SQL for MySQL 8.0 disables MyISAM, the image's own default.
  • Set PUBLIC_URL after the IP is known. It is not preset on GKE — patch the deployment or set environment_variables to the external URL once the LoadBalancer IP or custom domain is assigned:
    kubectl patch deploy <service-name> -n "$NAMESPACE" \
    -p '{"spec":{"template":{"spec":{"containers":[{"name":"limesurvey","env":[
    {"name":"PUBLIC_URL","value":"https://survey.example.com"}]}]}}}}'
  • Health path. Startup probe is TCP on / (30s initial delay, 10s timeout, 15s period, 20 failures — generous for first-boot install). Liveness probe is HTTP GET / (300s initial delay, 60s timeout, 60s period, 3 failures) once LimeSurvey is serving requests. Allow several minutes on first boot for the installer.
  • Inspect the init job and running config:
    kubectl get jobs -n "$NAMESPACE"
    kubectl logs -n "$NAMESPACE" job/<db-init-job-name>
    kubectl exec -n "$NAMESPACE" deploy/<service-name> -- env | grep -E 'DB_|ADMIN_|PUBLIC_URL'

4. Configuration Variables​

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

Group 3 — Application Identity​

VariableDefaultDescription
application_namelimesurveyBase name for resources. Do not change after first deploy.
application_versionlatestmartialblog/limesurvey base tag; latest resolves to the pinned 6-apache tag via the app-specific LIMESURVEY_VERSION build arg.

Group 4 — Runtime & Scaling​

VariableDefaultDescription
cpu_limit1000m1 vCPU minimum.
memory_limit2GiMinimum 512Mi; 2Gi recommended for production.
min_instance_count1Keep at 1 to keep the workload reachable.
max_instance_count1Keep at 1 unless session-sharing behaviour is verified.
container_port8080The martialblog/limesurvey apache image listens on 8080 (the Cloud Run variant builds the identical thin wrapper via LimeSurvey_Common and also defaults to port 8080).
enable_cloudsql_volumetrueAuth Proxy sidecar (loopback) — required on GKE.
php_memory_limit512MPHP memory limit; raise for large surveys/exports.
upload_max_filesize / post_max_size64MMax upload / POST size; keep post_max_size ≥ upload_max_filesize.

Group 6 — GKE Backend & Cluster​

VariableDefaultDescription
service_typeLoadBalancerExternal IP for the LimeSurvey UI.
workload_typenull → DeploymentDeployment.
session_affinityClientIPSticky routing so a client reaches the same pod.

Group 10 — Observability​

VariableDefaultDescription
startup_probeTCP /, 30s delay, 20 failuresGenerous first-boot window for the console installer.
liveness_probeHTTP /, 300s delay, 3 failuresThe / landing page returns 200 once LimeSurvey is serving requests.

Group 13 — NFS Storage​

VariableDefaultDescription
enable_nfstrueNFS is on by default so uploaded survey assets persist and are shared.
nfs_mount_path/var/www/html/uploadWhere LimeSurvey stores uploads/exports/runtime data.

Group 14 — Cloud Storage​

VariableDefaultDescription
storage_buckets[{ name_suffix = "data" }]Generic Group 14 default bucket; a second limesurvey-uploads bucket is added by LimeSurvey_Common's module_storage_buckets. Neither is mounted by default.
gcs_volumes[]No GCS Fuse mount by default — persistence goes through NFS instead.

Group 16 — Database Configuration​

VariableDefaultDescription
database_typenull → MYSQL_8_0 (forced by Common)Only MySQL 8.0 is supported.
application_database_namelimesurveyDatabase name. Immutable after first deploy.
application_database_userlimesurveyApplication database user; password auto-generated in Secret Manager.

Group 19 — Access & Networking​

VariableDefaultDescription
reserve_static_iptrueStable external IP across redeploys.
application_domains[]Custom hostnames + managed certificate.

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 a static IP is reserved).
service_urlURL to reach LimeSurvey.
database_instance_nameCloud SQL instance name.
database_name / database_userApplication database name / user.
database_password_secretSecret Manager secret holding the DB password.
database_host / database_portDB endpoint (127.0.0.1 via the Auth Proxy) / port.
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.
initialization_jobs / db_import_jobNames of the setup (db-init) and (optional) import jobs.
deployment_id / tenant_id / resource_prefixNaming identifiers.
project_id / project_numberProject identifiers.
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
database_typenull (→ forced MYSQL_8_0)CriticalOnly MySQL 8.0 is supported by the entrypoint and schema.
application_database_name / application_database_userSet onceCriticalImmutable after first deploy; renaming recreates the DB/user and orphans all data.
DB engine env vars (DB_MYSQL_ENGINE/DBENGINE)Leave as InnoDB (module default)CriticalReverting to the image's MyISAM default breaks table creation on Cloud SQL — the pod looks healthy but every page 500s.
enable_nfstrueHighDisabling it makes uploaded survey assets/exports ephemeral — lost on pod recreation.
enable_cloudsql_volumetrueHighThe Auth Proxy sidecar on 127.0.0.1:3306 is required for DB connectivity on GKE.
max_instance_count1HighScaling beyond 1 without verified session-sharing behaviour risks split sessions.
session_affinityClientIPHighWithout stickiness, requests bounce between pods and disrupt authenticated sessions.
PUBLIC_URL (set after IP known)External LoadBalancer/domain URLHighA missing or wrong public URL breaks absolute links and asset resolution.
memory_limit2GiHighBelow 512Mi the PHP/Apache pod OOMs under load.
quota_memory_requests / _limitsbinary units (4Gi, 8192Mi)CriticalBare integers are treated as bytes and block all pod scheduling in the namespace.
ADMIN_PASSWORD (auto-generated)Retrieve before first loginMediumNot knowing it locks you out of the first super-admin account until reset via the DB.
reserve_static_iptrueMediumWithout it, the external IP can change across redeploys, breaking DNS and PUBLIC_URL.
backup_retention_days7 (raise for prod)MediumToo short for compliance retention.

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. LimeSurvey-specific application configuration shared with the Cloud Run variant (secret generation, the db-init job, NFS upload storage, and the DB env-var mapping) is described in LimeSurvey_Common.

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