Part 5: Deploy to Your Own Cluster
Everything you built in Parts 1–4 runs on a cluster on your machine.
None of it is specific to that cluster. The Deployment, the Effect
Jobs, and the Helm chart only need a cluster to run on. In this part
you’ll point the stack at a cluster of your own, while keeping the
local one for development.
Choose a cluster
Section titled “Choose a cluster”You need a cluster you can reach with kubectl. If you don’t have
one, create it with your platform’s tools and follow its section of
Setup:
- GKE.
gcloud container clusters get-credentialswrites the context. See Setup → GKE. - AKS.
az aks get-credentialswrites the context. See Setup → AKS. - k3s, RKE2, DigitalOcean, and others. Use the kubeconfig the platform gives you. See Setup → k3s, RKE2, and other distributions.
- Amazon EKS. Create the cluster in this same Stack. See Amazon EKS.
Then note its context name:
kubectl config get-contextsThe rest of this part uses a context named prod.
Choose a registry
Section titled “Choose a registry”The smoke test and health check are built from your code, so they
need a registry the cluster’s nodes can pull from. The local cluster
came with one. For yours, pick a repository you can push to, such as
ghcr.io/<you> on GitHub, and log in:
echo $GITHUB_TOKEN | docker login ghcr.io -u <you> --password-stdinMake the pushed images readable by the cluster. Use a public package, or pull credentials for a private one. See Container registries.
Pick the cluster by stage
Section titled “Pick the cluster by stage”Every Alchemy deploy targets a stage. By default that’s your own
live_<you> stage, or you can name one with --stage. Keep the
local cluster for your stage and use the hosted cluster for prod. In src/infra.ts,
replace the Cluster declaration:
import { Stage } from "alchemy";import * as Kubernetes from "alchemy/Kubernetes";import * as Effect from "effect/Effect";
export const Cluster = Kubernetes.LocalCluster("Cluster", { name: "alchemy",});export const Cluster = Effect.gen(function* () { const stage = yield* Stage; if (stage === "prod") { return Kubernetes.KubeConfig({ context: "prod", registry: { server: "ghcr.io/you" }, }); } return yield* Kubernetes.LocalCluster("Cluster", { name: "alchemy" });});For prod, Kubernetes.KubeConfig connects through the prod
context and pushes images to ghcr.io/you. Every other stage keeps
the local cluster. Nothing else changes. The Namespace, Deployment,
Jobs, and chart all take whichever cluster Cluster returns.
Set the node architecture
Section titled “Set the node architecture”Images are built for amd64 nodes by default. If your cluster runs
Arm nodes, such as AWS Graviton or Ampere, say so:
return Kubernetes.KubeConfig({ context: "prod", registry: { server: "ghcr.io/you" }, architecture: "arm64",});Stop returning the local context
Section titled “Stop returning the local context”cluster.context only exists on the local cluster. Remove it from
the Stack’s outputs in alchemy.run.ts:
return { context: cluster.context, namespace: web.namespace, service: web.serviceName, smokeTest: smokeTest.jobName,};Deploy to prod
Section titled “Deploy to prod”bun alchemy deploy --stage prodnpm run alchemy deploy -- --stage prodpnpm alchemy deploy --stage prodyarn alchemy deploy --stage prodPlan: 5 to create + HealthCheck (Kubernetes.Job) + MetricsServer (Kubernetes.HelmChart) + Namespace (Kubernetes.Manifest) + SmokeTest (Kubernetes.Job) + Web (Kubernetes.Deployment) Proceed? ◉ Yes ○ No Pushed ghcr.io/you/mycluster-smoketest-prod-…:7a31fba1… Pushed ghcr.io/you/mycluster-healthcheck-prod-…:27bde98e… ✓ Namespace (Kubernetes.Manifest) created ✓ MetricsServer (Kubernetes.HelmChart) created ✓ Web (Kubernetes.Deployment) created ✓ SmokeTest (Kubernetes.Job) created ✓ HealthCheck (Kubernetes.Job) created
prod has its own state, so this deploy creates everything on the
hosted cluster and leaves your local stage alone. There’s no
LocalCluster in the plan, because the prod stage never declares
one.
Check the hosted cluster
Section titled “Check the hosted cluster”The same commands work, with the prod context:
kubectl --context prod -n my-app get deployments,jobs,cronjobskubectl --context prod -n my-app logs job/<smokeTest>Skip what the platform already has
Section titled “Skip what the platform already has”Many hosted clusters include metrics-server, and applying the chart over it would take ownership of its objects. Install the chart only where it’s missing:
import { Stage } from "alchemy";import * as Kubernetes from "alchemy/Kubernetes";yield* HealthCheck;
const metricsServer = yield* Kubernetes.HelmChart("MetricsServer", { cluster, // ...});const stage = yield* Stage;if (stage !== "prod") { yield* Kubernetes.HelmChart("MetricsServer", { cluster, // ... });}Deploying prod again now plans the chart for deletion there.
Clean up the local cluster
Section titled “Clean up the local cluster”Destroy your own stage to remove everything on your machine, including the kind cluster and its registry:
bun alchemy destroynpm run alchemy destroypnpm alchemy destroyyarn alchemy destroyAlchemy deletes the resources in reverse dependency order and the
cluster last. The next alchemy deploy recreates it in about 30
seconds. Run alchemy destroy --stage prod when you want to remove
the hosted deployment too.
You started with an empty directory and now have:
- A stack that runs on a local cluster for development and on a
hosted cluster in
prod - Effect programs built and pushed to whichever registry the cluster uses
- A Deployment, a Helm chart, and Jobs that don’t depend on where they run
Where next
Section titled “Where next”- Setup. Connect to more kinds of clusters, check RBAC permissions, and deploy from CI.
- Container registries. Push credentials, pull access, and node architecture.
- Deployments. Effect HTTP servers, Service types, and the pod template escape hatch.
- Jobs & CronJobs. Everything about image and Effect Jobs.
- Stages. Per-developer, preview, and production stages.
- Amazon EKS. Create the cluster in the same Stack and use AWS bindings.