Skip to content

Images

An image is the filesystem a Modal container starts from. Modal builds it on its own builders, so a deploy needs no Docker on your machine, and it caches each build by its inputs.

Each image starts from a registry image or from another image, and adds its own layer. A warm base runs your setup once, at deploy, so every sandbox starts with the work done:

Nodenode:22-bookwormWarmfrom Nodeapt-get install gitgit clone · pnpm installNodenode:22-bookwormWarmfrom Nodeapt-get install gitgit clone · pnpm install
Node adds git to a registry image. Warm starts from Node and installs the repository. Sandboxes from Warm start with both layers in place.
src/Images.ts
import * as Modal from "@alchemy.run/modal";
export const Coder = Modal.App("Coder", {});
export const Node = Modal.Image("Node", {
app: Coder,
image: "node:22-bookworm",
commands: ["RUN apt-get update && apt-get install -y git ripgrep"],
});

commands are Dockerfile lines, run on top of the registry image.

src/Images.ts
import * as Dockerfile from "@alchemy.run/docker/Dockerfile";
import * as Modal from "@alchemy.run/modal";
export const Coder = Modal.App("Coder", {});
export const Python = Modal.Image("Python", {
app: Coder,
dockerfile: Dockerfile.inline`
FROM python:3.12-slim
RUN pip install pandas
`,
});

Modal’s builder isn’t Docker. Multi-stage builds, BuildKit mounts, EXPOSE, HEALTHCHECK, LABEL, ONBUILD, STOPSIGNAL and VOLUME fail at plan, naming the line. USER is ignored: containers run as root.

A Dockerfile path works too: dockerfile: "./sandbox/Dockerfile". Files its COPY lines read are uploaded from the Dockerfile’s directory, or from context.

src/Images.ts
export const Warm = Modal.Image("Warm", {
app: Coder,
from: Node,
run: [
"git clone https://github.com/alchemy-run/alchemy /work",
"cd /work && corepack enable && pnpm install",
],
});

run executes in a sandbox at deploy, then saves its files as the image. Changing run or the base builds a new one.

For an image per repository or per user, bind CreateImage and build at run time:

const images = yield* Modal.CreateImage(Coder);
const image = yield* images.build({
image: "node:22-bookworm",
commands: [`RUN git clone https://github.com/${repo} /work`],
});
const sandbox = yield* sandboxes.create({ image });

Modal caches builds by content, so the same inputs return the same image.