# Typhoon turns a Kubernetes cluster into a Terraform module call

> Typhoon ships upstream Kubernetes as Terraform modules, one per cloud and operating system, so a cluster is declared in HCL and built by a single apply. Support is uneven: Google Cloud, AWS and bare metal are stable on Fedora CoreOS, Azure is alpha, and every addon module is beta.

**poseidon/typhoon** — Minimal and free Kubernetes distribution with Terraform

- Repository: https://github.com/poseidon/typhoon
- Website: https://typhoon.psdn.io/
- Stars: 2,055 · Forks: 322
- Language: HCL
- License: MIT
- Published: 2026-10-13 · Updated: 2026-10-13 · Language: en
- Canonical page: https://hysenlabs.com/projects/poseidon-typhoon

## One module directory per cloud and operating system pair

Typhoon's primary language is HCL, and that single fact explains how the distribution is assembled: a cluster is a Terraform module call, not a shell script you run. Every supported pairing of cloud and operating system gets its own module directory, and the repository root is split along those lines, with `aws/`, `azure/`, `bare-metal/`, `digital-ocean/`, `google-cloud/`, `addons/` and `docs/`, plus `mkdocs.yml`, `requirements.txt` and `theme/` that build the documentation site.

The example the README gives names one of those directories in its `source` argument and pins a version through the query string, so the code you write picks the platform module explicitly:

```tf
module "yavin" {
  source = "git::https://github.com/poseidon/typhoon//google-cloud/fedora-coreos/kubernetes?ref=v1.36.2"
  # Google Cloud
  cluster_name  = "yavin"
  region        = "us-central1"
  dns_zone      = "example.com"
  dns_zone_name = "example-zone"
  # configuration
  ssh_authorized_key = "ssh-ed25519 AAAAB3Nz..."
  # optional
  worker_count = 2
  worker_preemptible = true
}
```

Seven arguments cover naming, region, the DNS zone the cluster will publish into, the SSH public key copied onto nodes, and two worker knobs. Everything else, from the control plane layout to kubelet settings, arrives as a module default. That is the distribution idea in one place: Typhoon ships upstream Kubernetes plus architectural conventions and addons, the way a GNU/Linux distribution ships a kernel and userspace, and the module holds those conventions so the operator does not have to.

## Sixty-two resources, then a four to eight minute wait

The three commands after the module block are ordinary Terraform. Init fetches the module, plan prints what would be built, apply builds it, and the output the README shows gives a concrete size for a cluster on Google Cloud.

```sh
$ terraform init
$ terraform plan
Plan: 62 to add, 0 to change, 0 to destroy.
$ terraform apply
Apply complete! Resources: 62 added, 0 changed, 0 destroyed.
```

Once apply returns, the README puts the wait at 4-8 minutes, varying by platform, and says what the finished cluster exposes: a `yavin.example.com` DNS record resolving to a network load balancer across controller nodes. Two things are absent from that flow. No way to stop a run partway is documented, and nothing describes what a failed apply leaves behind in the cloud account. The plan output is therefore the only preview of those resources before they exist.

One version detail deserves a check before you copy anything. The module example pins `?ref=v1.36.2` and the node table further down reports v1.36.2, while the three most recent tags the project published are v1.36.1 from 2026-06-08, v1.35.4 from 2026-05-11 and v1.34.2 from 2025-12-07. The example runs ahead of that list, so treat any copied `ref` value as something to confirm against the tags rather than as a given.

## kubeconfig-admin is an output you have to persist yourself

One detail separates this from distributions that hand you a credentials file. The module exposes cluster administrator credentials as an output named `kubeconfig-admin`, and writing them to disk is left to the operator as a `local_file` resource declared outside the module.

```tf
# Obtain cluster kubeconfig
resource "local_file" "kubeconfig-yavin" {
  content         = module.yavin.kubeconfig-admin
  filename        = "/home/user/.kube/configs/yavin-config"
  file_permission = "0600"
}
```

That permission of 0600 matters, because the content is administrator level, and no least-privilege user or non-admin credential is documented anywhere in the example. Point `KUBECONFIG` at the file you just wrote and the cluster answers.

```sh
$ export KUBECONFIG=/home/user/.kube/configs/yavin-config
$ kubectl get nodes
NAME                                       ROLES    STATUS  AGE  VERSION
yavin-controller-0.c.example-com.internal  <none>   Ready   6m   v1.36.2
yavin-worker-jrbf.c.example-com.internal   <none>   Ready   5m   v1.36.2
yavin-worker-mzdm.c.example-com.internal   <none>   Ready   5m   v1.36.2
```

Three nodes for a `worker_count` of 2, one controller and two workers, and the ages of 6m and 5m sit inside the stated window. The ROLES column reads `<none>` on all three rows though, controller included, so this listing cannot tell a master from a worker. Read the name prefix instead.

## etcd with TLS, RBAC and enforcing SELinux are not switches

Storage and authorization are not left to operator judgment. On-cluster etcd with TLS, RBAC-enabled access, network policy and SELinux enforcing are listed as properties of the distribution rather than as flags to set. etcd running inside the cluster means the control plane carries its own state store, which is what allows the bare-metal module to look like the Google Cloud one without a hosted database standing in front of it.

None of these four has a documented off switch among the module arguments on show. That cuts both ways. There is no knob to weaken host security policy on a locked-down machine, and no way to hand the key-value store to a cloud-managed service, a choice some operators make for backup and failover reasons the repository never discusses.

## Cilium or flannel, and three addon modules still at beta

Networking is an argument too: pick a single or multi-master control plane, then Cilium or flannel as the CNI, and both are also offered as standalone addon modules. The status detail worth pausing on sits in those addon rows. `addons/coredns`, `addons/cilium` and `addons/flannel` are each marked beta, while the AWS, bare-metal and Google Cloud platform rows beside them are marked stable. A stable cluster path therefore terminates in beta components, and the addon status column is the only stability statement the repository makes about them.

The choice between the two CNIs gets no treatment at all. Network policy appears in the feature list without being attributed to either of them, and the addons overview is linked as the place where detail lives. If Cilium versus flannel is your decision, this repository does not settle it, and the only project-specific difference the status table offers is that both addon modules sit at beta.

## Azure and ARM64 rows are alpha, Google Cloud and bare metal are not

Platform support is uneven and the module table is explicit about where. On Fedora CoreOS, AWS, bare metal and Google Cloud are stable, DigitalOcean is beta and Azure is alpha. On Flatcar Linux the same three stay stable, and two extra ARM64 rows appear, AWS on ARM64 and Azure on ARM64, both alpha. ARM64 has no Fedora CoreOS module at all, so the architecture you pick decides which directory even exists.

Alpha is the project's own label and nothing in the repository defines it. No criterion is given for when a target leaves alpha, no support window is attached to it, and no compatibility promise covers those rows. The practical reading is narrow: the example cluster most people copy runs on Google Cloud because that row is stable, and an Azure or ARM64 plan is a plan against an alpha path with no stated end state.

## Preemptible workers, worker pools and undocumented eviction behaviour

The last two lines of the module block hold the cost trade-off in miniature. `worker_count = 2` sets how many workers exist. `worker_preemptible = true` marks them reclaimable, a flag the feature list ties to Google Cloud through a dedicated preemption section, and the same list files preemptible workers under the advanced features next to worker pools and snippets customization for hosts.

What the repository text does not say is what happens to pods already running when a preemptible worker is reclaimed. No drain behaviour, no disruption budget and no re-creation guarantee appear in the README or the platform tables. Snippets customization is the documented route for host configuration the module inputs do not reach, and worker pools have their own page under the advanced documentation, yet neither explains what a reclaimed node does to its workloads. Judge the flag as an availability decision, not a cost toggle.

## The README stops one command early and requirements.txt pins only docs

Two small details reward a close reading. The final code block ends at `$ kubectl get pods --all-n` with nothing printed after it, so the project hands you a node listing and stops one command short of a pod listing. The flag is abbreviated as well, and nothing in the repository says whether that is the form it means to teach.

Second, `requirements.txt` at the root pins four documentation packages to exact versions: mkdocs==1.6.1, mkdocs-material==9.7.7, pygments==2.21.0 and pymdown-extensions==11.0.2. Those pins build the site published at typhoon.psdn.io and nothing else, and there is no runtime manifest for a cluster, because the modules target operating system images such as Fedora CoreOS and Flatcar Linux rather than installing packages onto hosts. Anyone hunting for the inventory of what lands inside the cluster should read the module inputs and the addons pages, not this file.

## Conclusion

Typhoon fits an engineer who already writes Terraform and wants to own the control plane, and misses anyone who wants a hosted Kubernetes without reading HCL. Its beta addon modules and its alpha Azure and ARM64 rows are the real boundary. Before committing, check the status column for your exact platform and operating system pair, confirm that the `?ref=` tag you pin exists, and verify that your `dns_zone` and `ssh_authorized_key` values are what you intended. The repository is MIT licensed and its last push was on 2026-09-23.

## FAQ

### How long does it take to create a Typhoon Kubernetes cluster?

The README puts the wait at 4-8 minutes after terraform apply returns, varying by platform. Its own node listing shows a controller aged 6m and two workers aged 5m, both reporting v1.36.2.

### Which platforms does Typhoon support stably?

AWS, bare metal and Google Cloud are stable on Fedora CoreOS, and those same three are stable on Flatcar Linux. Azure is alpha on both operating systems, DigitalOcean is beta on both, and the AWS ARM64 and Azure ARM64 rows are alpha.

### How do I get kubectl access to a Typhoon cluster?

Write the module output kubeconfig-admin to disk with a local_file resource set to file_permission "0600", then export KUBECONFIG pointing at that file before running kubectl.

### Can Typhoon use Cilium or flannel for cluster networking?

Both are offered as module arguments, and each has a standalone addon module, addons/cilium and addons/flannel, both marked beta. The README names no operational difference between the two.

### What licence is Typhoon released under?

MIT, with the LICENSE file at the repository root beside CHANGES.md, CONTRIBUTING.md and DCO.

## Sources

- [License: MIT](https://github.com/poseidon/typhoon/blob/main/LICENSE)
- [poseidon/typhoon on GitHub](https://github.com/poseidon/typhoon)
- [Project website](https://typhoon.psdn.io/)
- [README](https://github.com/poseidon/typhoon/blob/main/README.md)
- [Releases](https://github.com/poseidon/typhoon/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/poseidon-typhoon
