mz-terraform-self-managed

v2026.09.25

Terraform modules for deploying self-managed Materialize on AWS (EKS), Azure (AKS), and GCP (GKE). Covers networking, Kubernetes clusters, managed PostgreSQL, object storage, the Materialize operator, and Materialize instance configuration. Use this skill whenever the user asks about deploying self-managed Materialize with Terraform, setting up Materialize infrastructure on any cloud, configuring EKS/AKS/GKE for Materialize, troubleshooting a self-managed deployment, upgrading Materialize instances, customizing Terraform variables for Materialize, or working with the materialize-terraform-self-managed repository. Also trigger when the user mentions Karpenter node pools for Materialize, persist or metadata backend URLs, the Materialize operator Helm chart, CRD versions (v1alpha1 vs v1), rollout strategies, or NVMe swap configuration for Materialize nodes.

GitHub
Install command
npx skhub add materializeinc/mz-terraform-self-managed
Markdown
SKILL.md

Materialize Terraform Self-Managed Modules

Production-ready Terraform modules for deploying Materialize on AWS, Azure, and GCP, maintained in the materialize-terraform-self-managed repository. Each cloud provider has its own set of modules plus shared Kubernetes modules for the operator and Materialize instance.

All file paths in this skill refer to that repository. When you need the actual module code, clone the repository or fetch individual files from GitHub.

Repository Layout

aws/
  modules/         # AWS-specific infra modules
  examples/
    simple/        # Minimal production-like deployment
    enterprise/    # Adds Ory (Kratos + Hydra) for auth
azure/
  modules/         # Azure-specific infra modules
  examples/
    simple/
    enterprise/
gcp/
  modules/         # GCP-specific infra modules
  examples/
    simple/
    enterprise/
kubernetes/
  modules/         # Cloud-agnostic K8s modules (operator, instance, cert-manager)
test/              # Rust-based integration test harness
scripts/           # Migration and utility scripts

How to Use This Skill

  1. User wants to deploy Materialize on a specific cloud: Start with the relevant <cloud>/examples/simple/ directory in the repository. Read main.tf and variables.tf there for the full working configuration.
  2. User asks about a specific module: Read <cloud>/modules/<name>/variables.tf and main.tf for that module.
  3. User wants to customize the instance: Read kubernetes/modules/materialize-instance/variables.tf for all instance-level options.
  4. User asks about upgrades or rollouts: See the Upgrades section below and kubernetes/modules/materialize-instance/variables.tf for rollout config.
  5. User wants enterprise/auth setup: Read <cloud>/examples/enterprise/.
  6. User has an existing Terraform project: See "Using the Modules from an Existing Terraform Project" below.

Architecture Layers

Every deployment creates two layers:

Cloud infrastructure (cloud-specific modules):

  • Networking: VPC/VNet, subnets, NAT, security groups
  • Kubernetes: EKS/AKS/GKE cluster with autoscaling
  • Database: Managed PostgreSQL for Materialize metadata
  • Storage: Object storage (S3/Blob/GCS) for persist layer

Kubernetes application (shared modules under kubernetes/modules/):

  • cert-manager for TLS certificates
  • Materialize Operator (Helm chart)
  • Materialize Instance (custom resource)

Cloud Provider Modules

AWS (aws/modules/)

ModulePurpose
networkingVPC, subnets, NAT gateways, security groups
eksEKS cluster with OIDC provider
eks-node-groupManaged node groups for base workloads
karpenterKarpenter autoscaler controller
karpenter-ec2nodeclassEC2NodeClass for Karpenter provisioning
karpenter-nodepoolNodePool for Karpenter scheduling
databaseRDS PostgreSQL for metadata
storageS3 bucket with IRSA
aws-lbcAWS Load Balancer Controller
nlbNetwork Load Balancer
operatorMaterialize operator (Helm)
ebs-csi-driverEBS CSI driver
vpc-cniVPC CNI with network policy support

Key AWS patterns:

  • Node autoscaling via Karpenter (not cluster autoscaler)
  • Two Karpenter node classes: generic (t4g.xlarge) and Materialize (r8gd.2xlarge with NVMe swap)
  • Storage auth via IRSA (IAM Roles for Service Accounts)
  • NLB for external access on ports 6875 (SQL), 6876 (HTTP), 8080 (console)

Azure (azure/modules/)

ModulePurpose
networkingVNet, subnets, NAT gateway
aksAKS cluster with Cilium networking
nodepoolAdditional AKS node pools
databasePostgreSQL Flexible Server
storageStorage Account with workload identity
load_balancersAzure Load Balancers
operatorMaterialize operator (Helm)

Key Azure patterns:

  • Cilium for networking and network policies
  • Workload Identity Federation for storage auth (passwordless OIDC)
  • Standard_E4pds_v6 instances for Materialize nodes with swap
  • Private DNS zone for PostgreSQL resolution
  • Requires registering EnableAPIServerVnetIntegrationPreview feature

GCP (gcp/modules/)

ModulePurpose
networkingVPC, subnets, Cloud NAT
gkeGKE cluster with Workload Identity
nodepoolAdditional GKE node pools
databaseCloud SQL PostgreSQL
storageCloud Storage bucket with HMAC keys
load_balancersGCP Load Balancers
operatorMaterialize operator (Helm)

Key GCP patterns:

  • HMAC keys for S3-compatible GCS access (these modules use the S3-compatible API)
  • VPC peering for Cloud SQL private access
  • c4a-highmem-8-lssd instances for Materialize nodes with local SSD and swap
  • Secondary IP ranges for pods and services (VPC-native)
  • Requires enabling multiple GCP APIs (container, compute, sqladmin, servicenetworking, etc.)

Kubernetes Modules (kubernetes/modules/)

ModulePurpose
cert-managerInstalls cert-manager Helm chart
self-signed-cluster-issuerCreates self-signed ClusterIssuer
materialize-instanceDeploys Materialize CR
corednsCoreDNS configuration
grafanaGrafana monitoring
prometheusPrometheus monitoring stack
hpaHorizontal Pod Autoscaling
ory-*Ory stack (Kratos, Hydra, etc.) for enterprise auth

Materialize Instance Module

This is the most important Kubernetes module. Key variables in kubernetes/modules/materialize-instance/variables.tf:

VariableDefaultNotes
crd_versionv1alpha1Use v1 for v26.30+. See CRD section below.
instance_name(required)Name of the Materialize CR
instance_namespace(required)Namespace for the instance
metadata_backend_url(required)PostgreSQL connection string
persist_backend_url(required)Object storage URL
license_keynullCommunity or enterprise key
environmentd_versionv26.29.0Materialize version
rollout_strategyWaitUntilReadySee Rollout Strategies below
authenticator_kindNoneOptions: None, Password, Sasl, Oidc
cpu_request1CPU request for environmentd
memory_request4095MiMemory request for environmentd
memory_limit4GiMemory limit for environmentd
system_parameters{}Map of system config parameters
issuer_refnullcert-manager issuer for TLS
internal_issuer_refnullOverride for internal mTLS certs

Backend URL Formats

The Materialize instance needs two backend URLs:

Metadata backend (PostgreSQL):

postgres://user:password@host:5432/database?sslmode=require&options=-c%20statement_timeout%3D15min

The statement_timeout=15min parameter is required for metadata operations.

Persist backend (object storage):

AWS S3:

s3://bucket-name/system:serviceaccount:namespace:name

Azure Blob:

https://storageaccount.blob.core.windows.net/container

GCP Cloud Storage (via HMAC/S3-compatible API):

s3://hmac-access-id:hmac-secret@bucket-name/materialize?endpoint=https%3A%2F%2Fstorage.googleapis.com&region=us-central1

Storage authentication support: Materialize currently supports IAM-based authentication for the persist backend on AWS (IRSA) and Azure (Workload Identity Federation) only. Native GCS IAM auth is not supported, which is why the GCP modules use HMAC keys with the S3-compatible API.

CRD Versions: v1alpha1 vs v1

v1alpha1 (default, pre-v26.30): Two-step rollout. Change spec, then set request_rollout to a new UUID to trigger the rollout. Gives explicit control over timing.

v1 (recommended for v26.30+): Spec changes automatically trigger rollouts. The operator computes a hash and handles rollout. The request_rollout field is removed. Use force_rollout with a new UUID for manual triggers.

Set via crd_version variable in the example or the materialize-instance module.

Rollout Strategies

StrategyBehaviorResource impact
WaitUntilReadyNew pods created, cutover when healthyTemporarily doubles resources
ManuallyPromoteNew pods created, waits for manual forcePromoteTemporarily doubles resources
ImmediatelyPromoteCausingDowntimeOld pods torn down firstNo extra resources, causes downtime

Deploying: Quick Start

Each cloud follows the same pattern:

git clone https://github.com/MaterializeInc/materialize-terraform-self-managed.git
cd materialize-terraform-self-managed/<cloud>/examples/simple

# Create terraform.tfvars with required variables (see below)
terraform init
terraform apply

Variables by Cloud

AWS (aws/examples/simple/):

name_prefix = "my-mz"                # required
aws_profile = "my-profile"            # required
license_key = "your-license-key"      # required
tags        = { environment = "dev" } # required (no default)
aws_region  = "us-east-1"            # optional, defaults to us-east-1

Azure (azure/examples/simple/):

subscription_id     = "12345678-..."   # required
resource_group_name = "materialize-rg" # required
name_prefix         = "my-mz"         # required
tags                = { environment = "dev" } # required (no default)
location            = "westus2"        # optional, defaults to westus2
license_key         = "your-key"       # optional (null default), needed for production

GCP (gcp/examples/simple/):

project_id  = "my-gcp-project"        # required
labels      = { environment = "dev" }  # required (no default)
license_key = "your-license-key"      # optional (null default), needed for production
name_prefix = "my-mz"                # optional, defaults to "materialize"
region      = "us-central1"           # optional, defaults to us-central1

Common Optional Variables (all clouds)

VariableDefaultPurpose
internal_load_balancertrueSet false for internet-facing LB
ingress_cidr_blocks["0.0.0.0/0"]Restrict access to Materialize ports
k8s_apiserver_authorized_networksvariesRestrict K8s API access
crd_versionv1alpha1Use v1 for v26.30+
enable_observabilityfalseInstall Prometheus + Grafana

Using the Modules from an Existing Terraform Project

You do not need to clone the repository or copy the examples into your project. Reference modules directly with a Git source, pinning to a release tag or commit SHA:

module "materialize_instance" {
  source = "github.com/MaterializeInc/materialize-terraform-self-managed//kubernetes/modules/materialize-instance?ref=<tag-or-commit>"

  # module inputs ...
}
  • The // separates the repository URL from the module subdirectory. Any module in the repository can be referenced this way.
  • Always pin ref to a tag or commit SHA. Never track main, or applies become non-reproducible and can pick up breaking changes.
  • To update, first check the version notes for breaking changes between your current ref and the target, then bump the ref, run terraform init -upgrade, and review the plan before applying. See Upgrading Materialize for the full path.
  • Use the relevant <cloud>/examples/simple/main.tf as the reference for how the modules compose, then reproduce that composition in your own project with pinned Git sources. This is the pattern Materialize uses for its own internal deployments.

Post-Deployment Setup

Configure kubectl

After terraform apply, configure kubectl to talk to the new cluster:

AWS:

aws eks update-kubeconfig --name $(terraform output -raw eks_cluster_name) --region <region>

Azure:

az aks get-credentials --resource-group <rg> --name $(terraform output -raw aks_cluster_name)

GCP:

gcloud container clusters get-credentials $(terraform output -raw gke_cluster_name) --region <region>

Connecting to Materialize

Ports:

  • 6875: PostgreSQL-compatible SQL (pgwire)
  • 6876: HTTP API
  • 8080: Materialize Console (HTTPS)

With a public (internet-facing) load balancer (internal_load_balancer = false):

AWS (uses NLB DNS):

psql "postgres://mz_system@$(terraform output -raw nlb_dns_name):6875/materialize"
open "https://$(terraform output -raw nlb_dns_name):8080/materialize"

Azure / GCP (uses load balancer IPs):

psql "postgres://mz_system@$(terraform output -raw balancerd_load_balancer_ip):6875/materialize"
open "https://$(terraform output -raw console_load_balancer_ip):8080/materialize"

With a private (internal) load balancer (the default):

Use kubectl port-forwarding. The resource ID is in terraform output materialize_instance_resource_id:

kubectl port-forward svc/mz<resource-id>-balancerd 6875:6875 -n materialize-environment
psql "postgres://mz_system@localhost:6875/materialize"
kubectl port-forward svc/mz<resource-id>-console 8080:8080 -n materialize-environment
open "http://localhost:8080"

Use the external_login_password_mz_system output for the password when authentication is enabled. Create dedicated users after initial setup; avoid using mz_system for regular operations.

Observability

When enable_observability = true, Prometheus and Grafana are deployed in the monitoring namespace with pre-configured Materialize dashboards:

kubectl port-forward svc/grafana 3000:80 -n monitoring
# Username: admin, Password: terraform output -raw grafana_admin_password

Upgrading Materialize

  1. Check the version notes for the target version first. Some versions have breaking changes or special upgrade requirements that must be handled before bumping.
  2. Upgrade one minor version at a time for versions before v26. From v26+ you can skip minor versions.
  3. Downgrading is not supported.
  4. Upgrade order: operator first, then instances.

With Terraform: Update environmentd_version (and optionally operator_version) in your variables, then terraform apply. For v1alpha1, also update request_rollout to a new UUID.

For the full upgrade procedure, see the materialize-docs skill in this repository at skills/materialize-docs/self-managed-deployments/upgrading/index.md (with per-cloud guides in the sibling upgrade-on-* directories), or the online upgrading documentation. Always review the version notes for breaking changes before upgrading.

Instance Sizing

Materialize nodes should use memory-optimized instances with NVMe local storage for swap:

CloudInstance TypevCPUsMemoryMax cluster size
AWSr8gd.2xlarge864 GiB~300cc
AWSr8gd.4xlarge16128 GiB~600cc
AWSr8gd.8xlarge32256 GiB~1200cc
AWSr8gd.16xlarge64512 GiB~3200cc
AzureStandard_E4pds_v6432 GiBvaries
GCPc4a-highmem-8-lssd864 GiBvaries

ARM-based CPUs with a 1:8 vCPU-to-memory ratio and 8:1 local-storage-to-memory ratio are recommended.

Common Gotchas

  • ECR auth conflicts: If terraform apply fails with 403 pulling public images, run docker logout public.ecr.aws and retry.
  • GCP APIs: You must enable multiple GCP APIs before running Terraform (container, compute, sqladmin, servicenetworking, iamcredentials, iam, storage).
  • Azure preview feature: Register EnableAPIServerVnetIntegrationPreview before deploying on Azure.
  • Self-signed certs: The simple examples use self-signed TLS. For production, use a real CA or ACME issuer. When using a public ACME issuer (like Let's Encrypt), set internal_issuer_ref separately because public CAs cannot sign *.cluster.local names.
  • statement_timeout: The metadata backend URL must include statement_timeout=15min or metadata operations may time out.
  • Console slow loads: If the Console UI is slow, increase mz_catalog_server cluster size from 25cc to 50cc via internal SQL port 6877.

Troubleshooting

Check operator status:

kubectl -n materialize get all
kubectl -n materialize logs -l app.kubernetes.io/name=materialize-operator

Check Materialize instance:

kubectl -n materialize-environment get all
kubectl -n materialize-environment logs <pod-name>
kubectl -n materialize-environment describe pod/<pod-name>

Check Materialize CR status:

kubectl get materialize -n materialize-environment -o jsonpath='{.items[0].status}'

The CR status should show UpToDate when healthy.

Testing

The test harness in test/ uses Rust and runs full lifecycle tests (init, apply, verify, destroy):

cd test
cargo run -- run aws --owner "Name" --license-key-file key.txt \
  --aws-region us-east-1 --aws-profile my-profile

Verification checks: Materialize CR status is UpToDate, all pods (environmentd, console, balancerd, clusterd) are Running, and SELECT 1 succeeds over SQL.

Keeping This Skill Up to Date

The verified-against value in the frontmatter metadata records the upstream commit this skill was last verified against. To refresh the skill:

  1. Diff the upstream repository from that commit to current main, focusing on variables.tf files, example configurations, and READMEs.
  2. Update the affected sections here (variable defaults, module tables, instance types, gotchas).
  3. Bump verified-against to the new commit SHA.
Discovery
Tags

No tags published for this skill.

Version
Latest version metadata

Version

v2026.09.25

Published

Sep 25, 2026

Category

Uncategorized

License

NOASSERTION

Source path

skills/mz-terraform-self-managed

Default branch

main

Latest commit

820acbd

Tree SHA

0801eb3