1---
2title: Cluster adapters
3description: The extension point between Alchemy's cluster-agnostic Kubernetes resources and the platform a cluster runs on. Covers authentication, image registries, workload identity, load balancer defaults, and how to write your own.
4---
5
6The Kubernetes resources don't know which platform a cluster runs
7on. Everything platform-specific lives in a **cluster adapter**,
8chosen by the `auth.kind` of the resource's
9[connection](/kubernetes/clusters/connecting):
10
11- `kubeconfig`, `token`, `client-cert`, and `exec` are registered by
12 `Kubernetes.providers()`. They handle authentication only.
13- `aws-eks` is registered by `AWS.providers()`. It handles
14 authentication, image registry, workload identity, generated
15 entrypoints, and load balancer defaults.
16
17When a resource runs, Alchemy looks up the adapter for its
18connection's kind among the Stack's provider layers. If none is
19registered, the deploy fails with a message naming the missing kind
20and the provider layer that contributes it.
21
22## What an adapter provides
23
24An adapter implements `Kubernetes.ClusterAdapterService`. Only
25`connect` is required:
26
27- `connect` resolves the API endpoint and CA, and returns a
28 per-request header mint so short-lived tokens stay fresh. It fails
29 with `ClusterNotFoundError` when the cluster definitely no longer
30 exists.
31- `registry` builds or mirrors workload images into a registry the
32 platform manages. Without it, `main` and `context` sources are
33 pushed to the connection's
34 [`registry`](/kubernetes/clusters/registries).
35- `identity` provisions cloud credentials for a workload's
36 ServiceAccount. It turns binding grants, such as AWS
37 `policyStatements`, into those credentials.
38- `bootstrap` replaces the generated container entrypoints for
39 `main` programs, for example to wire a cloud credential chain.
40- `loadBalancerDefaults` sets defaults for `LoadBalancer` Services,
41 such as `loadBalancerClass` and annotations. Your
42 `serviceAnnotations` win.
43
44Missing members degrade gracefully. Without `registry`, only `image`
45sources work. Without `identity`, bindings may only carry `env`.
46
47## Write an adapter
48
49A custom adapter is a Layer for `Kubernetes.ClusterAdapter(kind)`
50plus a type for its auth descriptor. This one reads a bearer token
51from an environment variable on every request, so the token itself
52never lands in Alchemy's state:
53
54```typescript
55// src/env-token-adapter.ts
56import * as Kubernetes from "alchemy/Kubernetes";
57import * as Config from "effect/Config";
58import * as Effect from "effect/Effect";
59import * as Layer from "effect/Layer";
60
61declare module "alchemy/Kubernetes" {
62 interface AuthRegistry {
63 "env-token": {
64 /** Environment variable holding the bearer token. */
65 variable: string;
66 };
67 }
68}
69
70export const EnvTokenAdapter = Layer.succeed(
71 Kubernetes.ClusterAdapter("env-token"),
72 {
73 kind: "Kubernetes.ClusterAdapter",
74 connect: (connection: Kubernetes.Connection) =>
75 Effect.gen(function* () {
76 if (connection.auth.kind !== "env-token" || !connection.endpoint) {
77 return yield* Effect.fail(
78 new Error("env-token needs an endpoint and an env-token auth"),
79 );
80 }
81 const variable = connection.auth.variable;
82 return {
83 endpoint: connection.endpoint,
84 certificateAuthorityData: connection.certificateAuthorityData,
85 headers: Config.String(variable).pipe(
86 Effect.map((token) => ({ Authorization: `Bearer ${token}` })),
87 Effect.mapError((cause) => new Error(String(cause))),
88 ),
89 };
90 }),
91 },
92);
93```
94
95The `declare module` block adds `"env-token"` to the `auth` union,
96so connections using it type-check.
97
98## Register it
99
100Merge the adapter into the Stack's providers, then use its kind in a
101connection:
102
103```typescript
104import { EnvTokenAdapter } from "./src/env-token-adapter.ts";
105
106const cluster: Kubernetes.Connection = {
107 endpoint: "https://k8s.internal:6443",
108 certificateAuthorityData: caData,
109 auth: { kind: "env-token", variable: "K8S_TOKEN" },
110};
111
112export default Alchemy.Stack(
113 "MyCluster",
114 {
115 providers: Layer.mergeAll(Kubernetes.providers(), EnvTokenAdapter),
116 state: Alchemy.localState(),
117 },
118 Effect.gen(function* () {
119 yield* Kubernetes.Manifest("Config", {
120 cluster,
121 manifest: {
122 apiVersion: "v1",
123 kind: "ConfigMap",
124 metadata: { name: "app-config", namespace: "default" },
125 data: { LOG_LEVEL: "info" },
126 },
127 });
128 }),
129);
130```
131
132The connection stored in state is `{ kind: "env-token", variable:
133"K8S_TOKEN" }`, so `alchemy destroy` authenticates the same way. It
134uses whatever `K8S_TOKEN` holds at that time.
135
136## Registries and identity
137
138A platform adapter such as `aws-eks` goes further:
139
140- `registry.resolve` receives the workload's image source, target
141 platform, generated entrypoint, and tags. It returns the image URI
142 the pod should run, a content hash, and serializable state. The EKS
143 adapter stores its ECR repository there. `registry.hash` computes
144 the same hash at plan time so content changes show up as updates.
145 `registry.delete` cleans up on destroy.
146- `identity.reconcile` receives the workload's namespace,
147 ServiceAccount, bindings, and `identity` options. It returns extra
148 environment variables, ServiceAccount annotations, and state. The
149 EKS adapter stores its IAM role and Pod Identity association there.
150 `identity.delete` removes them, even when the cluster is already
151 gone.
152
153Platforms extend the typed surfaces through module augmentation of
154`alchemy/Kubernetes`:
155
156- `AuthRegistry` holds connection kinds.
157- `IdentityStateRegistry` and `RegistryStateRegistry` hold persisted
158 state.
159- `WorkloadIdentityOptions` types the `identity` prop. EKS adds
160 `managedPolicyArns`.
161- `WorkloadServicesRegistry` lists services available inside the
162 container.
163
164The
165[EKS adapter source](https://github.com/alchemy-run/alchemy/blob/main/packages/alchemy/src/AWS/EKS/KubernetesAdapter.ts)
166is the complete reference.
167
168## Where next
169
170- **[Connecting to clusters](/kubernetes/clusters/connecting).** The
171 built-in connection kinds.
172- **[Amazon EKS](/kubernetes/clusters/eks).** What the EKS
173 adapter does in practice.
174- **[Layers](/infrastructure-as-effects/layers).** How Layers compose
175 in an Alchemy program.