Back to Daytona

Declarative Builder

apps/docs/src/content/docs/en/declarative-builder.mdx

0.181.025.1 KB
Original Source

import { TabItem, Tabs } from '@astrojs/starlight/components'

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

Daytona provides an option to create declarative images on-the-fly when creating sandboxes. This is ideal for iterating quickly without creating separate snapshots.

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.

<Tabs syncKey="language"> <TabItem label="Python" icon="seti:python">
python
# 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,
)
</TabItem> <TabItem label="TypeScript" icon="seti:typescript">
typescript
// 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,
  }
)
</TabItem> <TabItem label="Ruby" icon="seti:ruby">
ruby
# 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 }
)
</TabItem> <TabItem label="Go" icon="seti:go">
go
// 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
}
</TabItem> <TabItem label="Java" icon="seti:java">
java
// 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);
</TabItem> </Tabs>

:::note Use the following best practices when working with the declarative builder:

  • Layer Optimization: Group related operations to minimize Docker layers

  • Cache Utilization: Identical build commands and context will be cached and subsequent builds will be almost instant

  • Security: Create non-root users for application workloads

  • Resource Efficiency: Use slim base images when appropriate

  • Context Minimization: Only include necessary files in the build context :::

Create pre-built Snapshots

Daytona provides an option to create pre-built snapshots that can be reused across multiple sandboxes.

The snapshot remains visible in the Daytona Dashboard ↗ and is permanently cached, ensuring instant availability without rebuilding.

<Tabs syncKey="language"> <TabItem label="Python" icon="seti:python">
python
# Create a python data science image
snapshot_name = "data-science-snapshot"

image = (
  Image.debian_slim("3.12")
  .pip_install(["pandas", "numpy"])
  .workdir("/home/daytona")
)

# Create the snapshot and stream the build logs
daytona.snapshot.create(
  CreateSnapshotParams(
    name=snapshot_name,
    image=image,
  ),
on_logs=print,
)

# Create a new sandbox using the pre-built snapshot
sandbox = daytona.create(
CreateSandboxFromSnapshotParams(snapshot=snapshot_name)
)
</TabItem> <TabItem label="TypeScript" icon="seti:typescript">
typescript
// Create a python data science image
const snapshotName = 'data-science-snapshot'

const image = Image.debianSlim('3.12')
  .pipInstall(['pandas', 'numpy'])
  .workdir('/home/daytona')

// Create the snapshot and stream the build logs
await daytona.snapshot.create(
  {
    name: snapshotName,
    image,
  },
  {
    onLogs: console.log,
  }
)

// Create a new sandbox using the pre-built snapshot
const sandbox = await daytona.create({
  snapshot: snapshotName,
})
</TabItem> <TabItem label="Ruby" icon="seti:ruby">
ruby
# Create a simple Python data science image
snapshot_name = 'data-science-snapshot'

image = Daytona::Image
  .debian_slim('3.12')
  .pip_install(['pandas', 'numpy'])
  .workdir('/home/daytona')

# Create the Snapshot and stream the build logs
daytona.snapshot.create(
  Daytona::CreateSnapshotParams.new(
    name: snapshot_name,
    image: image
  ),
  on_logs: proc { |chunk| puts chunk }
)

# Create a new Sandbox using the pre-built Snapshot
sandbox = daytona.create(
  Daytona::CreateSandboxFromSnapshotParams.new(snapshot: snapshot_name)
)
</TabItem> <TabItem label="Go" icon="seti:go">
go
// Create a python data science image
snapshotName := "data-science-snapshot"

version := "3.12"
image := daytona.DebianSlim(&version).
  PipInstall([]string{"pandas", "numpy"}).
  Workdir("/home/daytona")

// Create the snapshot and stream the build logs
_, logChan, err := client.Snapshot.Create(ctx, &types.CreateSnapshotParams{
  Name:  snapshotName,
  Image: image,
})
if err != nil {
  // handle error
}
for log := range logChan {
  fmt.Print(log)
}

// Create a new sandbox using the pre-built snapshot
sandbox, err := client.Create(ctx, types.SnapshotParams{
  Snapshot: snapshotName,
})
if err != nil {
  // handle error
}
</TabItem> <TabItem label="Java" icon="seti:java">
java
// Create a python data science image
String snapshotName = "data-science-snapshot";

Image image = Image.debianSlim("3.12")
    .pipInstall("pandas", "numpy")
    .workdir("/home/daytona");

// Create the snapshot and stream the build logs
daytona.snapshot().create(snapshotName, image, System.out::println);

// Create a new sandbox using the pre-built snapshot
CreateSandboxFromSnapshotParams snapshotParams = new CreateSandboxFromSnapshotParams();
snapshotParams.setSnapshot(snapshotName);
Sandbox sandbox = daytona.create(snapshotParams);
</TabItem> </Tabs>

Image configuration

Daytona provides an option to define images programmatically using the Daytona SDK. You can specify base images, install packages, add files, set environment variables, and more.

For a complete API reference and method signatures, see the Python, TypeScript, Ruby, Go, and Java SDK references.

Base image selection

Daytona provides an option to select base images. The following snippets demonstrate how to select and configure base images:

<Tabs syncKey="language"> <TabItem label="Python" icon="seti:python">
python
# Create an image from a base
image = Image.base("python:3.12-slim-bookworm")

# Use a Debian slim image with Python 3.12
image = Image.debian_slim("3.12")
</TabItem> <TabItem label="TypeScript" icon="seti:typescript">
typescript
// Create an image from a base
const image = Image.base('python:3.12-slim-bookworm')

// Use a Debian slim image with Python 3.12
const image = Image.debianSlim('3.12')
</TabItem> <TabItem label="Ruby" icon="seti:ruby">
ruby
# Create an image from a base
image = Daytona::Image.base('python:3.12-slim-bookworm')

# Use a Debian slim image with Python 3.12
image = Daytona::Image.debian_slim('3.12')
</TabItem> <TabItem label="Go" icon="seti:go">
go
// Create an image from a base
image := daytona.Base("python:3.12-slim-bookworm")

// Use a Debian slim image with Python 3.12
version := "3.12"
image := daytona.DebianSlim(&version)
</TabItem> <TabItem label="Java" icon="seti:java">
java
// Create an image from a base
Image image = Image.base("python:3.12-slim-bookworm");

// Use a Debian slim image with Python 3.12
image = Image.debianSlim("3.12");
</TabItem> </Tabs>

Package management

Daytona provides an option to install packages and dependencies to your image. The following snippets demonstrate how to install packages and dependencies to your image:

<Tabs syncKey="language"> <TabItem label="Python" icon="seti:python">
python
# Add pip packages
image = Image.debian_slim("3.12").pip_install(["requests", "pandas"])

# Install from requirements.txt
image = Image.debian_slim("3.12").pip_install_from_requirements("requirements.txt")

# Install from pyproject.toml (with optional dependencies)
image = Image.debian_slim("3.12").pip_install_from_pyproject("pyproject.toml", optional_dependencies=["dev"])
</TabItem> <TabItem label="TypeScript" icon="seti:typescript">
typescript
// Add pip packages
const image = Image.debianSlim('3.12').pipInstall(['requests', 'pandas'])

// Install from requirements.txt
const image = Image.debianSlim('3.12').pipInstallFromRequirements('requirements.txt')

// Install from pyproject.toml (with optional dependencies)
const image = Image.debianSlim('3.12').pipInstallFromPyproject('pyproject.toml', {
  optionalDependencies: ['dev']
})
</TabItem> <TabItem label="Ruby" icon="seti:ruby"> ```ruby # Add pip packages image = Daytona::Image.debian_slim('3.12').pip_install(['requests', 'pandas'])

Install from requirements.txt

image = Daytona::Image.debian_slim('3.12').pip_install_from_requirements('requirements.txt')

Install from pyproject.toml (with optional dependencies)

image = Daytona::Image.debian_slim('3.12').pip_install_from_pyproject('pyproject.toml', optional_dependencies: ['dev'] )


</TabItem>
<TabItem label="Go" icon="seti:go">

```go
// Add pip packages
version := "3.12"
image := daytona.DebianSlim(&version).PipInstall([]string{"requests", "pandas"})

// Install from requirements.txt
image := daytona.DebianSlim(&version).
  AddLocalFile("requirements.txt", "/tmp/requirements.txt").
  Run("pip install -r /tmp/requirements.txt")

// Install from pyproject.toml (with optional dependencies)
image := daytona.DebianSlim(&version).
  AddLocalFile("pyproject.toml", "/tmp/pyproject.toml").
  Run("pip install /tmp[dev]")
</TabItem> <TabItem label="Java" icon="seti:java">
java
// Add pip packages
Image image = Image.debianSlim("3.12").pipInstall("requests", "pandas");
</TabItem> </Tabs>

File system operations

Daytona provides an option to add files and directories to your image. The following snippets demonstrate how to add files and directories to your image:

<Tabs syncKey="language"> <TabItem label="Python" icon="seti:python">
python
# Add a local file
image = Image.debian_slim("3.12").add_local_file("package.json", "/home/daytona/package.json")

# Add a local directory
image = Image.debian_slim("3.12").add_local_dir("src", "/home/daytona/src")
</TabItem> <TabItem label="TypeScript" icon="seti:typescript">
typescript
// Add a local file
const image = Image.debianSlim('3.12').addLocalFile('package.json', '/home/daytona/package.json')

// Add a local directory
const image = Image.debianSlim('3.12').addLocalDir('src', '/home/daytona/src')
</TabItem> <TabItem label="Ruby" icon="seti:ruby">
ruby
# Add a local file
image = Daytona::Image.debian_slim('3.12').add_local_file('package.json', '/home/daytona/package.json')

# Add a local directory
image = Daytona::Image.debian_slim('3.12').add_local_dir('src', '/home/daytona/src')
</TabItem> <TabItem label="Go" icon="seti:go">
go
// Add a local file
version := "3.12"
image := daytona.DebianSlim(&version).AddLocalFile("package.json", "/home/daytona/package.json")

// Add a local directory
image := daytona.DebianSlim(&version).AddLocalDir("src", "/home/daytona/src")
</TabItem> </Tabs>

Environment configuration

Daytona provides an option to configure environment variables and working directories. The following snippets demonstrate how to configure environment variables and working directories:

<Tabs syncKey="language"> <TabItem label="Python" icon="seti:python">
python
# Set environment variables
image = Image.debian_slim("3.12").env({"PROJECT_ROOT": "/home/daytona"})

# Set working directory
image = Image.debian_slim("3.12").workdir("/home/daytona")
</TabItem> <TabItem label="TypeScript" icon="seti:typescript">
typescript
// Set environment variables
const image = Image.debianSlim('3.12').env({ PROJECT_ROOT: '/home/daytona' })

// Set working directory
const image = Image.debianSlim('3.12').workdir('/home/daytona')
</TabItem> <TabItem label="Ruby" icon="seti:ruby">
ruby
# Set environment variables
image = Daytona::Image.debian_slim('3.12').env({ 'PROJECT_ROOT' => '/home/daytona' })

# Set working directory
image = Daytona::Image.debian_slim('3.12').workdir('/home/daytona')
</TabItem> <TabItem label="Go" icon="seti:go">
go
// Set environment variables
version := "3.12"
image := daytona.DebianSlim(&version).Env("PROJECT_ROOT", "/home/daytona")

// Set working directory
image := daytona.DebianSlim(&version).Workdir("/home/daytona")
</TabItem> <TabItem label="Java" icon="seti:java">
java
// Set environment variables
Image image = Image.debianSlim("3.12")
    .env(java.util.Map.of("PROJECT_ROOT", "/home/daytona"));

// Set working directory
image = Image.debianSlim("3.12").workdir("/home/daytona");
</TabItem> </Tabs>

Commands and entrypoints

Daytona provides an option to execute commands during build and configure container startup behavior. The following snippets demonstrate how to execute commands during build and configure container startup behavior:

<Tabs syncKey="language"> <TabItem label="Python" icon="seti:python">
python
# Run shell commands during build
image = Image.debian_slim("3.12").run_commands(
    'apt-get update && apt-get install -y git',
    'groupadd -r daytona && useradd -r -g daytona -m daytona',
    'mkdir -p /home/daytona/workspace'
)

# Set entrypoint
image = Image.debian_slim("3.12").entrypoint(["/bin/bash"])

# Set default command
image = Image.debian_slim("3.12").cmd(["/bin/bash"])
</TabItem> <TabItem label="TypeScript" icon="seti:typescript">
typescript
// Run shell commands during build
const image = Image.debianSlim('3.12').runCommands(
    'apt-get update && apt-get install -y git',
    'groupadd -r daytona && useradd -r -g daytona -m daytona',
    'mkdir -p /home/daytona/workspace'
)

// Set entrypoint
const image = Image.debianSlim('3.12').entrypoint(['/bin/bash'])

// Set default command
const image = Image.debianSlim('3.12').cmd(['/bin/bash'])
</TabItem> <TabItem label="Ruby" icon="seti:ruby">
ruby
# Run shell commands during build
image = Daytona::Image.debian_slim('3.12').run_commands(
  'apt-get update && apt-get install -y git',
  'groupadd -r daytona && useradd -r -g daytona -m daytona',
  'mkdir -p /home/daytona/workspace'
)

# Set entrypoint
image = Daytona::Image.debian_slim('3.12').entrypoint(['/bin/bash'])

# Set default command
image = Daytona::Image.debian_slim('3.12').cmd(['/bin/bash'])
</TabItem> <TabItem label="Go" icon="seti:go">
go
// Run shell commands during build
version := "3.12"
image := daytona.DebianSlim(&version).
  Run("apt-get update && apt-get install -y git").
  Run("groupadd -r daytona && useradd -r -g daytona -m daytona").
  Run("mkdir -p /home/daytona/workspace")

// Set entrypoint
image := daytona.DebianSlim(&version).Entrypoint([]string{"/bin/bash"})

// Set default command
image := daytona.DebianSlim(&version).Cmd([]string{"/bin/bash"})
</TabItem> <TabItem label="Java" icon="seti:java">
java
// Run shell commands during build
Image image = Image.debianSlim("3.12").runCommands(
    "apt-get update && apt-get install -y git",
    "groupadd -r daytona && useradd -r -g daytona -m daytona",
    "mkdir -p /home/daytona/workspace"
);

// Set entrypoint
image = Image.debianSlim("3.12").entrypoint("/bin/bash");

// Set default command
image = Image.debianSlim("3.12").cmd("/bin/bash");
</TabItem> </Tabs>

Dockerfile integration

Daytona provides an option to integrate existing Dockerfiles or add custom Dockerfile commands. The following snippets demonstrate how to integrate existing Dockerfiles or add custom Dockerfile commands:

<Tabs syncKey="language"> <TabItem label="Python" icon="seti:python">
python
# 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"])
</TabItem> <TabItem label="TypeScript" icon="seti:typescript">
typescript
// Add custom Dockerfile commands
const image = Image.debianSlim('3.12').dockerfileCommands(['RUN echo "Hello, world!"'])

// Use an existing Dockerfile
const image = Image.fromDockerfile('Dockerfile')

// Extend an existing Dockerfile
const image = Image.fromDockerfile("app/Dockerfile").pipInstall(['numpy'])
</TabItem> <TabItem label="Ruby" icon="seti:ruby">
ruby
# 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'])
</TabItem> <TabItem label="Go" icon="seti:go">
go
// 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"})
</TabItem> </Tabs>

System package installation

Daytona provides an option to install OS-level packages during the image build. Use this pattern when your sandbox needs CLI tools or system libraries that are not available through pip.

Each string passed to run_commands becomes a separate Dockerfile RUN instruction, and every RUN produces an immutable layer. To keep the image small, chain the package install and the apt cache cleanup together with && inside a single string so the cache is never persisted in any layer.

<Tabs syncKey="language"> <TabItem label="Python" icon="seti:python">
python
image = Image.debian_slim("3.12").run_commands(
  "apt-get update "
  "&& apt-get install -y --no-install-recommends git curl ffmpeg jq "
  "&& rm -rf /var/lib/apt/lists/*"
)
</TabItem> <TabItem label="TypeScript" icon="seti:typescript">
typescript
const image = Image.debianSlim('3.12').runCommands(
  'apt-get update ' +
    '&& apt-get install -y --no-install-recommends git curl ffmpeg jq ' +
    '&& rm -rf /var/lib/apt/lists/*',
)
</TabItem> <TabItem label="Ruby" icon="seti:ruby">
ruby
image = Daytona::Image
  .debian_slim('3.12')
  .run_commands(
    'apt-get update ' \
    '&& apt-get install -y --no-install-recommends git curl ffmpeg jq ' \
    '&& rm -rf /var/lib/apt/lists/*'
  )
</TabItem> <TabItem label="Go" icon="seti:go">
go
version := "3.12"
image := daytona.DebianSlim(&version).
  AptGet([]string{"git", "curl", "ffmpeg", "jq"})
</TabItem> <TabItem label="Java" icon="seti:java">
java
Image image = Image.debianSlim("3.12").runCommands(
    "apt-get update "
        + "&& apt-get install -y --no-install-recommends git curl ffmpeg jq "
        + "&& rm -rf /var/lib/apt/lists/*"
);
</TabItem> </Tabs>

Non-root user setup

Daytona provides an option to define a non-root user for application workloads. Run all installation steps as root first, then create the user, fix ownership of the working directory, and switch to the new user with the USER directive. Subsequent commands and the sandbox runtime then operate without root privileges.

Place all installation steps before the USER directive. After switching to the non-root user, commands that write to system locations (such as apt-get install or pip install without --user) will fail with permission errors.

<Tabs syncKey="language"> <TabItem label="Python" icon="seti:python">
python
image = (
  Image.debian_slim("3.12")
  .pip_install(["fastapi", "uvicorn"])
  .run_commands(
    "groupadd -r daytona && useradd -r -g daytona -m -d /home/daytona daytona",
    "chown -R daytona:daytona /home/daytona",
  )
  .workdir("/home/daytona")
  .dockerfile_commands(["USER daytona"])
)
</TabItem> <TabItem label="TypeScript" icon="seti:typescript">
typescript
const image = Image.debianSlim('3.12')
  .pipInstall(['fastapi', 'uvicorn'])
  .runCommands(
    'groupadd -r daytona && useradd -r -g daytona -m -d /home/daytona daytona',
    'chown -R daytona:daytona /home/daytona',
  )
  .workdir('/home/daytona')
  .dockerfileCommands(['USER daytona'])
</TabItem> <TabItem label="Ruby" icon="seti:ruby">
ruby
image = Daytona::Image
  .debian_slim('3.12')
  .pip_install(['fastapi', 'uvicorn'])
  .run_commands(
    'groupadd -r daytona && useradd -r -g daytona -m -d /home/daytona daytona',
    'chown -R daytona:daytona /home/daytona'
  )
  .workdir('/home/daytona')
  .dockerfile_commands(['USER daytona'])
</TabItem> <TabItem label="Go" icon="seti:go">
go
version := "3.12"
image := daytona.DebianSlim(&version).
  PipInstall([]string{"fastapi", "uvicorn"}).
  Run("groupadd -r daytona && useradd -r -g daytona -m -d /home/daytona daytona").
  Run("chown -R daytona:daytona /home/daytona").
  Workdir("/home/daytona").
  User("daytona")
</TabItem> </Tabs>

Multi-language runtimes

Daytona provides an option to combine multiple language runtimes in a single image. The following pattern adds Node.js 20 to a Python base image by installing it from the NodeSource repository. The same approach works for adding Go, Ruby, Java, or any other runtime that distributes a Linux installer.

Chain the apt operations, the NodeSource installer, and the cache cleanup into a single RUN instruction. If the cache cleanup runs in a separate RUN, the apt cache is already persisted in the earlier layers and the final image keeps those bytes.

<Tabs syncKey="language"> <TabItem label="Python" icon="seti:python">
python
image = (
  Image.debian_slim("3.12")
  .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/*"
  )
  .pip_install(["fastapi", "uvicorn"])
)
</TabItem> <TabItem label="TypeScript" icon="seti:typescript">
typescript
const image = Image.debianSlim('3.12')
  .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/*',
  )
  .pipInstall(['fastapi', 'uvicorn'])
</TabItem> <TabItem label="Ruby" icon="seti:ruby">
ruby
image = Daytona::Image
  .debian_slim('3.12')
  .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/*'
  )
  .pip_install(['fastapi', 'uvicorn'])
</TabItem> <TabItem label="Go" icon="seti:go">
go
version := "3.12"
image := daytona.DebianSlim(&version).
  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/*").
  PipInstall([]string{"fastapi", "uvicorn"})
</TabItem> <TabItem label="Java" icon="seti:java">
java
Image image = Image.debianSlim("3.12")
    .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/*"
    )
    .pipInstall("fastapi", "uvicorn");
</TabItem> </Tabs>