Skip to content

Kubernetes.Workloads reference

Source: src/Kubernetes/Deployment.ts

A replicated Kubernetes server on any cluster, running a container image or an HTTP server written in Effect.

Deployment provisions a Kubernetes Deployment + Service (+ ServiceAccount) via server-side apply, with the 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). main and context images are built on the deploying machine and pushed to the connection’s registry (Kubernetes.LocalCluster includes one; EKS uses ECR; GKE uses Artifact Registry). Bindings attach environment variables on any cluster; cloud credential grants need a platform with workload identity. On AWS.EKS.Cluster targets it accepts the { env, policyStatements } contract of AWS.Lambda.Function: IAM policy statements go to a generated pod-identity role. On GCP.Container.Cluster targets it accepts the { env, iam } contract of GCP.Run.Service: IAM roles are granted to the Kubernetes ServiceAccount’s Workload Identity Federation principal.

Run an image on a local cluster

const cluster = yield* Kubernetes.LocalCluster("Cluster", {
name: "alchemy",
});
const web = yield* Kubernetes.Deployment("Web", {
cluster,
image: "ghcr.io/stefanprodan/podinfo:6.15.0",
replicas: 2,
port: 9898,
serviceType: "ClusterIP",
});
web.serviceName; // K8s-native attrs

Any cluster via kubeconfig

const cluster = Kubernetes.KubeConfig({ context: "prod-east" });
const nginx = yield* Kubernetes.Deployment("Nginx", {
cluster,
image: "nginx:1.27",
replicas: 3,
port: 80,
serviceType: "LoadBalancer",
});
nginx.url; // the load balancer's address

Build your own Dockerfile

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

Inline Effect server

const api = yield* Kubernetes.Deployment(
"Api",
{ cluster, main: import.meta.url, port: 3000, replicas: 2 },
Effect.gen(function* () {
return {
fetch: Effect.succeed(HttpServerResponse.text("ok")),
};
}),
);

Push to your own registry

const cluster = Kubernetes.KubeConfig({
context: "prod-east",
registry: { server: "ghcr.io/acme" },
});
const api = yield* Kubernetes.Deployment(
"Api",
{ cluster, main: import.meta.url, port: 3000 },
Effect.gen(function* () {
return {
fetch: Effect.succeed(HttpServerResponse.text("ok")),
};
}),
);

DynamoDB binding on EKS

const cluster = yield* AWS.EKS.Cluster("Cluster", { compute: "auto" });
const api = yield* Kubernetes.Deployment(
"Api",
{ cluster, main: import.meta.url, port: 3000 },
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. Unused code is tree-shaken. effect, alchemy, and @distilled.cloud are marked pure so unused parts prune more aggressively. Your app is not marked pure.

Mark additional packages as pure

Only list packages with no top-level side effects.

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

Turn it off

{
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" },
},
},
});

Source: src/Kubernetes/Job.ts

Run-to-completion work on any Kubernetes cluster, as a container image or an Effect program.

Job provisions a Kubernetes Job (or CronJob when schedule is set) via server-side apply and a ServiceAccount, 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 whose impl returns { run }), context (build your own Dockerfile), or image (a pre-built registry reference). main and context images are built on the deploying machine and pushed to the connection’s registry (Kubernetes.LocalCluster includes one; EKS uses ECR; GKE uses Artifact Registry). On AWS.EKS.Cluster targets, bindings attach IAM policy statements to a generated pod-identity role; on GCP.Container.Cluster targets, GCP bindings grant their IAM roles to the Kubernetes ServiceAccount’s Workload Identity Federation principal — exactly like Kubernetes.Deployment.

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

const migrate = yield* Kubernetes.Job("DbMigrate", {
cluster,
image: "ghcr.io/acme/migrator:v3",
backoffLimit: 2,
});

Inline Effect program

const cluster = yield* Kubernetes.LocalCluster("Cluster", {
name: "alchemy",
});
const hello = yield* Kubernetes.Job(
"Hello",
{ cluster, main: import.meta.url, backoffLimit: 2 },
Effect.gen(function* () {
return {
run: Effect.log("hello from a Job"),
};
}),
);

Inline Effect program with a DynamoDB binding (EKS)

const seed = yield* Kubernetes.Job(
"SeedData",
{ cluster, main: import.meta.url },
Effect.gen(function* () {
const putItem = yield* AWS.DynamoDB.PutItem(table);
return {
run: Effect.gen(function* () {
yield* putItem({ Item: { id: { S: "seed" } } });
}),
};
}).pipe(Effect.provide(AWS.DynamoDB.PutItemHttp)),
);

Tagged Effect program

export class Backfill extends Kubernetes.Job<Backfill, {
progress: () => Effect.Effect<number>;
}>()("Backfill") {}
export default Backfill.make(
{ cluster, main: import.meta.url, backoffLimit: 1 },
Effect.gen(function* () {
return {
run: Effect.gen(function* () { }),
progress: () => Effect.succeed(0),
};
}),
);

main is bundled with rolldown at deploy time. Unused code is tree-shaken. effect, alchemy, and @distilled.cloud are marked pure so unused parts prune more aggressively. Your app is not marked pure.

Mark additional packages as pure

Only list packages with no top-level side effects.

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

Turn it off

{
main: import.meta.url,
build: { pure: false },
}
const nightly = yield* Kubernetes.Job("NightlyBackfill", {
cluster,
main: import.meta.url,
schedule: "0 3 * * *",
});