Cluster adapters
The Kubernetes resources don’t know which platform a cluster runs
on. Everything platform-specific lives in a cluster adapter,
chosen by the auth.kind of the resource’s
connection:
kubeconfig,token,client-cert, andexecare registered byKubernetes.providers(). They handle authentication only.aws-eksis registered byAWS.providers(). It handles authentication, image registry, workload identity, generated entrypoints, and load balancer defaults.
When a resource runs, Alchemy looks up the adapter for its connection’s kind among the Stack’s provider layers. If none is registered, the deploy fails with a message naming the missing kind and the provider layer that contributes it.
What an adapter provides
Section titled “What an adapter provides”An adapter implements Kubernetes.ClusterAdapterService. Only
connect is required:
connectresolves the API endpoint and CA, and returns a per-request header mint so short-lived tokens stay fresh. It fails withClusterNotFoundErrorwhen the cluster definitely no longer exists.registrybuilds or mirrors workload images into a registry the platform manages. Without it,mainandcontextsources are pushed to the connection’sregistry.identityprovisions cloud credentials for a workload’s ServiceAccount. It turns binding grants, such as AWSpolicyStatements, into those credentials.bootstrapreplaces the generated container entrypoints formainprograms, for example to wire a cloud credential chain.loadBalancerDefaultssets defaults forLoadBalancerServices, such asloadBalancerClassand annotations. YourserviceAnnotationswin.
Missing members degrade gracefully. Without registry, only image
sources work. Without identity, bindings may only carry env.
Write an adapter
Section titled “Write an adapter”A custom adapter is a Layer for Kubernetes.ClusterAdapter(kind)
plus a type for its auth descriptor. This one reads a bearer token
from an environment variable on every request, so the token itself
never lands in Alchemy’s state:
import * as Kubernetes from "alchemy/Kubernetes";import * as Config from "effect/Config";import * as Effect from "effect/Effect";import * as Layer from "effect/Layer";
declare module "alchemy/Kubernetes" { interface AuthRegistry { "env-token": { /** Environment variable holding the bearer token. */ variable: string; }; }}
export const EnvTokenAdapter = Layer.succeed( Kubernetes.ClusterAdapter("env-token"), { kind: "Kubernetes.ClusterAdapter", connect: (connection: Kubernetes.Connection) => Effect.gen(function* () { if (connection.auth.kind !== "env-token" || !connection.endpoint) { return yield* Effect.fail( new Error("env-token needs an endpoint and an env-token auth"), ); } const variable = connection.auth.variable; return { endpoint: connection.endpoint, certificateAuthorityData: connection.certificateAuthorityData, headers: Config.String(variable).pipe( Effect.map((token) => ({ Authorization: `Bearer ${token}` })), Effect.mapError((cause) => new Error(String(cause))), ), }; }), },);The declare module block adds "env-token" to the auth union,
so connections using it type-check.
Register it
Section titled “Register it”Merge the adapter into the Stack’s providers, then use its kind in a connection:
import { EnvTokenAdapter } from "./src/env-token-adapter.ts";
const cluster: Kubernetes.Connection = { endpoint: "https://k8s.internal:6443", certificateAuthorityData: caData, auth: { kind: "env-token", variable: "K8S_TOKEN" },};
export default Alchemy.Stack( "MyCluster", { providers: Layer.mergeAll(Kubernetes.providers(), EnvTokenAdapter), state: Alchemy.localState(), }, Effect.gen(function* () { yield* Kubernetes.Manifest("Config", { cluster, manifest: { apiVersion: "v1", kind: "ConfigMap", metadata: { name: "app-config", namespace: "default" }, data: { LOG_LEVEL: "info" }, }, }); }),);The connection stored in state is { kind: "env-token", variable: "K8S_TOKEN" }, so alchemy destroy authenticates the same way. It
uses whatever K8S_TOKEN holds at that time.
Registries and identity
Section titled “Registries and identity”A platform adapter such as aws-eks goes further:
registry.resolvereceives the workload’s image source, target platform, generated entrypoint, and tags. It returns the image URI the pod should run, a content hash, and serializable state. The EKS adapter stores its ECR repository there.registry.hashcomputes the same hash at plan time so content changes show up as updates.registry.deletecleans up on destroy.identity.reconcilereceives the workload’s namespace, ServiceAccount, bindings, andidentityoptions. It returns extra environment variables, ServiceAccount annotations, and state. The EKS adapter stores its IAM role and Pod Identity association there.identity.deleteremoves them, even when the cluster is already gone.
Platforms extend the typed surfaces through module augmentation of
alchemy/Kubernetes:
AuthRegistryholds connection kinds.IdentityStateRegistryandRegistryStateRegistryhold persisted state.WorkloadIdentityOptionstypes theidentityprop. EKS addsmanagedPolicyArns.WorkloadServicesRegistrylists services available inside the container.
The EKS adapter source is the complete reference.
Where next
Section titled “Where next”- Connecting to clusters. The built-in connection kinds.
- Amazon EKS. What the EKS adapter does in practice.
- Layers. How Layers compose in an Alchemy program.