Skip to content

Declarative Builder

View as Markdown

Declarative Builder provides a powerful, code-first approach to defining dependencies for Daytona sandboxes. Instead of importing images from a container registry, you can programmatically define them using the Daytona SDK.

The declarative builder system supports two primary workflows:

Create a declarative image by defining the dependencies for the sandbox.

Declarative images are cached for 24 hours, and are automatically reused when running the same script. Thus, subsequent runs on the same runner will be almost instantaneous.

# Define a declarative image with python packages
declarative_image = (
Image.debian_slim("3.12")
.pip_install(["requests", "pytest"])
.workdir("/home/daytona")
)
# Create a new sandbox with the declarative image and stream the build logs
sandbox = daytona.create(
CreateSandboxFromImageParams(image=declarative_image),
timeout=0,
on_snapshot_create_logs=print,
)

Create a pre-built snapshot by building a declarative image and registering it as a snapshot.

# Define the declarative image for the snapshot
image = (
Image.debian_slim("3.12")
.pip_install(["numpy", "pandas"])
.workdir("/home/daytona")
)
# Create and register the snapshot, streaming the build logs
daytona.snapshot.create(
CreateSnapshotParams(name="my-snapshot", image=image),
on_logs=print,
)
# Create a new sandbox from the pre-built snapshot
sandbox = daytona.create(CreateSandboxFromSnapshotParams(snapshot="my-snapshot"))

Daytona provides an option to define images programmatically. Chain the methods below to build a complete image definition in a single fluent call.

  1. Select a base image

    Start from any registry image with Image.base(), or use Image.debian_slim() for a Python-ready Debian image.

  2. Install Python packages

    Add packages with pip_install(), or install from requirements.txt or pyproject.toml using pip_install_from_requirements() and pip_install_from_pyproject().

  3. Add files and directories

    Copy local files into the image with add_local_file() and add_local_dir().

  4. Configure environment

    Set environment variables and the working directory with env() and workdir().

  5. Install system packages

    Use run_commands() to install OS-level CLI tools and libraries not available through pip. Chain apt-get update, install, and cache cleanup with && in a single command to minimize Docker layers.

  6. Add additional runtimes

    Install secondary language runtimes in a single chained RUN instruction. The example below adds Node.js 20 alongside Python.

  7. Set up a non-root user

    Run all installation steps as root first, then create the user, fix ownership of the working directory, and switch with the USER directive. Commands that write to system locations after switching users will fail with permission errors.

  8. Configure startup

    Set the container entrypoint and default command with entrypoint() and cmd().

image = (
# 1. Base image
Image.debian_slim("3.12")
# 2. Python packages
.pip_install(["requests", "pandas"])
# 3. Local files
.add_local_file("package.json", "/home/daytona/package.json")
.add_local_dir("src", "/home/daytona/src")
# 4. Environment
.env({"PROJECT_ROOT": "/home/daytona"})
.workdir("/home/daytona")
# 5. System packages
.run_commands(
"apt-get update "
"&& apt-get install -y --no-install-recommends git curl ffmpeg jq "
"&& rm -rf /var/lib/apt/lists/*"
)
# 6. Additional runtime
.run_commands(
"apt-get update "
"&& apt-get install -y --no-install-recommends curl ca-certificates "
"&& curl -fsSL https://deb.nodesource.com/setup_20.x | bash - "
"&& apt-get install -y nodejs "
"&& rm -rf /var/lib/apt/lists/*"
)
# 7. Non-root user
.run_commands(
"groupadd -r daytona && useradd -r -g daytona -m -d /home/daytona daytona",
"chown -R daytona:daytona /home/daytona",
)
.dockerfile_commands(["USER daytona"])
# 8. Startup
.entrypoint(["/bin/bash"])
.cmd(["/bin/bash"])
)

Integrate Dockerfiles and custom Dockerfile commands.

# Add custom Dockerfile commands
image = Image.debian_slim("3.12").dockerfile_commands(["RUN echo 'Hello, world!'"])
# Use an existing Dockerfile
image = Image.from_dockerfile("Dockerfile")
# Extend an existing Dockerfile
image = Image.from_dockerfile("app/Dockerfile").pip_install(["numpy"])