# 11. Flavors, roles, and imports

Back in tutorial 02, when you ran `kix list packages --cluster 02-hello-world`, the output included two rows you did not write in `cluster.nix`: `platform-dns` and `platform-storage`, both marked `import [kube-system]`.

This tutorial explains where those `import` rows come from and how Kix models pre-existing cluster infrastructure. You will learn how **flavors** (`kix.flavors.kind`) declare what your Kubernetes distribution already provides, how **roles** (`dns`, `storageClasses`) wire those platform capabilities to workloads automatically, and how **`kix.mkImport`** lets your own packages depend on existing services through the standard `.out` contract.

## Prerequisites

- Finish the [reusing packages tutorial](/docs/tutorials/10-reusing-packages/).
- Keep the same `kix-examples` checkout.

## Inspect The `import` Rows From Tutorial 02

Look again at the `kix list packages` output for `02-hello-world`:

<Command {...listPackages} />

In `tutorials/02-hello-world/cluster.nix`, you only declared one instance (`hello-world`), yet you also included `kix.flavors.kind` in the cluster's `modules` list:

<Snippet
  {...clusterFrame}
  elide={{
    helloWorldInstance: '        # ...',
  }}
/>

That single entry (`kix.flavors.kind` in `modules`) added `platform-dns` and `platform-storage` in `kube-system`.

## What A Cluster Flavor Does

Every Kubernetes distribution starts with a different set of pre-installed services:

- **kind** ships with CoreDNS (`Deployment/coredns` and `Service/kube-dns` in `kube-system`) and Rancher local-path provisioner (`StorageClass/standard`).
- **EKS**, **GKE**, **AKS**, and **k3s** ship with their own DNS services, storage classes, CNI capabilities, and cloud load balancer controllers.
- **Self-managed (`kubeadm`, `talos`, `rke2`)** clusters may require you to install your own storage provisioner, ingress controller, and CNI.

Instead of forcing packages to guess what cluster they are running on, Kix packages declare what cluster capabilities they need, and a **cluster flavor** module (`kix.flavors.kind`, `kix.flavors.eks`, `kix.flavors.gke`, `kix.flavors.k3s`, `kix.flavors.kubeadm`, `kix.flavors.talos`, and others) declares what the platform already provides.

Specifically, `kix.flavors.kind` does two things:

1. It registers two **imported instances** in `kube-system` (`platform-dns` and `platform-storage`) using `kix.mkImport`.
2. It binds those imported instances to the cluster's **infrastructure roles** (`roles = [ "dns" ]` and `roles = [ "storageClasses" ]`, alongside `cluster.roles.dns` and `cluster.roles.storageClasses`).

:::note[Reference]
See [Built-in flavors](/docs/reference/flavors-and-capabilities/#provider-profiles) and [Cluster roles](/docs/reference/cluster-configuration/#cluster-roles) for all built-in flavors and role bindings.
:::

## How Infrastructure Roles Connect Workloads To The Platform

Why does `kix.flavors.kind` register `platform-dns` and `platform-storage` as instances in the dependency graph?

1. **Readiness barriers (`requires:`)**: Every Pod that uses cluster DNS (`dnsPolicy = "ClusterFirst"`, the Kubernetes default) needs CoreDNS to be running before it starts making upstream requests. And every `PersistentVolumeClaim` needs its `StorageClass` to exist before it can bind. Because `platform-dns` provides the `dns` role (`Deployment/coredns` in `kube-system`), Kix automatically adds a `requires:` readiness dependency from your `gateway` Deployment to `platform-dns`. During `kix deploy`, Kix verifies that CoreDNS is live and ready in `kube-system` before rolling out workloads that depend on DNS.
2. **`StorageClass` resolution and validation**: When a stateful package asks for `storageClasses` in `build` (or sets `spec.storageClassName`), Kix resolves the `storageClasses` role to `platform-storage` (`storageClasses.out.storageClassName = "standard"` on kind) and records a dependency on `platform-storage`. If a `PersistentVolumeClaim` or `StatefulSet` `volumeClaimTemplate` requests a `StorageClass` that is neither created in the cluster nor imported via `kix.mkImport`, evaluation fails before deploy telling you that nothing in the cluster creates or imports that `StorageClass`.
3. **DNS egress network policy**: When a cluster enables NetworkPolicy synthesis (`networkPolicy.enable = true`), Kix uses the `dns` role provider and the flavor's `platformIntents` so default-deny egress policies still allow UDP/TCP port 53 traffic to CoreDNS.

You can see that `platform-dns` dependency in the `kix graph` output you ran in tutorial 08:

```text
PackageInstance/platform-dns@kube-system (import)
├── Activation/08-namespace-deps-y50i580sy41b
└── Deployment/gateway@app
    └── Service/gateway@app
```

## Declare Your Own Imported Resource With `kix.mkImport`

Flavors use `kix.mkImport` for cluster-level DNS and storage, and you can use the exact same `kix.mkImport` helper in `cluster.nix` whenever a Kix package needs to talk to an existing service that Kix does not manage (for example, a legacy Service in another namespace or a Helm release you have not migrated yet).

In `how-to/adoption/cluster.nix`, the cluster declares an imported `Service` named `legacy-api` in the `legacy` namespace:

<Snippet {...importExistingService} />

Look at what `kix.mkImport` produces:

- **No YAML manifest is applied to Kubernetes.** `kix deploy` will never create, modify, or prune `Service/legacy-api`. Instead, `kix deploy` verifies that `Service/legacy-api` exists in the `legacy` namespace before rolling out dependents.
- **It exposes the standard `.out` interface.** Any Kix package can depend on `legacyApi` and read `legacyApi.out.fqdn` and `legacyApi.out.port`, just like it read `backend.out.fqdn` in tutorial 06:

<Snippet {...consumeImportedService} />

Later, if you migrate `legacy-api` into a native Kix package, you only replace `package = kix.mkImport { ... }` in `cluster.nix` with the real package. The consumer package (`client-package.nix`) does not change at all.

## Shared Dependency Contracts For Backing Services

In the `legacy-api` example above, `client-package.nix` only read two fields (`fqdn` and `port`). Catalog applications such as `grafana`, `harbor`, `immich`, `metabase`, `n8n`, `plausible`, and `vaultwarden` go further: when they ask for a backing service (`database`, `valkey`, `smtp`, `oidc`, `gateway`, or `otlp`), they validate the provider's `out` attributes against a shared **dependency contract** in `kix.contracts.*`.

Whether a `database` dependency is satisfied by an in-cluster operator (`cnpg-database` or `stackgres-database`) or by an external cloud database declared with `kix.mkImport`, Kix checks that `out` exposes `host`, `port`, `database`, and a `credentials` secret declaring the keys `username` and `password`:

<Snippet {...importedPostgresDatabase} />

Because a pod can only read `Secret` objects in its own namespace, an import that carries `out.credentials` (`credentials = ref.<namespace>.<secret>`) sits in the consuming application's namespace with `scope = "namespace"`. If a required contract field or secret key is missing on `out`, `kix check` catches it before deployment.

:::note[Explanation]
See [Dependency contracts](/docs/explanation/dependency-contracts/) and [Helm integration](/docs/explanation/helm-integration/) for how contracts and `kix.mkImport` let you swap between in-cluster operators and external cloud services without editing application configuration.
:::

:::tip[Use it in a task]
See [Connect external databases, caches, mail, and single sign-on](/docs/how-to/adopt-existing-resources/connect-external-databases-caches-mail-and-sso/) for complete recipes covering PostgreSQL (`database`), Redis (`valkey`), mail (`smtp`), and OpenID Connect (`oidc`).
:::

## What You Learned

You saw how Kix connects packages to the surrounding Kubernetes cluster:

<Recap>
- including `kix.flavors.kind` in `modules` declares the capabilities and pre-installed services of a kind cluster;
- infrastructure roles (`dns` and `storageClasses`) connect workloads and PVCs to the cluster's DNS provider and storage classes;
- `kix.mkImport` registers an existing Kubernetes resource or external service in the dependency graph without managing its lifecycle;
- packages consume imported services through `.out` (`.out.fqdn`, `.out.port`, `.out.credentials`) using the exact same syntax and `kix.contracts.*` checks as Kix-managed instances.
</Recap>

In the final tutorial (`12. Scorecards and compile-time policy`), you will write custom scorecard rules that inspect package instances and enforce routing and configuration policies across the entire cluster at build time.