Just use the Sailbox
Create a Sailbox and set it up the way you would any Linux machine: copy files in withsail box cp, run commands with sail box exec, or
enable SSH and use scp and rsync.
When it looks right, checkpoint it. Every Sailbox you start from that
checkpoint boots with the same disk and memory already in place:
Bring your own base image
PointSailbox.create at an image on a public registry. Sail pulls it and
layers what a Sailbox needs on top.
docker pull. The image must be
Debian- or Ubuntu-based and publicly pullable from docker.io, ghcr.io,
public.ecr.aws, or quay.io. Private registries are not supported.
How Sail treats your image
ENV,WORKDIR, andUSERbecome the defaults for every command you run in the Sailbox. Commands run as the image’sUSERwhen it sets one and as root otherwise. Passuser="0:0"on a call to run as root anyway.ENTRYPOINTandCMDare not run. A Sailbox manages its own processes. Your commands say what to execute.- The image keeps its own
python3. Sail never installs another Python over it, because a pinned interpreter would shadow the one the image was built around. - A few paths are Sail’s. The build replaces
/initand some Sail-owned files under/usr/local/bin, and writes configuration under/etc/sailboxand at/etc/profile.d/sailbox-env.sh. Everything else is left alone. - The Sailbox runs on the architecture the image was built for. An image
published for both amd64 and arm64 runs on amd64. Pass
architectureto require one.
Build an image from a Dockerfile
Already have a Dockerfile? Sail builds it for you.context_dir is where COPY and ADD read from, with .dockerignore
honored. Every FROM and COPY --from must reference a public image on one
of the registries above, and the result must be Debian- or Ubuntu-based. When a
step fails, the error includes that step’s output. When Sail stops a build
for running too long, the error lists the slowest finished steps and the
output of the steps that were still running.
Dockerfile support details
Dockerfile support details
- Pass a path or the Dockerfile text itself (
contents=in Python,{ contents }in TypeScript,DockerfileInput::Contentsin Rust). - The build runs for amd64 unless you pass
architecture.build_argsfillARGinstructions like--build-arg. Names starting withBUILDKIT_and Docker’s proxy variables (HTTP_PROXYand friends) are rejected; aRUNstep can set a proxy for itself. - A
Dockerfile.dockerignorenext to the Dockerfile replaces the context’s.dockerignore, andignorepatterns you pass take precedence over both. Python snapshots the context when you callfrom_dockerfile. TypeScript and Rust do it when the image is built. Edits after that point are not included in the build. - The context preserves file modes, empty directories, and symlinks. Hard
links become separate files. Setuid, setgid, and sticky bits, named pipes,
device nodes, and mode
000entries are rejected. Sockets are skipped. - Up to 25 different images per Dockerfile across
FROMandCOPY --from. - Multi-stage builds work, and
targetnames the stage to stop at, likedocker build --target.tmpfsmounts andbindmounts from the context or another stage work.RUN --mountof typecache,secret, orssh, abindmount whosefromnames another image, mount options that are variable references, andONBUILD(in your file or a base image) are rejected. - A
# syntax=line may declaredocker/dockerfile:1or a release from 1.4 through 1.22.0. Anything else is rejected. The line does not change how the file is built.
Build an image from a Compose project
Have a Docker Compose project? Sail builds it into a Sailbox image that has Docker, the project’s service images, and a copy of the project. Create a Sailbox from that image and callcompose_up to start the project.
docker compose in. Each service needs a
build inside that directory or a public image on one of the registries
above. The image contains the project at /root/<its directory name>, and
compose_up runs docker compose up there. After
you change the project, call from_compose again to build a new image.
Compose support details
Compose support details
- Compose fills a
${VAR}in a compose file from the shell it runs in, then from the project’s.env. The build runs on Sail, soenvstands in for your shell: a variable inenvis used ahead of the same one in.env, and a project whose.envdefines everything it needs takes noenvat all. - The build reads only the fields that choose and build the service images:
a service’s
image,build,platform, andextends, and the project’sname. A${VAR}in those fields must have a value in.envor inenv. A${VAR}anywhere else, such as in a service’senvironmentorports, is filled when the project starts, from.envand from theenvpassed tocompose_up. envcan hold every variable the project runs with, secrets included. Only the variables the build reads are sent to Sail, with those the project’s.envreferences. The rest stay on your machine, and the image does not hold them.- Only the project directory is built and copied. A
COMPOSE_FILEentry that is absolute or leads outside it is rejected. A bind mount whose source is outside it mounts an empty directory unless you upload that source first withupload_dir. A../datasource resolves to/root/data. fileslists the compose files to build from, in place of the ones Compose finds in the directory, andproject_namenames the project. A listed file may be outside the project directory.compose_updoes not know which files or name the image was built with, so pass it the same name and the same files in the same order, as paths on the Sailbox. A listed file inside the project directory is in the image’s copy at the same relative place. One outside it is not in the image, so write it to the Sailbox beforecompose_up.- The copy leaves out what the project root’s
.dockerignoreexcludes, except the compose files, the.envfiles Compose reads, and every.dockerignore, which are always kept. A service’s Dockerfile and the files it copies must not be excluded. Each service’s own.dockerignoreapplies to its build as usual. - Python reads the project when you call
from_compose. TypeScript and Rust read it when the image is built. Edits made after that are not included in the build. - The image builds for amd64 unless you pass
architecture. A project may merge up to 16 compose files and have up to 32 services. It may reference up to 64 different registry images, and its compose files may total 1 MiB. The project copy and the service images are stored on the Sailbox image’s disk, so the whole image must fit in the largest Sailbox disk, 1024 GiB. - Services under
profilesare built too. Abuildmay setcontext,dockerfile,dockerfile_inline,args, andtarget, and its Dockerfile follows the Dockerfile rules above, except that the result need not be Debian- or Ubuntu-based. Otherbuildkeys,include, anextendsthat reads another file,models, and aplatformthat does not match the image’s architecture are rejected. So is animagepinned by digest. Use a tag instead. - An
imagea service pulls is pinned the way aFROMtag is (see Building and caching). - The Rust SDK doesn’t have
compose_up. Useexecto run the command thatcompose_up_commandreturns.
Build an image in code
No Dockerfile? Start from Sail’s Debian base and chain the steps you need. Each step returns a new definition, so one base can serve several variants.
The same steps chain onto a registry image or a Dockerfile image too. Remote
paths must be absolute and cannot contain a space,
$, ", or \. Variable
names must start with a letter or _ and contain only letters, digits, and
_.
sail.Image.debian_amd64 and debian_arm64 also install a Python matching
your local interpreter, so that
@sail.function can run your Python
functions inside the Sailbox. Use sail.Image.debian("amd64", install_python=False) to keep the base’s stock python3.Run services with systemd
Callwith_systemd on an image and every Sailbox created from it runs systemd
as its init system. A service you enable in the image starts with the Sailbox,
and a unit that sets Restart=always is restarted whenever it exits.
worker.service
- Call it anywhere in the chain. The build installs systemd before your
build steps run, so a step can run
systemctl enable. - Services start when the Sailbox is created. A service may still be
starting when
createreturns. - Any image can use it except a Compose project. It works on Sail’s Debian base, a registry image, and a Dockerfile image. The devbox image always runs systemd.
with_systemd for how timers and
reboot behave in a Sailbox.
Building and caching
Pass an image definition toSailbox.create and Sail uploads any local files,
builds the image if it has not been built before, and starts the Sailbox from
it. image_build_timeout (imageBuildTimeoutSeconds in TypeScript) bounds
the build, retries included. In Rust the timeout is the duration passed to
build_image_definition. To build ahead of time instead, call build on the
definition and pass the result to Sailbox.create. In Rust,
build_image_definition is the ahead-of-time build.
- Builds are cached by content, per organization. The same base, steps, variables, and file contents reuse the existing image.
- Tags are pinned, per organization. The first build from a tag such as
python:3.13, or from aFROMline, pins the version the tag pointed at, and later builds keep it even after the tag moves upstream. Passforce_build(forceBuildin TypeScript,BuildMode::ForceBuildin Rust) to look the tag up again. That moves the pin for the whole organization. Sailboxes that already exist keep the version they started on. A digest (name@sha256:...) never moves. - The first build of a large image downloads all of it. Later builds usually reuse its layers.
Your image may boot when you are not looking
Your image may boot when you are not looking
After a build, and periodically while an image is in use, Sail boots it
outside any Sailbox to capture a start snapshot. Sailboxes created from the
image resume from that snapshot instead of cold-booting, so they start
quickly at every size.This makes two things part of the image contract:
- Early boot runs during every hidden boot. In an image that runs
systemd, units that are part of early boot, such as those wanted by
sysinit.target, run there. They must be safe to run repeatedly, outside any Sailbox. The services you enable do not run there; they start in each Sailbox when it is created. Your entrypoint and the commands you run in a Sailbox never run during a hidden boot. - State written at boot is shared. Whatever boot leaves on disk or in memory is in the snapshot every Sailbox resumes from. Generate per-instance identity (machine IDs, nonces, cached credentials) at runtime, for example in your application’s entrypoint, not at boot. Environment variables, networking, and Sail-managed credentials are applied per Sailbox after the resume, so they behave the same either way.