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:

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
}

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
}

Create pre-built snapshots

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

  1. Create a container snapshot from a declarative image
  2. 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);

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.

  1. Select a base image

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

  1. 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().

  1. Add files and directories

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

  1. Configure environment

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

  1. 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.

  1. Add additional runtimes

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

  1. 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.

  1. Configure startup

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

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"})