Container builds
Build an image by composing layers in TypeScript, with no Dockerfile.
A ContainerImage is an image you build by composing it. Every statement a
Dockerfile has is a method, each returns a new image, and nothing is written to
a file that a second tool then has to parse.
Start from a member
import { ContainerImage } from "bldr";
import debian from "debian";
const base = ContainerImage.fromMember(debian, "amd64")
.env("DEBIAN_FRONTEND", "noninteractive")
.run("apt-get update && apt-get install -y --no-install-recommends git && " +
"rm -rf /var/lib/apt/lists/*", { internet: true });A .run() layer is built in a pod with no network. { internet: true } is the
approval for a layer that downloads; leave it off everywhere else, and the layer
stays a function of the image it was built on. On a Debian or Ubuntu base,
new DebianContainerImage(image).aptInstall(["git"]) is the same layer with the
update, the cleanup and the approval already in it.
fromMember takes the image member and the architecture. From there the
methods read like the Dockerfile they replace: .run(), .env(), .copy(),
.workdir(), .user(), .cmd(), .entrypoint(), .label().
Each call returns a new image rather than mutating one, so a base can be shared by several derived images without them affecting each other.
Layer content in
const withTool = base.copy(builtBinary, "/usr/local/bin");.copy() takes a Directory from anywhere in your workspace: another member's
output, a built binary, a generated tree. That is the part a Dockerfile cannot
do without a build context and a lot of care about what is in it.
Publish it
scope.addContainerImage("app", withTool);That registers it as an image target, which the console can show and other
members can consume.
Run a command and keep what it wrote
An image is also where pods come from, which is how you build something with a container rather than building the container itself:
const out = base.pod()
.mount(source, "/src")
.workdir("/src")
.run("make && mkdir -p /out && cp result /out/")
.capture("/out");See Pods for what a pod can be given and what you can take out of it.
Things worth knowing
Order your layers by how often they change. A .run() that installs
packages should come before a .copy() of your source, so editing source does
not re-run the install. This is the same discipline a Dockerfile needs, and for
the same reason.
Pin your base. Declare the base image as a member with a digest, so the image you build today is the image you build next month. Tracking the tag tells you when a newer one exists without silently adopting it.
apt-get needs its cleanup in the same .run(). A separate cleanup layer
removes the files from the filesystem but not from the layer below it, so the
image keeps the weight.