Skip to content

Images on cloud runtimes

Cloud container runtimes don’t reach your engine. They pull images from a registry, so an image built on your machine has to be pushed before a cloud can run it. What you give the runtime is a reference: a tag like ghcr.io/acme/api:latest, or a digest like ghcr.io/acme/api@sha256:9c2e…, which names exactly one image.

Build and push with Docker.Image, then pass its imageRef as the runtime’s image:

const image = yield* Docker.Image("api", {
name: "acme/api",
build: { context: "./api" },
registry: {
server: "ghcr.io",
username: "acme",
password: yield* Config.Redacted("GITHUB_TOKEN"),
},
});
const task = yield* AWS.ECS.Task("Api", {
image: image.imageRef,
});

image.repoDigest holds the digest, for when a moving tag isn’t precise enough. The same reference works as the image of a Cloudflare Container, a container in a Cloud Run service or a Kubernetes Deployment or Job. An ECS Task copies it into ECR before it runs. A private registry needs its credentials on the runtime’s side as well; see each runtime’s page.

You often don’t need Docker.Image at all. ECS, Cloudflare Containers and Kubernetes workloads build your Dockerfile themselves from a context prop, and Cloud Run and the others bundle an Effect program from main. They build with the same local Docker, then push to their own registry:

const task = yield* AWS.ECS.Task("Api", {
context: "./api",
});

Reach for Docker.Image when you want to pick the registry yourself, or share one image between several runtimes or stages.

A runtime that bundles your Effect program with main takes a dockerfile for the base it runs on. Dockerfile.inline writes it in the Stack, for a base plus a few system packages:

import * as Dockerfile from "@alchemy.run/docker/Dockerfile";
const transcoder = yield* AWS.ECS.Task(
"Transcoder",
{
main: import.meta.url,
dockerfile: Dockerfile.inline`
FROM oven/bun:1
RUN apt-get update && apt-get install -y ffmpeg
`,
},
impl,
);

Interpolate another resource’s output, and the runtime depends on it, rebuilding when it changes:

const base = yield* AWS.ECR.Image("MlBase", { context: "./ml-base" });
const trainer = yield* AWS.ECS.Task(
"Trainer",
{
main: import.meta.url,
dockerfile: Dockerfile.inline`
FROM ${base.imageUri}
RUN pip install -r /opt/requirements.txt
`,
},
impl,
);

Anything in a Dockerfile is baked into the image’s layers, so never interpolate a secret. Use a build secret instead.

Every runtime that builds takes the same cache option as Docker.Image. See Build caches.