Declarative Builder | Daytona
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
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.
Create a container sandbox from a declarative image.
from daytona import Image, CreateSandboxFromImageParams
# 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,
)
import { Image } from '@daytona/sdk'
// Define a declarative image with python packages
const declarativeImage = Image.debianSlim('3.12')
.pipInstall(['requests', 'pytest'])
.workdir('/home/daytona')
// Create a new sandbox with the declarative image and stream the build logs
const sandbox = await daytona.create(
{
image: declarativeImage,
},
{
timeout: 0,
onSnapshotCreateLogs: console.log,
}
)
# Define a simple declarative image with Python packages
declarative_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 logs
sandbox = daytona.create(
Daytona::CreateSandboxFromImageParams.new(image: declarative_image),
on_snapshot_create_logs: proc { |chunk| puts chunk }
)
// Define a declarative image with python packages
version := "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 logs
logChan := 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 packages
Image declarativeImage = Image.debianSlim("3.12")
.pipInstall("requests", "pytest")
.workdir("/home/daytona");
// Create a new sandbox with the declarative image and stream the build logs
CreateSandboxFromImageParams params = new CreateSandboxFromImageParams();
params.setImage(declarativeImage);
Sandbox sandbox = daytona.create(params, 0L, System.out::println);
Create a GPU sandbox from a declarative image.
from daytona import Image, CreateSandboxFromImageParams, Resources
# Define a declarative image with python packages
declarative_image = (
Image.debian_slim("3.12")
.pip_install(["requests", "pytest"])
.workdir("/home/daytona")
)
# Create a GPU sandbox with the declarative image and stream the build logs
sandbox = daytona.create(
CreateSandboxFromImageParams(
image=declarative_image,
auto_delete_interval=0,
resources=Resources(gpu=1),
),
timeout=0,
on_snapshot_create_logs=print,
)
import { Image } from '@daytona/sdk'
// Define a declarative image with python packages
const declarativeImage = Image.debianSlim('3.12')
.pipInstall(['requests', 'pytest'])
.workdir('/home/daytona')
// Create a GPU sandbox with the declarative image and stream the build logs
const sandbox = await daytona.create(
{
image: declarativeImage,
autoDeleteInterval: 0,
resources: { gpu: 1 },
},
{
timeout: 0,
onSnapshotCreateLogs: console.log,
}
)
# Define a simple declarative image with Python packages
declarative_image = Daytona::Image
.debian_slim('3.12')
.pip_install(['requests', 'pytest'])
.workdir('/home/daytona')
# Create a GPU Sandbox with the declarative image and stream the build logs
sandbox = daytona.create(
Daytona::CreateSandboxFromImageParams.new(
image: declarative_image,
auto_delete_interval: 0,
resources: Daytona::Resources.new(gpu: 1)
),
on_snapshot_create_logs: proc { |chunk| puts chunk }
)
// Define a declarative image with python packages
version := "3.12"
declarativeImage := daytona.DebianSlim(&version).
PipInstall([]string{"requests", "pytest"}).
Workdir("/home/daytona")
// Create a GPU sandbox with the declarative image and stream the build logs
autoDelete := 0
logChan := make(chan string)
go func() {
for log := range logChan {
fmt.Print(log)
}
}
sandbox, err := client.Create(ctx, types.ImageParams{
Image: declarativeImage,
SandboxBaseParams: types.SandboxBaseParams{
AutoDeleteInterval: &autoDelete,
},
Resources: &types.Resources{
GPU: 1,
},
}, options.WithTimeout(0), options.WithLogChannel(logChan))
if err != nil {
// handle error
}
// Define a declarative image with python packages
Image declarativeImage = Image.debianSlim("3.12")
.pipInstall("requests", "pytest")
.workdir("/home/daytona");
// Create a GPU sandbox with the declarative image and stream the build logs
CreateSandboxFromImageParams params = new CreateSandboxFromImageParams();
params.setImage(declarativeImage);
params.setAutoDeleteInterval(0);
Resources resources = new Resources();
resources.setGpu(1);
params.setResources(resources);
Sandbox sandbox = daytona.create(params, 0L, System.out::println);
Create pre-built snapshots
Create a pre-built snapshot by building a declarative image and registering it as a snapshot.
- Create a container snapshot from a declarative image
- Create a sandbox from that snapshot
from daytona import Image, CreateSnapshotParams, CreateSandboxFromSnapshotParams
# 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"))
import { Image } from '@daytona/sdk'
// Define the declarative image for the snapshot
const image = Image.debianSlim('3.12')
.pipInstall(['numpy', 'pandas'])
.workdir('/home/daytona')
// Create and register the snapshot, streaming the build logs
await daytona.snapshot.create(
{
name: 'my-snapshot',
image,
},
{
onLogs: console.log,
}
)
// Create a new sandbox from the pre-built snapshot
const sandbox = await daytona.create({ snapshot: 'my-snapshot' })
# Define the declarative image for the snapshot
image = Daytona::Image
.debian_slim('3.12')
.pip_install(['numpy', 'pandas'])
.workdir('/home/daytona')
# Create and register the snapshot, streaming the build logs
daytona.snapshot.create(
Daytona::CreateSnapshotParams.new(name: 'my-snapshot', image: image),
on_logs: proc { |chunk| print chunk }
)
# Create a new sandbox from the pre-built snapshot
sandbox = daytona.create(Daytona::CreateSandboxFromSnapshotParams.new(snapshot: 'my-snapshot'))
// Define the declarative image for the snapshot
version := "3.12"
image := daytona.DebianSlim(&version).
PipInstall([]string{"numpy", "pandas"}).
Workdir("/home/daytona")
// Create and register the snapshot, streaming the build logs
snapshot, 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 snapshot
sandbox, err := client.Create(ctx, types.SnapshotParams{
Snapshot: snapshot.Name,
})
if err != nil {
// handle error
}
}
// Define the declarative image for the snapshot
Image image = Image.debianSlim("3.12")
.pipInstall("numpy", "pandas")
.workdir("/home/daytona");
// Create and register the snapshot, streaming the build logs
Snapshot snapshot = daytona.snapshot().create("my-snapshot", image, System.out::println);
// Create a new sandbox from the pre-built snapshot
CreateSandboxFromSnapshotParams params = new CreateSandboxFromSnapshotParams();
params.setSnapshot("my-snapshot");
Sandbox sandbox = daytona.create(params);
- Create a GPU snapshot from a declarative image
- Create a sandbox from that snapshot
from daytona import Image, CreateSnapshotParams, CreateSandboxFromSnapshotParams, Resources
# Define the declarative image for the GPU snapshot
image = (
Image.debian_slim("3.12")
.pip_install(["numpy", "pandas"])
.workdir("/home/daytona")
)
# Create and register the GPU snapshot, streaming the build logs
daytona.snapshot.create(
CreateSnapshotParams(
name="my-gpu-snapshot",
image=image,
resources=Resources(gpu=1),
),
on_logs=print,
)
# Create a new GPU sandbox from the pre-built snapshot
sandbox = daytona.create(
CreateSandboxFromSnapshotParams(
snapshot="my-gpu-snapshot",
auto_delete_interval=0,
)
)
import { Image } from '@daytona/sdk'
// Define the declarative image for the GPU snapshot
const image = Image.debianSlim('3.12')
.pipInstall(['numpy', 'pandas'])
.workdir('/home/daytona')
// Create and register the GPU snapshot, streaming the build logs
await daytona.snapshot.create(
{
name: 'my-gpu-snapshot',
image,
resources: { gpu: 1 },
},
{
onLogs: console.log,
}
)
// Create a new GPU sandbox from the pre-built snapshot
const sandbox = await daytona.create({
snapshot: 'my-gpu-snapshot',
autoDeleteInterval: 0,
})
# Define the declarative image for the GPU snapshot
image = Daytona::Image
.debian_slim('3.12')
.pip_install(['numpy', 'pandas'])
.workdir('/home/daytona')
# Create and register the GPU snapshot, streaming the build logs
daytona.snapshot.create(
Daytona::CreateSnapshotParams.new(
name: 'my-gpu-snapshot',
image: image,
resources: Daytona::Resources.new(gpu: 1)
),
on_logs: proc { |chunk| print chunk }
)
# Create a new GPU sandbox from the pre-built snapshot
sandbox = daytona.create(
Daytona::CreateSandboxFromSnapshotParams.new(
snapshot: 'my-gpu-snapshot',
auto_delete_interval: 0
)
)
// Define the declarative image for the GPU snapshot
version := "3.12"
image := daytona.DebianSlim(&version).
PipInstall([]string{"numpy", "pandas"}).
Workdir("/home/daytona")
// Create and register the GPU snapshot, streaming the build logs
snapshot, logChan, err := client.Snapshot.Create(ctx, &types.CreateSnapshotParams{
Name: "my-gpu-snapshot",
Image: image,
Resources: &types.Resources{
GPU: 1,
},
})
if err != nil {
// handle error
}
for log := range logChan {
fmt.Print(log)
}
// Create a new GPU sandbox from the pre-built snapshot
autoDelete := 0
sandbox, err := client.Create(ctx, types.SnapshotParams{
Snapshot: snapshot.Name,
SandboxBaseParams: types.SandboxBaseParams{
AutoDeleteInterval: &autoDelete,
},
})
if err != nil {
// handle error
}
// Define the declarative image for the GPU snapshot
Image image = Image.debianSlim("3.12")
.pipInstall("numpy", "pandas")
.workdir("/home/daytona");
Resources resources = new Resources();
resources.setGpu(1);
// Create and register the GPU snapshot, streaming the build logs
Snapshot snapshot = daytona.snapshot().create(
"my-gpu-snapshot", image, resources, System.out::println);
// Create a new GPU sandbox from the pre-built snapshot
CreateSandboxFromSnapshotParams params = new CreateSandboxFromSnapshotParams();
params.setSnapshot("my-gpu-snapshot");
params.setAutoDeleteInterval(0);
Sandbox sandbox = daytona.create(params);
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().
from daytona import Image
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"])
)
import { Image } from '@daytona/sdk'
// 1. Base image
const 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 image
image := 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 image
Image 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 (the Java SDK has no method for a USER instruction)
.runCommands(
"groupadd -r daytona && useradd -r -g daytona -m -d /home/daytona daytona",
"chown -R daytona:daytona /home/daytona"
)
// 8. Startup
.entrypoint("/bin/bash")
.cmd("/bin/bash");
Dockerfile integration
Integrate Dockerfiles and custom Dockerfile commands.
from daytona import Image
# 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"])"
import { Image } from '@daytona/sdk'
// Add custom Dockerfile commands
let image = Image.debianSlim('3.12').dockerfileCommands(['RUN echo "Hello, world!"'])
// Use an existing Dockerfile
image = Image.fromDockerfile('Dockerfile')
// Extend an existing Dockerfile
image = Image.fromDockerfile('app/Dockerfile').pipInstall(['numpy'])
# Add custom Dockerfile commands
image = Daytona::Image.debian_slim('3.12').dockerfile_commands(['RUN echo "Hello, world!"'])
// Use an existing Dockerfile
image = Daytona::Image.from_dockerfile('Dockerfile')
// Extend an existing Dockerfile
image = Daytona::Image.from_dockerfile('app/Dockerfile').pip_install(['numpy'])
// Note: In Go, FromDockerfile takes the Dockerfile content as a string
content, err := os.ReadFile("Dockerfile")
if err != nil {
// handle error
}
image := daytona.FromDockerfile(string(content))
// Extend an existing Dockerfile with additional commands
content, err = os.ReadFile("app/Dockerfile")
if err != nil {
// handle error
}
image = daytona.FromDockerfile(string(content)).
PipInstall([]string{"numpy"})