| 1 | --- |
| 2 | title: Connecting to clusters |
| 3 | description: 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 | |
| 6 | Every 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 |
| 11 | three 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 | |
| 21 | Every form can also say where workloads built from your code are |
| 22 | pushed, with a [`registry`](/kubernetes/clusters/registries). |
| 23 | |
| 24 | `KubeConfig` and `Connection` are plain, serializable data. Nothing |
| 25 | is provisioned for them and they never appear in a plan. Alchemy stores it on each |
| 26 | resource's state so `alchemy destroy` can reach the cluster later, |
| 27 | even 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 |
| 34 | import * as Kubernetes from "alchemy/Kubernetes"; |
| 35 | |
| 36 | const cluster = Kubernetes.KubeConfig({ context: "prod-east" }); |
| 37 | |
| 38 | const api = yield* Kubernetes.Deployment("Api", { |
| 39 | cluster, |
| 40 | image: "ghcr.io/acme/api:v3", |
| 41 | port: 8080, |
| 42 | }); |
| 43 | ``` |
| 44 | |
| 45 | All 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 | |
| 56 | Name the context explicitly for anything beyond a scratch cluster. |
| 57 | The current context changes whenever someone runs |
| 58 | `kubectl config use-context`, and a deploy would follow it to a |
| 59 | different cluster. |
| 60 | |
| 61 | The file is read at deploy time, on the machine running |
| 62 | `alchemy deploy`. The API endpoint, certificate authority, and |
| 63 | credentials all come from the selected context. Static tokens, |
| 64 | client certificates, and `exec` credential plugins are supported. |
| 65 | |
| 66 | ## Cloud CLI contexts |
| 67 | |
| 68 | The cloud CLIs write contexts that mint short-lived tokens through |
| 69 | an `exec` credential plugin. Alchemy runs the plugin before each |
| 70 | request, 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 | |
| 81 | The plugin must be installed and logged in wherever you deploy from, |
| 82 | including CI runners. |
| 83 | |
| 84 | ## Keep the kubeconfig next to the code |
| 85 | |
| 86 | Point `path` at a file you control to decouple deploys from your |
| 87 | personal `~/.kube/config`: |
| 88 | |
| 89 | ```typescript |
| 90 | const cluster = Kubernetes.KubeConfig({ |
| 91 | path: "./kubeconfig.yaml", |
| 92 | context: "staging", |
| 93 | }); |
| 94 | ``` |
| 95 | |
| 96 | A relative path is resolved from the directory you run |
| 97 | `alchemy deploy` in. In CI, write the file from a secret before the |
| 98 | deploy step, or set `KUBECONFIG` to its location and omit `path`. |
| 99 | |
| 100 | ## Connect with a bearer token |
| 101 | |
| 102 | A raw `Kubernetes.Connection` names the API endpoint and an `auth` |
| 103 | descriptor. For a ServiceAccount token or another static bearer |
| 104 | token: |
| 105 | |
| 106 | ```typescript |
| 107 | import * as Config from "effect/Config"; |
| 108 | |
| 109 | const token = yield* Config.String("K8S_TOKEN"); |
| 110 | |
| 111 | const 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 |
| 119 | kubeconfig carries as `certificate-authority-data`. For self-signed |
| 120 | local clusters you can set `insecureSkipTlsVerify: true` instead. |
| 121 | |
| 122 | ## Connect with a client certificate |
| 123 | |
| 124 | For mutual TLS, pass the PEM certificate and key: |
| 125 | |
| 126 | ```typescript |
| 127 | const 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 | |
| 136 | Run any command that speaks the Kubernetes |
| 137 | [ExecCredential protocol](https://kubernetes.io/docs/reference/access-authn-authz/authentication/#client-go-credential-plugins) |
| 138 | without a kubeconfig file: |
| 139 | |
| 140 | ```typescript |
| 141 | const 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 | |
| 153 | The plugin is re-run for every request, and a client certificate it |
| 154 | returns 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 |
| 158 | discover it. |
| 159 | |
| 160 | ## Credentials in state |
| 161 | |
| 162 | Alchemy persists each resource's connection in state. A kubeconfig |
| 163 | connection stores only the path and context name, and an exec |
| 164 | connection stores the command. A `token` or `client-cert` |
| 165 | connection stores the credential itself. Prefer kubeconfig or exec |
| 166 | connections 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 |
| 174 | const east = Kubernetes.KubeConfig({ context: "prod-east" }); |
| 175 | const west = Kubernetes.KubeConfig({ context: "prod-west" }); |
| 176 | |
| 177 | for (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 | |
| 192 | Alchemy identifies the target cluster by the connection's `auth` |
| 193 | descriptor. That is the kubeconfig path and context, the token or |
| 194 | certificate, or the exec command. Changing it replaces the resource. |
| 195 | Alchemy creates it on the new cluster, then deletes it from the old |
| 196 | one. The `endpoint`, certificate authority, and `registry` are not |
| 197 | part of that identity, so they can change without replacing |
| 198 | workloads. |
| 199 | |
| 200 | :::caution[Changing how you reach the same cluster] |
| 201 | A replacement assumes the old and new connections are different |
| 202 | clusters. Sometimes they reach the **same** cluster. For example, you |
| 203 | renamed the kubeconfig context, moved the file, rotated a `token` or |
| 204 | client certificate, or switched from a kubeconfig to a raw |
| 205 | connection. Then the new objects have the same names as the old |
| 206 | ones, and deleting the old generation removes them. The deploy |
| 207 | succeeds but the objects are gone. Run `alchemy deploy --force` right |
| 208 | after such a change to apply them again. Prefer kubeconfig or exec |
| 209 | connections, because their identity doesn't change when credentials |
| 210 | rotate. |
| 211 | ::: |
| 212 | |
| 213 | ## When no adapter is registered |
| 214 | |
| 215 | Each `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. |
| 218 | For example, EKS clusters need `AWS.providers()` in the Stack. If a |
| 219 | deploy fails with *No Kubernetes cluster adapter is registered for |
| 220 | auth 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. |