Manage KinD (Kubernetes in Docker) clusters with
Terraform. The provider embeds kind's own Go library rather than shelling out to
the kind binary, so create, refresh and destroy are driven by the same code
the CLI uses — and the kind binary does not need to be installed.
Built for ephemeral CI clusters, reproducible local development environments, and integration tests that need a real API server.
- Requirements
- Installation
- Quick start
- Common configurations
- Using the cluster with other providers
- Importing an existing cluster
- Troubleshooting
- Limitations
- Documentation
- Contributing
- License
| Requirement | Notes |
|---|---|
| Docker | Installed and running. Podman and nerdctl are auto-detected by kind but are not covered by this project's tests. |
| Terraform >= 1.0 | Or OpenTofu >= 1.6. |
| Memory | Each node is a container; budget roughly 2 GB per node. |
The kind CLI is optional. It is handy for troubleshooting, since
kind get clusters and kind export logs inspect the very same clusters.
terraform {
required_providers {
kind = {
source = "elioseverojunior/kind"
version = ">= 0.0.3"
}
}
}
provider "kind" {}git clone https://github.com/elioseverojunior/terraform-provider-kind.git
cd terraform-provider-kind
make installThis installs into ~/.terraform.d/plugins/registry.terraform.io/elioseverojunior/kind/<version>/<os>_<arch>/,
where Terraform will find it without any registry lookup.
resource "kind_cluster" "default" {
name = "my-cluster"
node {
role = "control-plane"
}
node {
role = "worker"
}
}
output "kubeconfig_path" {
value = kind_cluster.default.kubeconfig_path
}terraform init
terraform apply
export KUBECONFIG=$(terraform output -raw kubeconfig_path)
kubectl get nodesOmitting the node blocks entirely gives kind's default topology: one
control-plane node and one worker.
resource "kind_cluster" "versioned" {
name = "pinned"
node_image = "kindest/node:v1.37.0"
node {
role = "control-plane"
}
}Node images are built for a specific kind release. Leave node_image unset to
use the digest-pinned default that ships with the bundled kind version — the
safest option. If you do pin, pick an image listed in the
kind release notes for that
release; an arbitrary Kubernetes tag may not have a matching node image.
An ingress controller needs both a labelled node and host port mappings on that same node.
resource "kind_cluster" "ingress" {
name = "ingress"
node {
role = "control-plane"
labels = {
"ingress-ready" = "true"
}
extra_port_mappings {
container_port = 80
host_port = 8080
protocol = "TCP"
}
extra_port_mappings {
container_port = 443
host_port = 8443
protocol = "TCP"
}
}
node {
role = "worker"
}
}resource "kind_cluster" "ha" {
name = "ha"
node { role = "control-plane" }
node { role = "control-plane" }
node { role = "control-plane" }
node { role = "worker" }
}kind places an external load balancer container in front of the control planes automatically.
With the default CNI disabled, nodes stay NotReady until you install a
replacement — which cannot happen before terraform apply returns. Turn the
readiness wait off, or the apply fails on an expected timeout.
resource "kind_cluster" "cilium" {
name = "cilium"
wait_for_nodes_ready = false
networking {
disable_default_cni = true
kube_proxy_mode = "none"
}
node {
role = "control-plane"
}
}kube_proxy_mode = "none" suits CNIs such as Cilium that replace kube-proxy
entirely. The other accepted modes are iptables (default), ipvs and
nftables.
resource "kind_cluster" "registry" {
name = "with-registry"
containerd_config_patches = [
<<-TOML
[plugins."io.containerd.grpc.v1.cri".registry]
config_path = "/etc/containerd/certs.d"
TOML
]
node {
role = "control-plane"
}
}A complete registry setup, including the registry container itself, is in
examples/complete/cicd-cluster.
data "kind_clusters" "all" {}
output "clusters" {
value = data.kind_clusters.all.clusters
}kind_cluster exports the credentials the kubernetes and helm providers
need. The certificate attributes are base64-encoded, as they are inside a
kubeconfig, so decode each one:
provider "kubernetes" {
host = kind_cluster.default.endpoint
client_certificate = base64decode(kind_cluster.default.client_certificate)
client_key = base64decode(kind_cluster.default.client_key)
cluster_ca_certificate = base64decode(kind_cluster.default.cluster_ca_certificate)
}A fresh apply needs two stages. Terraform configures providers before the cluster exists, so the first
terraform applyof a configuration that both creates the cluster and deploys into it will fail. Either runterraform apply -target=kind_cluster.defaultfirst, or keep the cluster and its workloads in separate root modules. This is a Terraform-wide constraint, not specific to this provider.
The Kubernetes and Helm guide
covers this in full, and
examples/complete/kubernetes-provider
is a runnable version.
Clusters are imported by name, which is also the resource ID:
terraform import kind_cluster.default my-clusterRun kind get clusters to see the available names. Only name is imported;
every other attribute is re-read from kind on the next refresh.
Docker is not running, or is listening somewhere the provider is not looking.
Check with docker info. For Colima, Rancher Desktop, or a rootless daemon, set
the socket explicitly:
provider "kind" {
host = "unix:///Users/me/.colima/default/docker.sock"
}The cluster came up but some nodes never reached Ready; the error names them.
kubectl --context kind-<cluster-name> describe node <node-name>Usually this is too little memory allocated to Docker, or a cluster with
disable_default_cni = true and no CNI installed. Raise wait_for_ready on
slow machines, or set wait_for_nodes_ready = false.
Nearly every argument is RequiresReplace, because kind cannot reconfigure a
running cluster. Read the plan output to see which attribute changed — a
floating node_image tag is the usual culprit.
Another container or service holds a port named in extra_port_mappings. Choose
a different host_port, or set networking.api_server_port = 0 to let the
kernel pick a free API server port.
kind delete clusters --all- Clusters are immutable. kind cannot add, remove or reconfigure nodes on a running cluster, so changing the topology destroys and recreates it — along with everything deployed inside.
- Local only. Nodes are Docker containers on the machine running Terraform. There is no remote infrastructure to manage.
- Not for production. KinD clusters run development defaults and are not hardened.
- Credentials land in state.
kubeconfig,client_keyand the certificates are stored in Terraform state in cleartext. See SECURITY.md.
The full, generated reference lives on the Terraform Registry:
For the internals, see ARCHITECTURE.md.
Contributions are welcome. CONTRIBUTING.md covers the development loop, testing tiers and how the documentation is generated.
make test # unit tests, no Docker needed
make lint # must report 0 issues
make generate # regenerate docs/ after a schema change
make testacc # acceptance tests: real clusters, needs DockerReport bugs and request features through GitHub issues. Security reports go through SECURITY.md instead.
Dual-licensed under either of:
- MIT (LICENSE)
- Apache License 2.0 (LICENSE-APACHE)
at your option. Unless you state otherwise, any contribution you intentionally submit for inclusion in this work shall be dual-licensed as above, with no additional terms or conditions.
- KinD — Kubernetes in Docker
- terraform-plugin-framework — the provider SDK used here