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.


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 uses a different, prebuilt image on port 80).
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_typenullDeploymentDeployment.
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_typenullMYSQL_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.