1---
2title: Setup
3description: Install Alchemy and connect it to a Kubernetes cluster, whether a local cluster on your machine, any cluster in your kubeconfig, or an explicit connection for CI. Then add a registry for images built from your code.
4---
5
6import { Code, Tabs, TabItem } from "@astrojs/starlight/components";
7import { effectVersion } from "../../../versions";
8
9export const pkgs = `"alchemy@latest" "effect@${effectVersion}" "@effect/platform-bun@${effectVersion}" "@effect/platform-node@${effectVersion}"`;
10
11Alchemy deploys to any Kubernetes cluster whose API server it can
12reach. That includes a cluster on your machine, GKE, AKS, EKS,
13DigitalOcean, k3s, or an on-prem fleet. This page covers what you
14need before the first deploy. You need the alchemy package, a
15cluster to deploy to, a registry for images built from your code,
16and permission to manage objects in the cluster.
17
18## Prerequisites
19
20- [Bun](https://bun.sh) or Node.js 22+
21- [Docker](https://docs.docker.com/get-started/get-docker/), to build
22 images from your code and to run a local cluster
23- A cluster. Use [kind](https://kind.sigs.k8s.io/docs/user/quick-start/#installation)
24 for a local one, or credentials for a hosted one. Hosted
25 credentials are usually a kubeconfig context that already works
26 with `kubectl`.
27- [`helm`](https://helm.sh/docs/intro/install/), only if you install
28 [Helm charts](/kubernetes/objects/helm-charts)
29- [`kubectl`](https://kubernetes.io/docs/tasks/tools/) is useful for
30 checking access and inspecting what Alchemy deploys. Alchemy talks
31 to the Kubernetes API directly and never runs it.
32
33## Create a project directory
34
35<Tabs syncKey="pkgManager">
36 <TabItem label="bun" icon="bun">
37 <Code code="mkdir my-cluster && cd my-cluster && bun init -y" lang="sh" />
38 </TabItem>
39 <TabItem label="npm" icon="npm">
40 <Code code="mkdir my-cluster && cd my-cluster && npm init -y" lang="sh" />
41 </TabItem>
42 <TabItem label="pnpm" icon="pnpm">
43 <Code code="mkdir my-cluster && cd my-cluster && pnpm init" lang="sh" />
44 </TabItem>
45 <TabItem label="yarn" icon="seti:yarn">
46 <Code code="mkdir my-cluster && cd my-cluster && yarn init -y" lang="sh" />
47 </TabItem>
48</Tabs>
49
50## Install
51
52<Tabs syncKey="pkgManager">
53 <TabItem label="bun" icon="bun">
54 <Code code={`bun add ${pkgs}`} lang="sh" />
55 </TabItem>
56 <TabItem label="npm" icon="npm">
57 <Code code={`npm install ${pkgs}`} lang="sh" />
58 </TabItem>
59 <TabItem label="pnpm" icon="pnpm">
60 <Code code={`pnpm add ${pkgs}`} lang="sh" />
61 </TabItem>
62 <TabItem label="yarn" icon="seti:yarn">
63 <Code code={`yarn add ${pkgs}`} lang="sh" />
64 </TabItem>
65</Tabs>
66
67## Add the providers
68
69Every Stack that uses Kubernetes resources needs
70`Kubernetes.providers()`:
71
72```typescript
73// alchemy.run.ts
74import * as Alchemy from "alchemy";
75import * as Kubernetes from "alchemy/Kubernetes";
76import * as Effect from "effect/Effect";
77
78export default Alchemy.Stack(
79 "MyApp",
80 {
81 providers: Kubernetes.providers(),
82 state: Alchemy.localState(),
83 },
84 Effect.gen(function* () {
85 // Kubernetes resources go here
86 }),
87);
88```
89
90It registers the `LocalCluster`, `Deployment`, `Job`, `Manifest`, and
91`HelmChart` providers, plus authentication for kubeconfig,
92bearer-token, client-certificate, and exec-plugin connections. There is no
93`alchemy profile` step for Kubernetes. Credentials come from the
94connection each resource carries.
95
96## Connect to your cluster
97
98Each resource's `cluster` prop says where it runs and how Alchemy
99authenticates. Find your kind of cluster below.
100
101### A cluster on your machine
102
103If you don't have a cluster yet, let Alchemy create one.
104[`Kubernetes.LocalCluster`](/kubernetes/clusters/local) runs a
105[kind](https://kind.sigs.k8s.io) cluster in Docker with an image
106registry built in:
107
108```typescript
109const cluster = yield* Kubernetes.LocalCluster("Cluster", {
110 name: "alchemy",
111});
112
113const web = yield* Kubernetes.Deployment("Web", {
114 cluster,
115 image: "ghcr.io/stefanprodan/podinfo:6.15.0",
116 port: 9898,
117 serviceType: "ClusterIP",
118});
119```
120
121Install kind first. On macOS run `brew install kind`, or see
122[kind's install guide](https://kind.sigs.k8s.io/docs/user/quick-start/#installation).
123The [tutorial](/kubernetes/tutorial/part-1) uses a local cluster
124throughout.
125
126### Any cluster with a kubeconfig
127
128If `kubectl` already works against the cluster, Alchemy can use the
129same kubeconfig context. List your contexts and pick the one you
130deploy to:
131
132```sh
133kubectl config get-contexts
134```
135
136Pass its name in your program:
137
138```typescript
139const cluster = Kubernetes.KubeConfig({ context: "prod-us-east" });
140
141const web = yield* Kubernetes.Deployment("Web", {
142 cluster,
143 image: "ghcr.io/acme/web:1.4.0",
144 port: 8080,
145});
146```
147
148Alchemy reads the kubeconfig at deploy time. It uses `path` if you
149pass one, otherwise the first entry of `$KUBECONFIG`, otherwise
150`~/.kube/config`. Name the context explicitly, because the file's
151`current-context` changes whenever someone runs
152`kubectl config use-context`.
153
154Contexts created by a cloud CLI call an `exec` credential plugin for
155short-lived tokens. The plugin must be installed and logged in on
156every machine that deploys, including CI runners.
157
158### GKE
159
160Write a context with the Google Cloud CLI:
161
162```sh
163gcloud container clusters get-credentials <cluster> --region <region>
164```
165
166The context is named `gke_<project>_<region>_<cluster>` and calls
167`gke-gcloud-auth-plugin`. Install the plugin with
168`gcloud components install gke-gcloud-auth-plugin`.
169
170### AKS
171
172Write a context with the Azure CLI:
173
174```sh
175az aks get-credentials --name <cluster> --resource-group <group>
176```
177
178The context is named after the cluster. For clusters that use
179Microsoft Entra ID authentication, install
180[`kubelogin`](https://azure.github.io/kubelogin/) and convert the
181context to use your Azure CLI login:
182
183```sh
184kubelogin convert-kubeconfig -l azurecli
185```
186
187### EKS
188
189EKS clusters can be created and targeted from the same Stack, with
190ECR images and AWS bindings built in. See
191[Amazon EKS](/kubernetes/clusters/eks).
192
193### k3s, RKE2, and other distributions
194
195Self-managed distributions write a kubeconfig on the server. k3s
196writes it to `/etc/rancher/k3s/k3s.yaml` and RKE2 writes it to
197`/etc/rancher/rke2/rke2.yaml`. Copy it to the deploying machine and
198change its `server` address from `127.0.0.1` to one your machine can
199reach. Then point `path` at it:
200
201```typescript
202const cluster = Kubernetes.KubeConfig({
203 path: "./k3s.yaml",
204 context: "default",
205});
206```
207
208Hosted Kubernetes services such as DigitalOcean, Linode, and Scaleway
209provide a kubeconfig from their console or CLI. Use it the same way.
210
211### Existing local clusters
212
213A kind, minikube, or Docker Desktop cluster you started yourself adds
214a context to `~/.kube/config`. The context is named `kind-<name>`,
215`minikube`, or `docker-desktop`, and it works like any other
216kubeconfig cluster. Local clusters usually have no load balancer, so
217use `serviceType: "ClusterIP"` or `"NodePort"` for Deployments.
218
219### Without a kubeconfig
220
221Some environments have no kubeconfig, such as a CI job or a
222bootstrap script. There, describe the connection directly with an
223endpoint and an `auth` descriptor:
224
225```typescript
226import * as Config from "effect/Config";
227
228const token = yield* Config.String("K8S_TOKEN");
229
230const cluster: Kubernetes.Connection = {
231 endpoint: "https://k8s.internal.example.com:6443",
232 certificateAuthorityData: "LS0tLS1CRUdJTi…", // base64-encoded PEM
233 auth: { kind: "token", token },
234};
235```
236
237`auth` can also be a client certificate (`kind: "client-cert"`) or an
238exec plugin (`kind: "exec"`). A token or certificate in a raw
239connection is stored in Alchemy's state. Changing it replaces the
240resources that use it, so prefer kubeconfig or exec connections for
241long-lived stacks. See
242[Connecting to clusters](/kubernetes/clusters/connecting) for every
243option.
244
245## Check your permissions
246
247Alchemy needs RBAC permission to `get`, `create`, `patch`, and
248`delete` the objects each resource manages, in the namespaces you
249deploy to:
250
251- `Deployment` writes a ServiceAccount, a Deployment, and a Service.
252- `Job` writes a ServiceAccount and a Job, or a CronJob when it has a
253 `schedule`.
254- `Manifest` writes the one object in its `manifest`.
255- `HelmChart` writes every object the chart renders, often including
256 cluster-scoped ClusterRoles, CRDs, and webhook configurations.
257
258Namespaces, ClusterRoles, CRDs, and other cluster-scoped kinds need
259cluster-level permissions. Workload namespaces must already exist or
260be created in the same Stack with a
261[Manifest](/kubernetes/objects/manifests).
262
263Check before deploying with `kubectl auth can-i`, using the same
264context:
265
266```sh
267kubectl --context prod-us-east auth can-i create deployments --namespace apps
268kubectl --context prod-us-east auth can-i patch services --namespace apps
269```
270
271## Add a registry for your code
272
273Workloads that run a pre-built `image` need nothing else. Workloads
274built from your code, such as an Effect program (`main`) or a
275Dockerfile (`context`), are built on your machine. Alchemy pushes
276them to a registry the cluster's nodes can pull from. Add one to the
277connection:
278
279```typescript
280const cluster = Kubernetes.KubeConfig({
281 context: "prod-us-east",
282 registry: { server: "ghcr.io/acme" },
283});
284```
285
286Alchemy pushes with your machine's Docker login unless you pass
287credentials. `LocalCluster` and EKS provide a registry themselves.
288See [Container registries](/kubernetes/clusters/registries).
289
290## Deploy from CI
291
292A CI runner needs the same things as your laptop. It needs network
293access to the API server, credentials, and any exec plugin your
294context calls. Common setups:
295
296- Authenticate the runner to your cloud, for example with GitHub
297 OIDC. Then create the kubeconfig with the cloud CLI command above.
298- Log the runner's Docker in to your registry (`docker login`) before
299 deploying workloads built from your code.
300- Store a kubeconfig file as a secret, write it to disk, and set
301 `KUBECONFIG` to its path.
302- Pass `path` and `context` from environment variables so the same
303 program targets a different cluster per stage.
304
305Use a remote [state store](/state-store) so CI and your team see the
306same deployments. See [CI](/environments/ci) for running
307`alchemy deploy` in GitHub Actions.
308
309## State storage
310
311Kubernetes has no state backend of its own, so pick one of:
312
313- **`Alchemy.localState()`.** State lives on disk under `.alchemy/`
314 next to your code. This is fine for trying things out alone, but CI
315 and teammates can't see it.
316- **`postgresState()`.** State lives in a Postgres database you
317 already run, such as a managed database from your Kubernetes
318 provider. It locks each stage while a deploy runs. See
319 [Postgres state store](/state-store#postgres-state-store).
320- **`Cloudflare.state()` or `AWS.state()`.** State lives in your
321 Cloudflare or AWS account. See [State Store](/state-store).
322- **Your own store.** Implement the state service over S3-compatible
323 storage, Redis, or anything else. See
324 [Custom state store](/state-store/custom-state-store).
325
326Each resource stores its cluster connection in state so
327`alchemy destroy` can reach the cluster later, even after the code
328that declared it is gone.
329
330## Next steps
331
332- [Tutorial part 1](/kubernetes/tutorial/part-1) deploys a
333 Deployment, step by step.
334- [Connecting to clusters](/kubernetes/clusters/connecting) covers
335 every connection form, multi-cluster stacks, and moving between
336 clusters.
337- [Kubernetes overview](/kubernetes) is the map of resources and
338 guides.