11 min read

Setting Up a Production-Ready Kubernetes Cluster with Terraform

Provision a secure, scalable Kubernetes cluster with Terraform, including networking, node pools, access control, and deployment considerations.

Featured image for "Setting Up a Production-Ready Kubernetes Cluster with Terraform"

Setting Up a Production-Ready Kubernetes Cluster with Terraform

Most Kubernetes tutorials stop right after the cluster comes up.

That’s fine for learning. It’s not fine for production.

A cluster that is merely working can still be fragile, expensive, difficult to secure, and painful to operate. Production readiness means more than provisioning control planes and worker nodes. It means designing for failure, keeping blast radius small, making access explicit, and ensuring the platform is maintainable by humans at 2 a.m.

Terraform is a strong foundation because it gives you repeatability and reviewability. But Terraform alone does not make infrastructure production-ready. The real work is in the architecture and operational discipline around it.

Info

Thesis: Terraform gives you deterministic provisioning, but production readiness comes from secure networking, least-privilege access, resilient node design, and a deployment model you can actually support over time.

In this guide, we’ll build a mental model for a production-grade Kubernetes environment and walk through the practical considerations that separate a demo cluster from a platform you’d trust with real workloads.

What “production-ready” actually means

A production-ready Kubernetes cluster should satisfy a few non-negotiable requirements:

  • Isolated networking with clear separation between public and private surfaces
  • Secure access control using IAM, RBAC, and secrets management
  • Scalable compute with node pools and autoscaling
  • Operational visibility through logging, metrics, and audit trails
  • Safe deployment paths using ingress, rollout strategies, and health checks
  • Repeatability so environments can be recreated, patched, and evolved consistently

If any of those are missing, you don’t really have a platform. You have a fragile collection of resources.

Baseline architecture: build the network before the cluster

The biggest mistake I see engineers make is creating the Kubernetes cluster first and deciding on networking later. That reverses the dependency chain.

Your cluster sits inside a network boundary, and that boundary controls everything from node placement to ingress exposure to private service connectivity. Whether you are on AWS, GCP, or Azure, the same core principles apply.

A sensible baseline layout

At minimum, plan for:

  • A dedicated VPC/VNet for the environment
  • Multiple subnets across availability zones
  • Private subnets for worker nodes and internal services
  • Public subnets only where internet-facing components must live
  • NAT or egress controls for outbound access from private workloads
  • Route tables and security groups / firewall rules that are intentionally scoped

For most teams, the right default is:

  • Managed Kubernetes control plane outside your direct node management
  • Worker nodes in private subnets
  • Ingress exposed through a load balancer or ingress controller
  • Datastores kept outside the cluster unless you have a strong reason otherwise
Warning

Watch Out: Putting worker nodes in public subnets because it is “simpler” often creates long-term security debt. It may work, but it expands your attack surface and makes access control harder to reason about.

Cluster topology matters

A single-node or single-AZ cluster is not production-ready, no matter how elegant the Terraform is. Production workloads need failure-domain awareness.

At a minimum, aim for:

  • Multiple availability zones
  • At least two worker pools if your workloads have different resource or isolation needs
  • Separation between system workloads and application workloads
  • Dedicated node pools for latency-sensitive, batch, or high-memory services

Here is the kind of topology I recommend most teams start with:

LayerRecommendationWhy it matters
VPC/VNetDedicated environment networkReduces coupling and accidental exposure
SubnetsMulti-AZ private subnets for nodesImproves resilience
IngressLoad balancer + ingress controllerCentralizes and standardizes exposure
Node poolsSeparate system and app poolsPrevents noisy-neighbor issues
Data servicesManaged databases and cachesLowers operational burden

Terraform structure: keep it boring and modular

Terraform projects become unmaintainable when they turn into a pile of copy-pasted resources. Production infrastructure needs structure.

The goal is not to make the repository look clever. The goal is to make changes safe, obvious, and reviewable.

A practical Terraform layout

A common layout is:

  • modules/ for reusable infrastructure components
  • envs/dev, envs/staging, envs/prod for environment-specific stacks
  • variables.tf for input definitions
  • outputs.tf for exported values
  • providers.tf for cloud provider and Kubernetes provider configuration
  • backend.tf for remote state setup

A simple example:

# envs/prod/main.tf
module "network" {
  source              = "../../modules/network"
  environment         = "prod"
  cidr_block          = "10.20.0.0/16"
  availability_zones  = ["us-east-1a", "us-east-1b", "us-east-1c"]
}

module "kubernetes" {
  source              = "../../modules/kubernetes"
  environment         = "prod"
  vpc_id              = module.network.vpc_id
  private_subnet_ids  = module.network.private_subnet_ids
  cluster_version     = "1.30"
  node_min_size       = 3
  node_desired_size   = 6
  node_max_size       = 12
}

This is not fancy, and that’s the point.

Use remote state from day one

State is not an implementation detail. It is a critical dependency.

Store Terraform state in a remote backend with locking enabled. If you are on AWS, that usually means S3 plus DynamoDB locking. On GCP or Azure, use the equivalent managed storage and locking mechanisms.

Remote state gives you:

  • Team collaboration
  • Consistent plan/apply workflows
  • State durability
  • Reduced risk of accidental local corruption
Tip

Pro Tip: Separate state by environment and by blast radius. A networking stack, cluster stack, and application stack do not always need to share the same state file.

Environment separation should be real, not just names

A lot of teams create dev, staging, and prod directories but still share too much underneath.

Good environment separation means:

  • Different state files
  • Different credentials or access paths
  • Different quotas and guardrails
  • Different approval policies
  • Different assumptions about cost and resiliency

If your staging environment can accidentally mutate production resources, your setup is not isolated enough.

Cluster security: least privilege is not optional

Kubernetes security is a multi-layer problem. Cloud IAM, Kubernetes RBAC, network policy, secrets, and audit logging all need to work together.

IAM and RBAC should be mapped intentionally

Do not hand out cluster-admin to everyone because it is convenient.

Use cloud IAM to control who can reach the cluster and Kubernetes RBAC to control what they can do once inside.

A healthy pattern is:

  • SRE/platform team: elevated operational access
  • Application teams: namespace-scoped permissions
  • CI/CD service accounts: tightly scoped deployment permissions
  • Break-glass access: rare, audited, and time-bound

The principle is simple: the fewer identities with broad permissions, the better.

Secrets require a real strategy

A production cluster should not rely on plaintext Kubernetes Secrets as its only control. Base64 encoding is not security.

Prefer one of these patterns:

  • External secrets manager integration
  • Short-lived credentials issued at runtime
  • Secret encryption at rest with cloud-managed keys
  • Namespace-scoped access with explicit ownership

Here is a practical rule: if a secret can be rotated without redeploying the entire platform, you are in a much better place.

Danger

Important: Never commit cloud credentials, database passwords, or API tokens into Terraform variables, Git history, or container images. If secrets are present in state, treat that state as sensitive data.

Enable audit logging early

Many teams defer audit logging until they have an incident. That is backwards.

You want logs for:

  • API server actions
  • Authentication and authorization events
  • Changes to RBAC and cluster-scoped resources
  • Admission decisions if you use policy engines

Without audit logs, investigations become guesswork.

Network controls still matter inside the cluster

Kubernetes namespaces are organizational boundaries, not security boundaries by themselves.

Use:

  • Network policies where supported
  • Security groups or firewall rules at the cloud layer
  • Pod security controls
  • Admission policies for risky workloads

The real goal is to make lateral movement expensive.

Scaling and resilience: design for failure, not hope

A production cluster should scale gracefully and survive common failure modes.

Use multiple node pools

Node pools let you match infrastructure to workload characteristics.

Typical pools include:

  • System pool for cluster add-ons and controllers
  • General app pool for standard services
  • Memory-optimized pool for caches or JVM services
  • Compute-optimized pool for CPU-bound workloads
  • Spot/preemptible pool for fault-tolerant batch jobs

Here’s how I think about the tradeoff:

Node Pool TypeCostReliabilityBest ForCaveat
On-demand generalMediumHighCore servicesLess cost efficient
Spot / preemptibleLowLowerBatch, async jobsCan be interrupted
Memory-optimizedHigherHighJVM, data processingCan be expensive
Dedicated systemMediumHighPlatform componentsNeeds careful sizing

Autoscaling is necessary, but not sufficient

Autoscaling is useful only if your workloads are requests/limits aware and your cluster has room to place new pods.

You typically need both:

  • Horizontal Pod Autoscaler for application-level scaling
  • Cluster autoscaler or equivalent for node-level scaling

And you need to set realistic resource requests. If every service asks for tiny requests and then bursts unpredictably, the scheduler will make bad decisions under load.

Plan upgrades deliberately

Kubernetes upgrades are normal maintenance, not exceptional events.

You should know:

  • How often control plane versions are supported
  • How node images are rotated
  • Whether add-ons are compatible with the target version
  • What the rollback path is if an upgrade exposes a bug

Never let the cluster drift so far behind that upgrading becomes a weekend rescue project.

Note

A good upgrade process is boring: read release notes, test in non-production, upgrade incrementally, and validate workloads before moving on. :::

Failure domains are a design feature

An availability zone is not just a placement option. It is a failure boundary.

Distribute replicas across zones, avoid single-zone dependencies, and verify that your load balancers, storage classes, and node pools actually honor the fault domain assumptions you made on paper.

Deployment considerations: how workloads reach users

A cluster can be perfectly provisioned and still be operationally awkward if the deployment model is weak.

Ingress should be standardized

Most teams should not expose services directly with arbitrary load balancers unless there is a strong reason.

A better approach is:

  • One or a small number of ingress controllers
  • Standard TLS termination
  • Consistent path or host-based routing
  • Centralized certificate management
  • Explicit timeouts and body-size limits

This creates a predictable surface for apps and makes security review easier.

Service exposure needs policy, not improvisation

Be explicit about which services are:

  • Internal only
  • Accessible only from other namespaces
  • Exposed publicly
  • Restricted to admin or partner traffic

Not every service deserves a public endpoint.

Rollout strategy is part of infrastructure design

Terraform provisions the platform, but the deployment strategy determines how safely you change applications on it.

Prefer rollout methods that reduce blast radius:

  • Rolling updates with readiness and liveness checks
  • Blue/green for high-risk changes
  • Canary when you can measure user impact well
  • Feature flags for behavioral changes

If your deployment process cannot catch a bad release before it reaches all users, the platform is not giving you enough protection.

Here is a simple deployment decision guide:

StrategyRiskSpeedOperational ComplexityUse When
RollingLow to mediumFastLowMost services
Blue/greenLowMediumMediumHigh-risk releases
CanaryLowestSlowerHigherYou have good metrics and traffic shaping
RecreateHighFastLowOnly for stateless non-critical systems

A deployment and provisioning flow that actually works

A healthy sequence looks like this:

This flow is intentionally conservative. Production infrastructure should optimize for correctness before convenience.

Practical Terraform patterns that reduce pain

Let’s get specific about a few Terraform habits that matter.

1. Pin provider and module versions

Use explicit version constraints so upgrades are intentional.

terraform {
  required_version = ">= 1.6.0"

  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
}

2. Keep sensitive values out of plan output when possible

Mark variables sensitive and source secrets from a proper secret store rather than hardcoding them into variables files.

3. Make defaults safe

Your module defaults should represent secure, production-minded assumptions.

For example:

  • Private subnets by default
  • Encryption enabled by default
  • Logging enabled by default
  • Public exposure disabled by default

That way, the safe path is the easy path.

4. Add policy checks to CI

Your pipeline should catch risky infra before it reaches apply.

Common checks include:

  • terraform fmt
  • terraform validate
  • tflint
  • checkov or similar policy scanning
  • OPA/Conftest policies for custom rules

Common pitfalls and maintenance tasks

A lot of cluster failures are not dramatic. They are the result of neglected basics.

Common pitfalls

  • Overly broad IAM permissions
  • Worker nodes deployed in public subnets without a strong reason
  • No remote state locking
  • Single-AZ cluster design
  • No version upgrade strategy
  • Secrets stored in Git or state without proper controls
  • Missing resource requests and limits
  • No observability on critical control paths

Ongoing maintenance tasks

Production Kubernetes is not “set and forget.” It needs regular care.

You should schedule:

  • Kubernetes version reviews
  • Node image and patch updates
  • Certificate rotation checks
  • IAM/RBAC access reviews
  • Secret rotation
  • Cost review for overprovisioned node pools
  • Backup and restore validation
  • Incident drills and rollback rehearsals

If you are not testing restore procedures, you do not really know your recovery posture.

Tip

Pro Tip: Build a quarterly infrastructure review checklist. The goal is not to create bureaucracy; it is to keep drift from quietly turning your platform into technical debt.

A practical checklist for production readiness

Use this as a final sanity check before calling your cluster production-ready:

  • VPC/VNet and subnets are designed for multi-AZ resilience
  • Control plane and worker nodes are not publicly exposed unnecessarily
  • Remote Terraform state is enabled with locking
  • Terraform code is modular and environment-separated
  • IAM and RBAC are scoped to least privilege
  • Secrets are managed outside plain Kubernetes manifests
  • Audit logging is enabled and retained
  • Multiple node pools exist for system and application workloads
  • Autoscaling is configured and tested
  • Upgrade path is documented and rehearsed
  • Ingress and service exposure are standardized
  • Deployment strategy includes rollback or canary options
  • Monitoring, logging, and tracing are in place
  • Backup and restore have been validated

Conclusion

Terraform is an excellent tool for Kubernetes provisioning, but it is only one part of the system. A production-ready cluster is the result of deliberate architecture choices: secure networking, careful access control, resilient scaling, and operational habits that reduce surprises.

If you remember only one thing, make it this: production readiness is a property of the whole platform, not a checkbox on the cluster resource.

Start with a secure network foundation. Keep Terraform modular and stateful in a disciplined way. Treat IAM, RBAC, and secrets as first-class design concerns. Then validate that scaling, upgrades, and deployment workflows will still behave when things go wrong—not just when everything is green.

That is the difference between infrastructure that merely exists and infrastructure you can trust.


Further reading

  • Kubernetes documentation on cluster administration and security
  • Terraform documentation on remote state and module composition
  • Cloud provider guidance for managed Kubernetes services
  • CNCF materials on observability, policy, and platform engineering