Skip to main content

Directus GKE Module — Configuration Guide

Directus_GKE is a wrapper module that deploys Directus — an open-source headless CMS and data API platform — on Google Kubernetes Engine (GKE) Autopilot. It composes two underlying modules:

  • App_GKE — provides all GKE infrastructure: cluster targeting, Kubernetes workloads, networking, security, CI/CD, storage, observability, and backup.
  • Directus Common — generates the Directus application configuration, database initialisation scripts, migration jobs, and Directus-specific environment variables. Its outputs are injected into App GKE via the application_config, module_env_vars, module_secret_env_vars, and module_storage_buckets inputs.

How to use this guide: Every variable available in App_GKE is also available in Directus_GKE under the same name and with the same behaviour, unless noted below. For the full description of those variables, consult the App_GKE Configuration Guide. This guide documents only what is unique to Directus_GKE: variables that exist only in this module, variables whose names differ from their App_GKE equivalents, and variables whose default values have been tuned for Directus.


Standard Configuration Reference

The following configuration areas are provided by the underlying App_GKE module. Consult the linked sections of the App_GKE Configuration Guide for full documentation.

Configuration AreaApp GKE.md SectionDirectus-Specific Notes
Module Metadata & Configuration§1 Module OverviewDirectus-specific module_description, module_documentation, and module_services defaults are pre-set. resource_creator_identity — see Resource Creator Identity below.
Project & Identity§2 IAM & Access ControlIdentical, plus deployment_region unique to this module — see Project & Identity below.
Application Identity§3.A Compute (GKE Autopilot)Directus-specific defaults; also exposes db_name, db_user, and description — see Application & Database Identity below.
Runtime & Scaling§3.A Compute (GKE Autopilot)Directus-specific defaults for container_port, scaling counts, and enable_cloudsql_volume; also exposes cpu_limit, memory_limit, startup_probe, and liveness_probe — see Runtime Configuration below.
Environment Variables & Secrets§3 Core Service ConfigurationIdentical.
Networking & Network Policies§3.D Networking & Network PoliciesIdentical.
Initialization Jobs & CronJobs§3.E Initialization Jobs & CronJobsThe Directus database migration job is injected automatically by Directus Common; any jobs defined in initialization_jobs are appended after it.
Additional Services§3.F Additional ServicesIdentical.
Storage — NFS & GCS§3.C Storage (NFS / GCS / GCS Fuse)enable_nfs defaults to true for Directus to support shared asset storage across replicas.
Database Configuration§3.B Database (Cloud SQL)Directus-specific defaults for database_type, enable_postgres_extensions, and postgres_extensions; uses db_name / db_user — see Database Configuration below.
Backup Schedule & Retention§3.B Database (Cloud SQL)Identical.
Custom SQL Scripts§3.E Initialization Jobs & CronJobsIdentical.
Observability & Health Checks§3.A Compute (GKE Autopilot)Directus exposes startup_probe and liveness_probe pre-tuned for /server/health — see Runtime Configuration below.
Cloud Armor WAF§4.A Cloud Armor WAFDirectus GKE additionally exposes cloud_armor_policy_name (default "default-waf-policy") to target a named policy.
Identity-Aware Proxy§4.B Identity-Aware Proxy (IAP)Directus GKE additionally exposes iap_oauth_client_id, iap_oauth_client_secret (both sensitive), and iap_support_email — required for GKE Gateway-based IAP.
Binary Authorization§4.C Binary AuthorizationDirectus GKE additionally exposes binauthz_evaluation_mode (default "ALWAYS_ALLOW"; options: ALWAYS_ALLOW, REQUIRE_ATTESTATION, ALWAYS_DENY) forwarded to App GKE.
VPC Service Controls§4.D VPC Service ControlsIdentical.
Secrets Store CSI Driver§4.E Secrets Store CSI DriverIdentical.
Traffic & Ingress§5 Traffic & IngressIdentical.
CDN§5.B CDNIdentical.
Static IP§5.C Static IPIdentical.
Cloud Build Triggers§6.A Cloud Build TriggersIdentical.
Cloud Deploy Pipeline§6.B Cloud Deploy PipelineIdentical.
Image Mirroring§6.C Image Mirroringenable_image_mirroring defaults to true.
Pod Disruption Budgets§7.A Pod Disruption BudgetsIdentical.
Topology Spread Constraints§7.B Topology Spread ConstraintsIdentical.
Resource Quotas§7.C Resource QuotasIdentical.
Auto Password Rotation§7.D Auto Password RotationIdentical.
Redis Cache§8.A Redisenable_redis defaults to true for Directus. See Redis Cache for Directus-specific configuration.
Backup Import§8.B Backup ImportUses backup_uri (preferred) instead of backup_file — see Variable Name Differences. A legacy backup_file variable also exists (default "backup.sql") but is not forwarded to App GKE; use backup_uri instead.
Service Mesh (ASM)§8.C Service Mesh (ASM via Fleet)Identical.
Multi-Cluster Services§8.D Multi-Cluster Services (MCS)Identical.

Directus-Specific Defaults

The following variables are shared with App GKE but have different default values in Directus GKE, pre-tuned for a Directus deployment. Where the variable name differs from its App GKE equivalent, the App GKE name is shown in parentheses.

VariableDirectus GKE DefaultApp GKE DefaultReason
application_name"directus""gkeapp"Identifies the Directus workload across all resource names.
application_version"11.1.0""1.0.0"Pins the Directus container image version.
container_port80558080Directus listens on port 8055 by default.
min_instance_count01Scale-to-zero is the default; set to 1 or more to eliminate cold starts.
max_instance_count33Directus default; increase for high-traffic deployments.
cpu_limit (container_resources.cpu_limit)"2000m""1000m"Directus benefits from 2 vCPU for responsive API generation.
memory_limit (container_resources.memory_limit)"2Gi""512Mi"Directus requires at minimum 512Mi; 2Gi is recommended for production.
enable_nfstruetrueShared NFS storage is used for Directus uploaded assets and media.
enable_cloudsql_volumetruetrueCloud SQL Auth Proxy is required for Directus database connectivity.
database_type"POSTGRES_15""POSTGRES"Directus is optimised for PostgreSQL 15; pinning the version ensures consistency.
enable_postgres_extensionstruefalseDirectus requires the uuid-ossp extension and benefits from PostGIS.
postgres_extensions["uuid-ossp"][]uuid-ossp is required for Directus UUID generation. Add postgis for geospatial features.
enable_redistruetrueDirectus uses Redis for caching and rate limiting. See Redis Cache below.
enable_pod_disruption_budgettruetrueEnsures availability during node maintenance.

Variable Name Differences

Several variables in Directus GKE use different names from their App GKE equivalents. This is intentional — the Directus module exposes a simplified interface focused on Directus semantics. The mapping is:

Directus GKE VariableApp GKE EquivalentNotes
db_nameapplication_database_nameName of the database created within Cloud SQL. Default: "directus". Passed to App GKE as application_database_name.
db_userapplication_database_userUsername of the database user. Default: "directus". Passed to App GKE as application_database_user.
descriptionapplication_descriptionBrief description passed to App GKE as application_description. Note: Directus GKE also exposes application_description as a separate variable (default "Directus CMS on GKE Autopilot") which is overridden by description at the App GKE call site — use description to control this field.
cpu_limitcontainer_resources.cpu_limitTop-level convenience variable; overrides the cpu_limit field of container_resources. Default: "2000m".
memory_limitcontainer_resources.memory_limitTop-level convenience variable; overrides the memory_limit field of container_resources. Default: "2Gi".
startup_probe(no direct equivalent)Directus-specific probe variable passed to Directus Common. Does not override startup_probe_config, which is passed directly to App GKE. See Runtime Configuration.
liveness_probe(no direct equivalent)Directus-specific probe variable passed to Directus Common. Does not override health_check_config, which is passed directly to App GKE. See Runtime Configuration.
backup_uribackup_file (App GKE parameter)URI of the backup file to import. For GCS: gs://bucket/path/file.sql. For Google Drive: the file ID. Note: Directus GKE also exposes a backup_file variable (default "backup.sql") which is a legacy field — use backup_uri instead.

Resource Creator Identity

resource_creator_identity is a module-metadata variable present in Directus GKE that is not documented in the App GKE Configuration Guide. It controls which service account Terraform impersonates when provisioning GCP resources in the target project.

VariableDefaultOptions / FormatDescription & Implications
resource_creator_identity"rad-module-creator@tec-rad-ui-2b65.iam.gserviceaccount.com"Service account email addressThe service account Terraform impersonates when creating and managing GCP resources in the target project. This account must hold the Owner role (ideally time-limited and conditional) in the destination project. For production deployments, replace this with a project-scoped service account granted only the minimum permissions required by this module. Setting this to an empty string ("") disables impersonation and Terraform uses the executor's credentials directly.

Validating Resource Creator Identity:

Google Cloud Console: Navigate to IAM & Admin → IAM and filter by the service account email to confirm it exists and holds the expected roles in the project.

gcloud CLI:

# List IAM roles held by the resource creator service account
gcloud projects get-iam-policy PROJECT_ID \
--flatten="bindings[].members" \
--filter="bindings.members:serviceAccount:SERVICE_ACCOUNT_EMAIL" \
--format="table(bindings.role)"

Project & Identity

All variables described in App_GKE.md §2 IAM & Access Control apply to Directus_GKE unchanged. In addition, Directus_GKE exposes the following variables that have no equivalent in App_GKE:

VariableDefaultOptions / FormatDescription & Implications
deployment_id""Short alphanumeric string (e.g. "a1b2c3")Short alphanumeric identifier appended to all resource names. Auto-generated when empty; set explicitly to pin a stable suffix across Terraform runs.
region"us-central1"GCP region identifier (e.g. "us-central1", "europe-west1")Fallback GCP region used when the module's network discovery routine cannot determine the deployment region from existing VPC subnets in the project. The module inspects subnet configurations at apply time and derives the region automatically in most environments. Set this explicitly when (1) the project has no pre-existing subnets and Services GCP has not yet been deployed, or (2) the default "us-central1" does not match the intended deployment target. If network discovery succeeds, the discovered region takes precedence over this value.
network_name""VPC network nameDefined in the module for interface compatibility but not forwarded to App GKE — the module hardcodes network discovery. Not referenced — setting this variable has no effect on deployment.
prereq_gke_subnet_cidr"10.201.0.0/24"CIDR stringDefined in the module for interface compatibility but not forwarded to App GKE. Not referenced — setting this variable has no effect on deployment.

Validating Deployment Region:

# Confirm the GKE cluster was created in the expected region
gcloud container clusters list --project=PROJECT_ID \
--format="table(name,location,status)"

# Confirm the Cloud SQL instance is in the expected region
gcloud sql instances list --project=PROJECT_ID \
--format="table(name,region,state)"

Application & Database Identity

The Directus_GKE module extends the App_GKE §3.A Compute (GKE Autopilot) with two additional variables that set the Directus database identity. These map directly to application_database_name and application_database_user in the underlying App_GKE module.

VariableDefaultOptions / FormatDescription & Implications
db_name"directus"[a-z][a-z0-9_]{0,62}Name of the PostgreSQL database created within the Cloud SQL instance. Injected into the Directus container as the DB_DATABASE environment variable. Do not change after initial deployment — renaming requires manual data migration.
db_user"directus"[a-z][a-z0-9_]{0,31}Username of the database user created for the Directus application. Injected as the DB_USER environment variable. The password is auto-generated, stored in Secret Manager, and injected as DB_PASSWORD.

For all other application identity variables (application_name, application_display_name, application_version, deploy_application), see App_GKE.md §3.A.


Runtime Configuration

CPU and Memory

Rather than nesting resource settings inside the container_resources object, Directus GKE exposes cpu_limit and memory_limit as top-level variables for convenience. These override the corresponding fields of container_resources.

VariableDefaultOptions / FormatDescription & Implications
cpu_limit"2000m"Kubernetes CPU string (e.g. "1000m", "2")Maximum CPU allocated to each Directus pod. 2000m (2 vCPU) is recommended for production to ensure responsive API generation. For container_resources.cpu_request, use the container_resources variable directly.
memory_limit"2Gi"Kubernetes memory string (e.g. "1Gi", "4Gi")Maximum memory allocated to each Directus pod. 2Gi is the recommended minimum for production. Directus loads schema definitions and extension metadata into memory; larger deployments with many collections should increase this value.

For full resource configuration including cpu_request, mem_request, and ephemeral_storage_limit, use the container_resources variable as documented in App_GKE.md §3.A.

Health Probes

Directus GKE exposes two separate sets of probe variables with different routing:

  • startup_probe / liveness_probe — Directus-specific variables passed to Directus Common, which uses them to configure the Directus container probe spec. Both target /server/health with extended timeouts suited to Node.js/DB startup.
  • startup_probe_config / health_check_config — App GKE-standard variables passed directly to App GKE. Also default to /server/health in Directus GKE but use App GKE's standard timeout defaults.

In practice, use startup_probe and liveness_probe to tune Directus probe behaviour. The startup_probe_config / health_check_config variables are available for compatibility but are not the primary probe path for the Directus container.

VariableDefaultDescription & Implications
startup_probeSee belowConfigures the Kubernetes startup probe used to determine when a newly started Directus pod is ready to receive traffic. GKE will not route requests to the pod until this probe succeeds.
liveness_probeSee belowConfigures the Kubernetes liveness probe that periodically checks whether a running Directus pod remains healthy. A pod is restarted if this probe fails failure_threshold consecutive times.

startup_probe default:

startup_probe = {
enabled = true
type = "HTTP"
path = "/server/health"
initial_delay_seconds = 0
timeout_seconds = 10
period_seconds = 30
failure_threshold = 10 # Allows up to 300 seconds for Directus to start
}

The high failure_threshold (10 × 30s = 300 seconds) accommodates Directus startup, which includes database migration checks, extension loading, and schema caching. Reduce only if your deployment consistently starts within a shorter window.

liveness_probe default:

liveness_probe = {
enabled = true
type = "HTTP"
path = "/server/health"
initial_delay_seconds = 60
timeout_seconds = 5
period_seconds = 30
failure_threshold = 3
}

The initial_delay_seconds = 60 gives Directus time to complete startup before liveness checks begin. After startup, a pod is considered unhealthy and restarted if /server/health fails three consecutive times within 90 seconds.

For the structured probe configuration variables (startup_probe_config, health_check_config, uptime_check_config, alert_policies), see App_GKE.md §3.A.

Validating Runtime Configuration

# View resource requests, limits, and probe configuration on the Deployment
kubectl describe deployment DIRECTUS_APP_NAME -n NAMESPACE

# Check startup probe status on a running pod
kubectl describe pod POD_NAME -n NAMESPACE | grep -A 10 "Startup:"

# Check liveness probe status on a running pod
kubectl describe pod POD_NAME -n NAMESPACE | grep -A 10 "Liveness:"

# Manually verify the Directus health endpoint
kubectl exec -n NAMESPACE POD_NAME -- curl -sf http://localhost:8055/server/health

Database Configuration

Directus_GKE applies the following defaults that differ from those in App_GKE. All other database variables (sql_instance_name, sql_instance_base_name, database_password_length, enable_auto_password_rotation, rotation_propagation_delay_sec) are exposed and forwarded to App_GKE with the same names and behaviour as documented in App_GKE.md §3.B.

VariableDirectus GKE DefaultApp GKE DefaultNotes
database_type"POSTGRES_15""POSTGRES"Directus is tested and optimised against PostgreSQL. The version is pinned to POSTGRES_15 for production consistency. MySQL deployments are supported but PostGIS and some extensions require PostgreSQL.
db_name"directus""gkeappdb"See Application & Database Identity.
db_user"directus""gkeappuser"See Application & Database Identity.
enable_postgres_extensionstruefalseEnabled by default so that uuid-ossp is installed automatically.
postgres_extensions["uuid-ossp"][]uuid-ossp is required by Directus for UUID primary keys. Add "postgis" to enable geospatial support for Directus geo fields.

For the full description of enable_postgres_extensions, postgres_extensions, enable_mysql_plugins, and mysql_plugins, see App_GKE.md §3.B.


Redis Cache

Directus_GKE exposes Redis configuration as first-class variables. Redis is used by Directus for API response caching and rate limiting, and is enabled by default. For the App_GKE-level Redis documentation, see App_GKE.md §8.A. The underlying App_GKE module accepts these variables and injects REDIS_HOST, REDIS_PORT, and optionally REDIS_AUTH as environment variables into the Directus pod.

When enable_redis is true and redis_host is left blank, the module defaults to using the NFS server's IP address as the Redis host. This works when a Redis-compatible service (such as Redis installed on a shared NFS VM provisioned by Services GCP) is co-located on that same host. For dedicated Redis instances (e.g. Cloud Memorystore), set redis_host explicitly.

VariableDefaultOptions / FormatDescription & Implications
enable_redistruetrue / falseWhen true, injects REDIS_HOST and REDIS_PORT environment variables into the Directus pod, and injects REDIS_AUTH when redis_auth is set. Directus uses these to connect to its caching and rate-limiting backend. When false, Directus falls back to in-memory caching, which does not persist across pod restarts and is not shared between replicas — not suitable for deployments with max_instance_count > 1.
redis_host"" (defaults to NFS server IP)IP address or hostnameThe hostname or IP address of the Redis server, injected as REDIS_HOST. Leave blank to fall back to the NFS server IP (suitable for single-VM Services GCP environments where Redis runs on the NFS host). Set explicitly when using a dedicated instance such as Cloud Memorystore for Redis — use the instance's private IP, found at Memorystore → Redis → instance → Primary endpoint. The GKE pods reach this host via the VPC network; ensure firewall rules permit TCP traffic from the GKE node CIDR to the Redis instance on redis_port.
redis_port"6379"Port number as stringThe TCP port the Redis server listens on, injected as REDIS_PORT. The default 6379 is correct for both self-hosted Redis and Cloud Memorystore. Change only if your instance uses a non-standard port.
redis_auth"" (no authentication)Password string (sensitive)Authentication password for the Redis server. When set, this value is stored in Secret Manager and injected securely as REDIS_AUTH — it is never stored in plaintext. Leave empty for development environments or instances accessible only within a private VPC where auth is not required. For production deployments using Cloud Memorystore with AUTH enabled, set this to the instance's auth string (found at Memorystore → Redis → instance → AUTH string). Enabling AUTH is strongly recommended for any Redis instance accessible over a network.

Validating Redis Configuration

Google Cloud Console:

  • Memorystore Redis instance: Navigate to Memorystore → Redis to confirm the instance exists, its IP address, port, and AUTH status.
  • Redis environment variables on pods: Navigate to Kubernetes Engine → Workloads → your workload, click a pod, and select Environment to confirm REDIS_HOST and REDIS_PORT are present.

gcloud CLI / kubectl:

# List Cloud Memorystore Redis instances
gcloud redis instances list \
--region=REGION \
--project=PROJECT_ID \
--format="table(name,host,port,tier,memorySizeGb,state,authEnabled)"

# Describe a specific Memorystore instance
gcloud redis instances describe INSTANCE_NAME \
--region=REGION \
--project=PROJECT_ID \
--format="yaml(host,port,authEnabled,state)"

# Confirm REDIS_HOST and REDIS_PORT are injected into the Directus pod
kubectl exec -n NAMESPACE POD_NAME -- env | grep REDIS

# Test Redis connectivity from within the cluster (requires redis-cli in the pod)
# kubectl exec -n NAMESPACE POD_NAME -- redis-cli -h REDIS_HOST -p 6379 ping

StatefulSet Persistent Storage

When workload_type is set to "StatefulSet" (see App_GKE.md §3.A), Directus_GKE exposes additional variables to configure a PersistentVolumeClaim (PVC) for each StatefulSet pod. This provides each pod with its own dedicated persistent storage, independent of the shared NFS volume.

Note: StatefulSet PVCs are appropriate when each Directus replica needs its own local state that must survive pod restarts — for example when running Directus extensions that write to local disk. For shared asset storage across replicas, use enable_nfs = true (the default) or gcs_volumes instead. If workload_type is "Deployment" (the default), all StatefulSet PVC variables are ignored.

VariableDefaultOptions / FormatDescription & Implications
stateful_pvc_enabledfalsetrue / falseWhen true, a PersistentVolumeClaim is created for each StatefulSet pod using the storage class and size defined below. Each pod receives its own dedicated PVC — data is not shared between pods. Only relevant when workload_type = "StatefulSet".
stateful_pvc_size"10Gi"Kubernetes storage quantity string (e.g. "10Gi", "50Gi")The capacity of the PVC provisioned for each pod. Size based on the local data the pod must persist — for example, Directus extension caches or local database files. Storage costs are incurred per pod, so max_instance_count × stateful_pvc_size is the total storage consumption.
stateful_pvc_mount_path"/data"Filesystem pathThe path inside each pod's container where the PVC is mounted. The Directus application (or any extensions) must be configured to write persistent data to this path. Files written outside this path are ephemeral and lost when the pod is replaced.
stateful_pvc_storage_class"standard-rwo"Kubernetes StorageClass nameThe storage class used to provision each PVC. "standard-rwo" is a GKE Autopilot standard class that provisions a regional SSD PersistentDisk with ReadWriteOnce access (only one pod can mount the volume at a time). Alternative classes: "premium-rwo" (higher IOPS SSD), "standard" (zonal standard disk). List available storage classes with kubectl get storageclasses.
stateful_headless_servicetruetrue / falseWhen true, creates a headless Kubernetes Service alongside the StatefulSet. A headless Service gives each pod a stable DNS name of the form POD_NAME.SERVICE_NAME.NAMESPACE.svc.cluster.local, enabling pod-level DNS discovery. This is the standard configuration for StatefulSets and is required if Directus extensions or other services need to address individual pod replicas by name.
stateful_pod_management_policy"OrderedReady"OrderedReady / ParallelControls the order in which StatefulSet pods are created and deleted. OrderedReady (default): pods are started and stopped sequentially — pod N must be Running and Ready before pod N+1 starts. Use for stateful applications where startup order matters, such as database replicas with a primary-replica topology. Parallel: all pods are started or stopped simultaneously without waiting for each other. Use when startup order does not matter and faster scaling is preferred.
stateful_update_strategy"RollingUpdate"RollingUpdate / OnDeleteControls how StatefulSet pods are updated when the pod template changes. RollingUpdate (default): pods are updated one at a time in reverse ordinal order, waiting for each pod to become Ready before continuing. This provides zero-downtime updates. OnDelete: pods are only updated when manually deleted — the controller does not replace them automatically. Use OnDelete when you need full manual control over when individual pods are replaced.

Configuration Pitfalls & Sensible Defaults

The table below identifies the variables most commonly misconfigured in Directus GKE deployments, explains the sensible starting value, and describes exactly what happens when the value is wrong.

Risk levels: Critical (data loss, full outage, security breach) — High (service unavailable or significant degradation) — Medium (degraded function or increased cost) — Low (minor impact).

VariableSensible DefaultRiskConsequence of Incorrect Value
application_name"directus" (default; do not change after first deploy)CriticalEmbedded in GKE namespace name, Artifact Registry repo, and Secret Manager secret IDs. Changing recreates all named resources — existing KEY and SECRET values are replaced, invalidating all user sessions, access tokens, and signed file URLs.
tenant_deployment_idMatch environment: "prod", "staging", "dev"CriticalChanging after first deploy orphans the old Cloud SQL instance. A new empty database and fresh KEY/SECRET pair are generated — all existing user sessions and API integrations are invalidated.
KEY (generated secret)Auto-generated 32-character random string stored in Secret ManagerCriticalRotating the KEY after first deploy: all active user sessions and access tokens are immediately invalidated. All users are logged out. Never rotate without a planned maintenance window and client notification.
SECRET (generated secret)Auto-generated 32-character random string stored in Secret ManagerCriticalRotating SECRET after first deploy: all JWT tokens issued to API clients become invalid. Third-party integrations fail with HTTP 401 until tokens are re-issued. Never rotate without updating all dependent API clients first.
workload_typenull (auto-selects Deployment)CriticalSetting "StatefulSet" without stateful_pvc_enabled = true creates a StatefulSet with no stable storage per pod. Setting "Deployment" alongside stateful_pvc_enabled = true fails at plan time. For most Directus deployments, keep null (Deployment) and use enable_nfs = true for shared assets.
quota_memory_requests"4Gi" (use binary suffix)CriticalA bare integer like "4" is treated as 4 bytes by Kubernetes. The ResourceQuota rejects every pod that requests more than 4 bytes of memory — all pods fail to schedule permanently. Always use "4Gi" or "4096Mi".
quota_memory_limits"8Gi" (must be ≥ quota_memory_requests)CriticalSame bare-integer issue. A value of "8" = 8 bytes, blocking all pod scheduling.
database_type"POSTGRES_15" (default; Directus requires PostgreSQL for full feature support)CriticalChanging to MYSQL after first deploy: a new empty MySQL instance is provisioned; all data remains in the orphaned PostgreSQL instance. Directus schema (JSONB, UUID primary keys) is incompatible with MySQL.
ADMIN_EMAIL"admin@example.com" (hardcoded default) — must be overridden via environment_variablesHighLeft as default: the Directus admin account is created with a guessable email. Override with a real email before first deploy using environment_variables = { ADMIN_EMAIL = "you@example.com" }.
enable_redisfalse (default); set true for production multi-replica deploymentsHighfalse with max_instance_count > 1: each pod maintains its own in-process cache. Cache inconsistency between pods causes stale API responses. Directus rate-limiting is also per-pod — clients can bypass rate limits by hitting different replicas. Enable Redis for any multi-replica deployment.
REDIS (secret env var)Full Redis URL: redis://:<password>@<host>:<port>HighURL omits authentication password when Redis requires it: AUTH fails and Directus falls back to in-process cache. Rate-limiting breaks. Always include the password in the URL if the Redis instance has auth enabled.
enable_nfstrue (default; required for shared upload storage across replicas)Highfalse with max_instance_count > 1 and local storage driver: uploaded files on one pod's ephemeral disk are invisible to other pods. Users see 404 for assets uploaded by a different replica. All local files are lost on pod restart. Use STORAGE_GCS_DRIVER = "gcs" (the default) to avoid this — NFS is only required for non-GCS workloads.
nfs_mount_path"/mnt/nfs" (default) — must match any local storage configurationHighMismatch: Directus writes local files to an ephemeral path instead of the shared NFS volume. Files are lost on pod restart.
stateful_pvc_size"10Gi" (default) — provision based on expected local data volumeHighPVC storage cannot be decreased after provisioning. Too small: Directus extension cache or local DB files fill the disk, causing I/O errors and pod crashes. Total storage = stateful_pvc_size × max_instance_count.
container_resources{ cpu_limit = "1000m", memory_limit = "512Mi" } minimum; 1024Mi recommendedHighMemory too low: Directus is OOMKilled when loading complex collection schemas or processing image transformations. GKE Autopilot enforces a minimum of 512Mi — increase for production workloads.
min_instance_count1 for production (eliminates cold starts on GKE)Medium0 in production: GKE scales Directus pods to zero when idle. Cold starts (image pull + DB schema load) take 20–40 s. Users experience long delays on first API request after an idle period.
enable_pod_disruption_budgetfalse (default; safe at replica count 1)Hightrue with max_instance_count = 1 and pdb_min_available = "1": GKE node drains are permanently blocked. Autopilot maintenance windows cannot complete. Only enable when min_instance_count ≥ 2.
startup_probe{ path = "/server/ping", failure_threshold = 30, period_seconds = 10 }Criticalfailure_threshold too low on first deploy: Directus runs schema migrations that can take 2–3 minutes on a fresh database. Kubernetes kills the pod before migrations complete, causing a restart loop. Increase failure_threshold to 40 if first-deploy timeouts occur.
liveness_probe{ path = "/server/ping", period_seconds = 30, failure_threshold = 3 } — must be fastHighLiveness endpoint that touches the DB: if the DB is slow, all healthy Directus pods are restarted simultaneously by the probe. Use "/server/ping" which Directus resolves without a synchronous DB query.
binauthz_evaluation_mode"ALWAYS_ALLOW" until CI pipeline attests imagesCritical"REQUIRE_ATTESTATION" without a working attestation pipeline: no new Directus image can be deployed to GKE, and rollbacks also fail.
enable_vpc_scfalse until VPC-SC perimeter exists; use vpc_sc_dry_run = true firstCriticalenable_vpc_sc = true with vpc_sc_dry_run = false: if the Directus GKE SA is missing from the VPC-SC access level, Cloud SQL, Secret Manager, and Artifact Registry calls all fail simultaneously.
enable_audit_loggingfalse for dev; true for production environments handling personal dataLowfalse in production: reads of KEY, SECRET, and ADMIN_PASSWORD secrets are not logged. Compliance frameworks may require access logs for cryptographic keys.

Validating StatefulSet Configuration

Google Cloud Console:

  • StatefulSet workload: Navigate to Kubernetes Engine → Workloads — the workload type column should show StatefulSet rather than Deployment.
  • PersistentVolumeClaims: Navigate to Kubernetes Engine → Config & Storage → Storage to confirm individual PVCs have been created per pod (named PVC_NAME-POD_NAME).
  • Headless Service: Navigate to Kubernetes Engine → Services & Ingress and confirm a Service with ClusterIP: None exists for the StatefulSet.

kubectl:

# Confirm the workload is a StatefulSet and view its status
kubectl get statefulsets -n NAMESPACE -o wide

# View individual pod PVCs (one per pod)
kubectl get pvc -n NAMESPACE

# Describe a PVC to confirm it is bound and view its storage class
kubectl describe pvc PVC_NAME -n NAMESPACE

# Confirm the headless Service exists (ClusterIP should be None)
kubectl get service -n NAMESPACE -o wide

# Verify stable pod DNS names (from within the cluster)
# nslookup POD_NAME.SERVICE_NAME.NAMESPACE.svc.cluster.local