1---
2title: Container registries
3description: Give a Kubernetes connection a registry so Deployments and Jobs built from Effect programs or Dockerfiles are pushed where the cluster's nodes can pull them, such as GHCR, Docker Hub, Artifact Registry, ACR, or a registry of your own.
4---
5
6A Kubernetes node can only run an image it can pull from a registry.
7Workloads that use a pre-built `image` need nothing extra, but a
8`main` Effect program or a `context` Dockerfile is built on your
9machine and has to be pushed somewhere first. The connection's
10`registry` says where:
11
12```typescript
13const cluster = Kubernetes.KubeConfig({
14 context: "prod-us-east",
15 registry: { server: "ghcr.io/acme" },
16});
17
18const worker = yield* Kubernetes.Job(
19 "Worker",
20 { cluster, main: import.meta.url },
21 Effect.gen(function* () {
22 return { run: Effect.log("working") };
23 }),
24);
25```
26
27On deploy, Alchemy bundles the program, builds the image with your
28local Docker, pushes it to `ghcr.io/acme/<name>:<hash>`, and points
29the Job at that reference.
30
31Two kinds of cluster fill in the registry for you:
32
33- [`Kubernetes.LocalCluster`](/kubernetes/clusters/local) includes a
34 local registry.
35- [Amazon EKS](/kubernetes/clusters/eks) pushes each workload to its
36 own ECR repository.
37
38## Choose the server
39
40`server` is the registry host, optionally followed by the namespace
41images go under:
42
43- **GitHub Container Registry.** Use `ghcr.io/<owner>`.
44- **Docker Hub.** Use `docker.io/<user>`.
45- **Google Artifact Registry.** Use
46 `<region>-docker.pkg.dev/<project>/<repository>`.
47- **Azure Container Registry.** Use `<name>.azurecr.io`.
48- **A registry you run.** Use its host and port, for example
49 `registry.internal:5000`.
50
51Each workload pushes to its own repository under `server`, named
52after the stack, stage, and logical ID, and tags every image with a
53hash of its content.
54
55## Authenticate pushes
56
57Without credentials, Alchemy pushes with your machine's own Docker
58login, so any registry you've run `docker login` against works:
59
60```sh
61echo $GITHUB_TOKEN | docker login ghcr.io -u acme-bot --password-stdin
62```
63
64For CI, you can keep the credential in the program instead. Pass
65`username` and `password`:
66
67```typescript
68import * as Config from "effect/Config";
69
70const token = yield* Config.Redacted("GHCR_TOKEN");
71
72const cluster = Kubernetes.KubeConfig({
73 context: "prod-us-east",
74 registry: { server: "ghcr.io/acme", username: "acme-bot", password: token },
75});
76```
77
78The connection is stored in Alchemy's state with each resource. A
79`Redacted` password is stored as a redacted value, but anyone who can
80read the state can read it, so prefer `docker login` on machines you
81control.
82
83## Let the nodes pull
84
85Pushing and pulling are separate. The nodes need read access to the
86repository. Public repositories need nothing. For private ones, use
87your platform's node credentials. GKE and AKS can grant the nodes
88access to Artifact Registry and ACR. You can also use an image-pull
89Secret referenced from the pod template:
90
91```typescript
92const pullSecret = yield* Kubernetes.Manifest("GhcrPullSecret", {
93 cluster,
94 manifest: {
95 apiVersion: "v1",
96 kind: "Secret",
97 type: "kubernetes.io/dockerconfigjson",
98 metadata: { name: "ghcr", namespace: "apps" },
99 data: { ".dockerconfigjson": dockerConfigJsonBase64 },
100 },
101});
102
103const worker = yield* Kubernetes.Job(
104 "Worker",
105 {
106 cluster,
107 main: import.meta.url,
108 namespace: "apps",
109 podTemplate: {
110 spec: { imagePullSecrets: [{ name: pullSecret.name }] },
111 },
112 },
113 Effect.gen(function* () {
114 return { run: Effect.log("working") };
115 }),
116);
117```
118
119## Match the node architecture
120
121Images are built for `linux/amd64` unless you say otherwise. For
122Arm nodes, set `architecture` on the connection so every workload
123builds for them:
124
125```typescript
126const cluster = Kubernetes.KubeConfig({
127 context: "graviton",
128 registry: { server: "ghcr.io/acme" },
129 architecture: "arm64",
130});
131```
132
133A workload's own `architecture` prop overrides the connection's.
134`LocalCluster` sets the architecture to match your machine.
135
136## Old images
137
138Alchemy pushes a new tag whenever a workload's image changes and
139never deletes tags from registries you configure. Use your
140registry's retention or cleanup policies to prune old images.
141
142## Where next
143
144- **[Container images](/kubernetes/workloads/images).** The `image`,
145 `context`, and `main` sources.
146- **[Connecting to clusters](/kubernetes/clusters/connecting).** Every
147 connection form that accepts a `registry`.