Declarative Builder
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:
- Declarative images: build images on demand when creating sandboxes
- Pre-built snapshots: create and register ready-to-use snapshots
Build declarative images
Section titled “Build declarative images”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 packagesdeclarative_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 logssandbox = daytona.create( CreateSandboxFromImageParams(image=declarative_image), timeout=0, on_snapshot_create_logs=print,)// Define a declarative image with python packagesconst declarativeImage = Image.debianSlim('3.12') .pipInstall(['requests', 'pytest']) .workdir('/home/daytona')
// Create a new sandbox with the declarative image and stream the build logsconst sandbox = await daytona.create( { image: declarativeImage, }, { timeout: 0, onSnapshotCreateLogs: console.log, })# Define a simple declarative image with Python packagesdeclarative_image = Daytona::Image .debian_slim('3.12') .pip_install(['requests', 'pytest']) .workdir('/home/daytona')
# Create a new Sandbox with the declarative image and stream the build logssandbox = daytona.create( Daytona::CreateSandboxFromImageParams.new(image: declarative_image), on_snapshot_create_logs: proc { |chunk| puts chunk })// Define a declarative image with python packagesversion := "3.12"declarativeImage := daytona.DebianSlim(&version). PipInstall([]string{"requests", "pytest"}). Workdir("/home/daytona")
// Create a new sandbox with the declarative image and stream the build logslogChan := make(chan string)go func() { for log := range logChan { fmt.Print(log) }}()
sandbox, err := client.Create(ctx, types.ImageParams{ Image: declarativeImage,}, options.WithTimeout(0), options.WithLogChannel(logChan))if err != nil { // handle error}// Define a declarative image with python packagesImage declarativeImage = Image.debianSlim("3.12") .pipInstall("requests", "pytest") .workdir("/home/daytona");
// Create a new sandbox with the declarative image and stream the build logsCreateSandboxFromImageParams params = new CreateSandboxFromImageParams();params.setImage(declarativeImage);Sandbox sandbox = daytona.create(params, 0L, System.out::println);Create pre-built snapshots
Section titled “Create pre-built snapshots”Create a pre-built snapshot by building a declarative image and registering it as a snapshot.
# Define the declarative image for the snapshotimage = ( Image.debian_slim("3.12") .pip_install(["numpy", "pandas"]) .workdir("/home/daytona"))
# Create and register the snapshot, streaming the build logsdaytona.snapshot.create( CreateSnapshotParams(name="my-snapshot", image=image), on_logs=print,)
# Create a new sandbox from the pre-built snapshotsandbox = daytona.create(CreateSandboxFromSnapshotParams(snapshot="my-snapshot"))// Define the declarative image for the snapshotconst image = Image.debianSlim('3.12') .pipInstall(['numpy', 'pandas']) .workdir('/home/daytona')
// Create and register the snapshot, streaming the build logsawait daytona.snapshot.create( { name: 'my-snapshot', image, }, { onLogs: console.log, })
// Create a new sandbox from the pre-built snapshotconst sandbox = await daytona.create({ snapshot: 'my-snapshot' })# Define the declarative image for the snapshotimage = Daytona::Image .debian_slim('3.12') .pip_install(['numpy', 'pandas']) .workdir('/home/daytona')
# Create and register the snapshot, streaming the build logsdaytona.snapshot.create( Daytona::CreateSnapshotParams.new(name: 'my-snapshot', image: image), on_logs: proc { |chunk| print chunk })
# Create a new sandbox from the pre-built snapshotsandbox = daytona.create(Daytona::CreateSandboxFromSnapshotParams.new(snapshot: 'my-snapshot'))// Define the declarative image for the snapshotversion := "3.12"image := daytona.DebianSlim(&version). PipInstall([]string{"numpy", "pandas"}). Workdir("/home/daytona")
// Create and register the snapshot, streaming the build logssnapshot, logChan, err := client.Snapshot.Create(ctx, &types.CreateSnapshotParams{ Name: "my-snapshot", Image: image,})if err != nil { // handle error}for log := range logChan { fmt.Print(log)}
// Create a new sandbox from the pre-built snapshotsandbox, err := client.Create(ctx, types.SnapshotParams{ Snapshot: snapshot.Name,})if err != nil { // handle error}// Define the declarative image for the snapshotImage image = Image.debianSlim("3.12") .pipInstall("numpy", "pandas") .workdir("/home/daytona");
// Create and register the snapshot, streaming the build logsSnapshot snapshot = daytona.snapshot().create("my-snapshot", image, System.out::println);
// Create a new sandbox from the pre-built snapshotCreateSandboxFromSnapshotParams params = new CreateSandboxFromSnapshotParams();params.setSnapshot("my-snapshot");Sandbox sandbox = daytona.create(params);Image configuration
Section titled “Image configuration”Daytona provides an option to define images programmatically. Chain the methods below to build a complete image definition in a single fluent call.
-
Select a base image
Start from any registry image with
Image.base(), or useImage.debian_slim()for a Python-ready Debian image. -
Install Python packages
Add packages with
pip_install(), or install fromrequirements.txtorpyproject.tomlusingpip_install_from_requirements()andpip_install_from_pyproject(). -
Add files and directories
Copy local files into the image with
add_local_file()andadd_local_dir(). -
Configure environment
Set environment variables and the working directory with
env()andworkdir(). -
Install system packages
Use
run_commands()to install OS-level CLI tools and libraries not available throughpip. Chainapt-get update, install, and cache cleanup with&&in a single command to minimize Docker layers. -
Add additional runtimes
Install secondary language runtimes in a single chained
RUNinstruction. The example below adds Node.js 20 alongside Python. -
Set up a non-root user
Run all installation steps as
rootfirst, then create the user, fix ownership of the working directory, and switch with theUSERdirective. Commands that write to system locations after switching users will fail with permission errors. -
Configure startup
Set the container entrypoint and default command with
entrypoint()andcmd().
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"]))// 1. Base imageconst image = Image.debianSlim('3.12') // 2. Python packages .pipInstall(['requests', 'pandas']) // 3. Local files .addLocalFile('package.json', '/home/daytona/package.json') .addLocalDir('src', '/home/daytona/src') // 4. Environment .env({ PROJECT_ROOT: '/home/daytona' }) .workdir('/home/daytona') // 5. System packages .runCommands( 'apt-get update ' + '&& apt-get install -y --no-install-recommends git curl ffmpeg jq ' + '&& rm -rf /var/lib/apt/lists/*', ) // 6. Additional runtime .runCommands( '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 .runCommands( 'groupadd -r daytona && useradd -r -g daytona -m -d /home/daytona daytona', 'chown -R daytona:daytona /home/daytona', ) .dockerfileCommands(['USER daytona']) // 8. Startup .entrypoint(['/bin/bash']) .cmd(['/bin/bash'])image = Daytona::Image # 1. Base 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'])version := "3.12"// 1. Base imageimage := daytona.DebianSlim(&version). // 2. Python packages PipInstall([]string{"requests", "pandas"}). // 3. Local files AddLocalFile("package.json", "/home/daytona/package.json"). AddLocalDir("src", "/home/daytona/src"). // 4. Environment Env("PROJECT_ROOT", "/home/daytona"). Workdir("/home/daytona"). // 5. System packages AptGet([]string{"git", "curl", "ffmpeg", "jq"}). // 6. Additional runtime Run("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("groupadd -r daytona && useradd -r -g daytona -m -d /home/daytona daytona"). Run("chown -R daytona:daytona /home/daytona"). User("daytona"). // 8. Startup Entrypoint([]string{"/bin/bash"}). Cmd([]string{"/bin/bash"})// 1. Base imageImage image = Image.debianSlim("3.12") // 2. Python packages .pipInstall("requests", "pandas") // 3. Local files .addLocalFile("package.json", "/home/daytona/package.json") .addLocalDir("src", "/home/daytona/src") // 4. Environment .env(java.util.Map.of("PROJECT_ROOT", "/home/daytona")) .workdir("/home/daytona") // 5. System packages .runCommands( "apt-get update " + "&& apt-get install -y --no-install-recommends git curl ffmpeg jq " + "&& rm -rf /var/lib/apt/lists/*" ) // 6. Additional runtime .runCommands( "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 .runCommands( "groupadd -r daytona && useradd -r -g daytona -m -d /home/daytona daytona", "chown -R daytona:daytona /home/daytona" ) .dockerfileCommands("USER daytona") // 8. Startup .entrypoint("/bin/bash") .cmd("/bin/bash");Dockerfile integration
Section titled “Dockerfile integration”Integrate Dockerfiles and custom Dockerfile commands.
# Add custom Dockerfile commandsimage = Image.debian_slim("3.12").dockerfile_commands(["RUN echo 'Hello, world!'"])
# Use an existing Dockerfileimage = Image.from_dockerfile("Dockerfile")
# Extend an existing Dockerfileimage = Image.from_dockerfile("app/Dockerfile").pip_install(["numpy"])// Add custom Dockerfile commandsconst image = Image.debianSlim('3.12').dockerfileCommands(['RUN echo "Hello, world!"'])
// Use an existing Dockerfileconst image = Image.fromDockerfile('Dockerfile')
// Extend an existing Dockerfileconst image = Image.fromDockerfile("app/Dockerfile").pipInstall(['numpy'])# Add custom Dockerfile commandsimage = Daytona::Image.debian_slim('3.12').dockerfile_commands(['RUN echo "Hello, world!"'])
# Use an existing Dockerfileimage = Daytona::Image.from_dockerfile('Dockerfile')
# Extend an existing Dockerfileimage = Daytona::Image.from_dockerfile('app/Dockerfile').pip_install(['numpy'])// Note: In Go, FromDockerfile takes the Dockerfile content as a stringcontent, err := os.ReadFile("Dockerfile")if err != nil { // handle error}image := daytona.FromDockerfile(string(content))
// Extend an existing Dockerfile with additional commandscontent, err = os.ReadFile("app/Dockerfile")if err != nil { // handle error}image := daytona.FromDockerfile(string(content)). PipInstall([]string{"numpy"})