Skip to main content

Terraform Deployment

Deploy Immich Memories to Kubernetes using Terraform. The module lives in deploy/terraform/ and uses the hashicorp/kubernetes provider. CPU only by default; NVIDIA GPU scheduling is a variable.

Less travelled than Docker Compose

Docker Compose is the primary self-hosting path. What the test suite pins is the module's contract: writable state volume, /health/live + /health/ready probes, gpu_enabled = false by default, and that the examples only set variables the module declares. Nothing runs terraform validate, and nothing applies the module to a live cluster. Read the plan before you apply it, and open an issue if something does not boot.

Before enabling Ingress

Authentication is disabled by default. An enabled Ingress exposes the UI to every client that can reach it, so configure authentication first (secret_env with IMMICH_MEMORIES_AUTH_USERNAME / IMMICH_MEMORIES_AUTH_PASSWORD, or OIDC). The UI is single-user, single-replica; do not scale the deployment beyond one pod.

What it creates

Namespace (optional), Secret, two ReadWriteOnce PVCs, Deployment, Service, Ingress (optional).

The image runs as user immich, UID/GID 1000, HOME=/home/immich (run_as_user / fs_group 1000, all capabilities dropped, RuntimeDefault seccomp, read_only_root_filesystem = true). These three mounts are the only writable paths:

MountBacked byHolds
/home/immich/.immich-memoriescache PVC (writable)config.yaml, cache.db (analysis scores), video cache, projects, automation history
/app/outputoutput PVCgenerated videos (IMMICH_MEMORIES_OUTPUT__DIRECTORY=/app/output)
/tmpemptyDir (tmp_size, 4Gi)FFmpeg intermediates — 8Gi for 4K

There is no ConfigMap. immich_url / immich_api_key (plus llm_api_key, musicgen_api_key and anything in secret_env) land in the Secret and reach the pod through envFrom; every other setting is an IMMICH_MEMORIES_<SECTION>__<KEY> env var (env). Settings saved from the UI go to config.yaml on the PVC; env vars override them.

Probes: /health/live (liveness) and /health/ready (readiness — 503 until config is present and Immich answers, which keeps the pod out of the Service while Immich is down).

Prerequisites

  1. Terraform >= 1.0 and the hashicorp/kubernetes provider >= 2.20
  2. Kubernetes cluster with a storage class for PVCs and Immich reachable from it (port 2283 by default). For gpu_enabled = true: NVIDIA GPU Operator and the nvidia RuntimeClass
  3. kubeconfig configured and pointing at your cluster

Quick Start

cd deploy/terraform/examples/basic        # CPU, no ingress, port-forward
# or: cd deploy/terraform/examples/production # pinned tag, basic auth, ingress + TLS, GPU optional

cp terraform.tfvars.example terraform.tfvars
vim terraform.tfvars

terraform init
terraform plan
terraform apply

$(terraform output -raw port_forward_command) # http://localhost:8080

Module Usage

module "immich_memories" {
source = "path/to/deploy/terraform"

# Required
immich_url = "https://photos.example.com"
immich_api_key = var.immich_api_key

# Optional: LLM clip content analysis (any OpenAI-compatible API)
# Tested against Qwen3.6-27B and Qwen3.6-35B-A3B; `llm_model` is the tag the server serves
llm_base_url = "http://ollama.ollama.svc.cluster.local:11434/v1"
llm_model = "qwen3.6:27b"

# Optional: anything else, e.g. the in-pod daily automation
env = {
IMMICH_MEMORIES_AUTOMATION__ENABLED = "true"
IMMICH_MEMORIES_AUTOMATION__DAILY_AT = "09:00"
}

# Optional: NVIDIA GPU nodes
gpu_enabled = true

# Storage
output_storage_size = "100Gi"
cache_storage_size = "50Gi"
}

Variables

Required

NameDescriptionType
immich_urlURL of your Immich instancestring
immich_api_keyImmich API keystring

Deployment

NameDescriptionTypeDefault
namespaceKubernetes namespacestring"immich-memories"
create_namespaceCreate the namespacebooltrue
image_repositoryContainer imagestring"ghcr.io/sam-dumont/immich-video-memory-generator"
image_tagImage tag — no v prefix, so release vX.Y.Z is tag X.Y.Zstring"latest"
replicasReplica count — keep at 1, the UI is single-replicanumber1
resourcesRequests/limits object (requests.memory/cpu, limits.memory/cpu)object2Gi/1000m8Gi/4000m
tmp_size/tmp emptyDir for FFmpeg intermediates (8Gi for 4K)string"4Gi"
envExtra env vars, typically IMMICH_MEMORIES_<SECTION>__<KEY> (plain names like TZ work too)map(string){}
secret_envExtra env vars stored in the Secret (auth password, storage secret)map(string){}
labelsExtra labels on every resourcemap(string){}

GPU Configuration

NameDescriptionTypeDefault
gpu_enabledSchedule on NVIDIA GPU nodes (RuntimeClass, nvidia.com/gpu, node selector, toleration, NVIDIA_* env)boolfalse
gpu_countNumber of GPUs to requestnumber1
gpu_node_selectorNode selector for GPU nodesmap(string){"nvidia.com/gpu.present": "true"}
runtime_class_nameRuntimeClass for NVIDIAstring"nvidia"

Storage

NameDescriptionTypeDefault
output_storage_sizeSize of the output PVCstring"50Gi"
cache_storage_sizeSize of the cache/state PVCstring"20Gi"
storage_class_nameStorage class for PVCsstringnull (cluster default)

Ingress

NameDescriptionTypeDefault
ingress_enabledEnable ingressboolfalse
ingress_class_nameIngress classstring"nginx"
ingress_hostIngress hostnamestring"memories.example.com"
ingress_tls_enabledEnable TLSboolfalse
ingress_tls_secret_nameTLS secret namestring"immich-memories-tls"
ingress_annotationsExtra ingress annotationsmap(string){}

LLM and music

NameDescriptionTypeDefault
llm_base_urlOpenAI-compatible endpoint (Ollama: append /v1). Sets llm.base_url and turns on content_analysis.enabled; empty disables LLM analysisstring""
llm_modelVision model name served at llm_base_urlstring""
llm_api_keyAPI key for llm_base_url (stored in the Secret)string""
musicgen_enabledGenerate AI music with a MusicGen serverboolfalse
musicgen_base_urlMusicGen server URLstring"http://musicgen.musicgen.svc.cluster.local:8000"
musicgen_api_keyMusicGen API key (stored in the Secret)string""
output_resolutionVideo resolution (720p, 1080p, 4k)string"1080p"

Outputs

NameDescription
namespaceKubernetes namespace
service_nameService name for internal access
service_endpointInternal service endpoint (FQDN)
ingress_hostIngress hostname (if enabled)
port_forward_commandReady-to-run kubectl port-forward command
deployment_nameDeployment name
pvc_outputName of the output PVC
pvc_cacheName of the cache/state PVC
gpu_enabledWhether GPU support is enabled

Troubleshooting

# Pod events: scheduling, PVC binding, GPU
kubectl describe pod -n immich-memories -l app.kubernetes.io/name=immich-memories
kubectl get pvc -n immich-memories

# Readiness stays 503 until Immich answers — check the payload
kubectl port-forward -n immich-memories svc/immich-memories 8080:80
curl -s localhost:8080/health/ready

# GPU: operator pods, node label, RuntimeClass
kubectl get pods -n gpu-operator
kubectl get nodes -L nvidia.com/gpu.present
kubectl get runtimeclass nvidia

Common causes of a Pending pod: the storage class doesn't exist, resource requests exceed the cluster, or gpu_enabled = true without GPU nodes.