Skip to content

Deployment

Source: src/Kubernetes/Deployment.ts

A replicated Kubernetes server on any cluster — the Kubernetes analog of AWS.ECS.Service.

Deployment provisions a Kubernetes Deployment + Service (+ ServiceAccount) via server-side apply, plus — through the target cluster’s platform adapter — workload identity and a container image from exactly one of three sources flat on props: main (bundle an inline Effect program), context (build your own Dockerfile), or image (a pre-built registry reference). On AWS.EKS.Cluster targets it accepts the same { env, policyStatements } host binding contract as AWS.Lambda.Function and AWS.ECS.Task: every AWS Binding.Service (S3, DynamoDB, SQS, …) attaches env vars to the pod spec and IAM policy statements to a generated pod-identity role. On registry-less clusters (Kubernetes.KubeConfig(...)) run pre-built image references and bind through environment variables.

Remote image on EKS (external — no Effect runtime in the container)

const cluster = yield* AWS.EKS.Cluster("Cluster", { compute: "auto" });
const nginx = yield* Kubernetes.Deployment("Nginx", {
cluster,
image: "nginx:1.27",
namespace: "default",
replicas: 3,
port: 80,
serviceType: "LoadBalancer",
});
nginx.url; // LB URL, e.g. "http://k8s-….elb.amazonaws.com"
nginx.deploymentName; // K8s-native attrs

Any cluster via kubeconfig

const local = Kubernetes.KubeConfig({ context: "kind-dev" });
const api = yield* Kubernetes.Deployment("Api", {
cluster: local,
image: "ghcr.io/acme/api:v3",
port: 8080,
serviceType: "ClusterIP",
});

Build your own Dockerfile

const legacy = yield* Kubernetes.Deployment("LegacyApp", {
cluster,
context: "./legacy",
replicas: 2,
port: 8080,
});

Inline Effect server with a DynamoDB binding (EKS)

const api = yield* Kubernetes.Deployment(
"Api",
{ cluster, main: import.meta.url, port: 3000, replicas: 2 },
Effect.gen(function* () {
const putItem = yield* AWS.DynamoDB.PutItem(table);
return {
fetch: Effect.gen(function* () {
yield* putItem({ Item: { id: { S: "1" } } });
return HttpServerResponse.text("ok");
}),
};
}).pipe(Effect.provide(AWS.DynamoDB.PutItemHttp)),
);

Tagged Effect server

export class Api extends Kubernetes.Deployment<Api, {
health: () => Effect.Effect<string>;
}>()("Api") {}
export default Api.make(
{ cluster, main: import.meta.url, port: 3000 },
Effect.gen(function* () {
return {
fetch: Effect.gen(function* () {
return HttpServerResponse.text("ok");
}),
health: () => Effect.succeed("ok"),
};
}),
);

main is bundled with rolldown at deploy time. Top-level calls in the effect, @effect/*, alchemy, @alchemy.run/*, and @distilled.cloud/* packages receive #__PURE__ annotations by default, so anything the deployment doesn’t use from those packages is tree-shaken out of the bundle. Any other package — including your own app — is left untouched unless you list it explicitly.

Treat additional packages as pure

Pass package names (or picomatch globs) via build.pure.packages to annotate them in addition to the defaults.

{
main: import.meta.url,
build: {
pure: { packages: ["my-lib", "@my-scope/*"] },
},
}

Listing a package annotates calls whose result is bound (variable initializers, exports) — safe anywhere. If a listed package also declares "sideEffects": false (or []) in its package.json, that combination opts it into full annotation: top-level calls whose result is discarded (e.g. router.on("/path", handler) registrations) are also marked pure and deleted under minification when unused. Only list a sideEffects: false package if its modules really are free of meaningful top-level side effects. The effect, alchemy, and @distilled.cloud defaults declare exactly that, on purpose — their modules are designed to be fully tree-shakeable.

Disable pure annotations

{
main: import.meta.url,
build: { pure: false },
}
const tuned = yield* Kubernetes.Deployment("Api", {
cluster,
main: import.meta.url,
port: 3000,
podTemplate: {
spec: {
tolerations: [{ key: "gpu", operator: "Exists" }],
nodeSelector: { pool: "arm" },
},
},
});