1---
2title: Connecting to clusters
3description: Point Kubernetes resources at any cluster with Kubernetes.KubeConfig, a raw Kubernetes.Connection (bearer token, client certificate, or exec plugin), or a cluster resource such as Kubernetes.LocalCluster.
4---
5
6Every Kubernetes resource takes a `cluster` prop. That includes
7[`Deployment`](/kubernetes/workloads/deployments),
8[`Job`](/kubernetes/workloads/jobs),
9[`Manifest`](/kubernetes/objects/manifests), and
10[`HelmChart`](/kubernetes/objects/helm-charts). The prop accepts
11three shapes:
12
13- **`Kubernetes.KubeConfig({ ... })`.** Any cluster in a kubeconfig
14 file, such as kind, k3s, on-prem, GKE, AKS, or EKS.
15- **A `Kubernetes.Connection` object.** An API endpoint plus a
16 bearer token, client certificate, or exec plugin.
17- **A cluster resource.** Any resource with a `connection`
18 attribute, such as a [`Kubernetes.LocalCluster`](/kubernetes/clusters/local)
19 or an [EKS cluster](/kubernetes/clusters/eks) from the same Stack.
20
21Every form can also say where workloads built from your code are
22pushed, with a [`registry`](/kubernetes/clusters/registries).
23
24`KubeConfig` and `Connection` are plain, serializable data. Nothing
25is provisioned for them and they never appear in a plan. Alchemy stores it on each
26resource's state so `alchemy destroy` can reach the cluster later,
27even after you delete the code that declared it.
28
29## Use a kubeconfig context
30
31`Kubernetes.KubeConfig` authenticates exactly like `kubectl`:
32
33```typescript
34import * as Kubernetes from "alchemy/Kubernetes";
35
36const cluster = Kubernetes.KubeConfig({ context: "prod-east" });
37
38const api = yield* Kubernetes.Deployment("Api", {
39 cluster,
40 image: "ghcr.io/acme/api:v3",
41 port: 8080,
42});
43```
44
45All options are optional:
46
47- `path` is the kubeconfig file. It defaults to the first entry of
48 `$KUBECONFIG`, then `~/.kube/config`. A leading `~` is expanded.
49- `context` is the context to use. It defaults to the file's
50 `current-context`.
51- `registry` is where `main` and `context` workloads push their
52 images. See [Container registries](/kubernetes/clusters/registries).
53- `architecture` is the nodes' CPU architecture, `"amd64"` or
54 `"arm64"`. It is used as the default image build target.
55
56Name the context explicitly for anything beyond a scratch cluster.
57The current context changes whenever someone runs
58`kubectl config use-context`, and a deploy would follow it to a
59different cluster.
60
61The file is read at deploy time, on the machine running
62`alchemy deploy`. The API endpoint, certificate authority, and
63credentials all come from the selected context. Static tokens,
64client certificates, and `exec` credential plugins are supported.
65
66## Cloud CLI contexts
67
68The cloud CLIs write contexts that mint short-lived tokens through
69an `exec` credential plugin. Alchemy runs the plugin before each
70request, so tokens never go stale mid-deploy:
71
72- **GKE.** `gcloud container clusters get-credentials <cluster>`
73 writes a context that runs `gke-gcloud-auth-plugin`.
74- **AKS.** `az aks get-credentials --name <cluster> --resource-group
75 <group>` writes the context. Clusters using Microsoft Entra ID also
76 need `kubelogin`.
77- **EKS.** `aws eks update-kubeconfig --name <cluster>` writes a
78 context that runs `aws eks get-token`. See
79 [Amazon EKS](/kubernetes/clusters/eks) for the fuller integration.
80
81The plugin must be installed and logged in wherever you deploy from,
82including CI runners.
83
84## Keep the kubeconfig next to the code
85
86Point `path` at a file you control to decouple deploys from your
87personal `~/.kube/config`:
88
89```typescript
90const cluster = Kubernetes.KubeConfig({
91 path: "./kubeconfig.yaml",
92 context: "staging",
93});
94```
95
96A relative path is resolved from the directory you run
97`alchemy deploy` in. In CI, write the file from a secret before the
98deploy step, or set `KUBECONFIG` to its location and omit `path`.
99
100## Connect with a bearer token
101
102A raw `Kubernetes.Connection` names the API endpoint and an `auth`
103descriptor. For a ServiceAccount token or another static bearer
104token:
105
106```typescript
107import * as Config from "effect/Config";
108
109const token = yield* Config.String("K8S_TOKEN");
110
111const cluster: Kubernetes.Connection = {
112 endpoint: "https://10.0.0.10:6443",
113 certificateAuthorityData: "LS0tLS1CRUdJTi…", // base64-encoded PEM
114 auth: { kind: "token", token },
115};
116```
117
118`certificateAuthorityData` is the same base64-encoded PEM bundle a
119kubeconfig carries as `certificate-authority-data`. For self-signed
120local clusters you can set `insecureSkipTlsVerify: true` instead.
121
122## Connect with a client certificate
123
124For mutual TLS, pass the PEM certificate and key:
125
126```typescript
127const cluster: Kubernetes.Connection = {
128 endpoint: "https://k8s.internal:6443",
129 certificateAuthorityData: caData,
130 auth: { kind: "client-cert", certificate: certPem, key: keyPem },
131};
132```
133
134## Connect with an exec plugin
135
136Run any command that speaks the Kubernetes
137[ExecCredential protocol](https://kubernetes.io/docs/reference/access-authn-authz/authentication/#client-go-credential-plugins)
138without a kubeconfig file:
139
140```typescript
141const cluster: Kubernetes.Connection = {
142 endpoint: "https://k8s.internal:6443",
143 certificateAuthorityData: caData,
144 auth: {
145 kind: "exec",
146 command: "/usr/local/bin/cluster-token",
147 args: ["--cluster", "prod"],
148 env: { TOKEN_PROFILE: "prod" },
149 },
150};
151```
152
153The plugin is re-run for every request, and a client certificate it
154returns is used for mutual TLS.
155
156`token`, `client-cert`, and `exec` connections need an explicit
157`endpoint`. Only kubeconfig connections and cluster resources can
158discover it.
159
160## Credentials in state
161
162Alchemy persists each resource's connection in state. A kubeconfig
163connection stores only the path and context name, and an exec
164connection stores the command. A `token` or `client-cert`
165connection stores the credential itself. Prefer kubeconfig or exec
166connections for stacks whose state is shared, and use a remote
167[state store](/state-store) with restricted access either way.
168
169## Deploy to several clusters
170
171`cluster` is per resource, so one Stack can span clusters:
172
173```typescript
174const east = Kubernetes.KubeConfig({ context: "prod-east" });
175const west = Kubernetes.KubeConfig({ context: "prod-west" });
176
177for (const [region, cluster] of [
178 ["East", east],
179 ["West", west],
180] as const) {
181 yield* Kubernetes.Deployment(`Api${region}`, {
182 cluster,
183 name: "api",
184 image: "ghcr.io/acme/api:v3",
185 port: 8080,
186 });
187}
188```
189
190## Move a resource to another cluster
191
192Alchemy identifies the target cluster by the connection's `auth`
193descriptor. That is the kubeconfig path and context, the token or
194certificate, or the exec command. Changing it replaces the resource.
195Alchemy creates it on the new cluster, then deletes it from the old
196one. The `endpoint`, certificate authority, and `registry` are not
197part of that identity, so they can change without replacing
198workloads.
199
200:::caution[Changing how you reach the same cluster]
201A replacement assumes the old and new connections are different
202clusters. Sometimes they reach the **same** cluster. For example, you
203renamed the kubeconfig context, moved the file, rotated a `token` or
204client certificate, or switched from a kubeconfig to a raw
205connection. Then the new objects have the same names as the old
206ones, and deleting the old generation removes them. The deploy
207succeeds but the objects are gone. Run `alchemy deploy --force` right
208after such a change to apply them again. Prefer kubeconfig or exec
209connections, because their identity doesn't change when credentials
210rotate.
211:::
212
213## When no adapter is registered
214
215Each `auth.kind` is served by a [cluster adapter](/kubernetes/clusters/cluster-adapters).
216`Kubernetes.providers()` registers `kubeconfig`, `token`,
217`client-cert`, and `exec`. Cloud platforms contribute their own.
218For example, EKS clusters need `AWS.providers()` in the Stack. If a
219deploy fails with *No Kubernetes cluster adapter is registered for
220auth kind …*, add the provider layer that contributes it.
221
222## Where next
223
224- **[Container registries](/kubernetes/clusters/registries).** Add a
225 registry to a connection.
226- **[Local clusters](/kubernetes/clusters/local).** Run a cluster
227 resource on your machine.
228- **[How objects are managed](/kubernetes/workloads/object-lifecycle).**
229 See what happens when a cluster is unreachable or gone.
230- **[`Deployment` reference](/providers/kubernetes/deployment).** Read
231 about the `cluster` prop and every other prop.