# Mount External Storage (/docs/mount-external-storage)

Mount object storage (Amazon S3, Cloudflare R2, Tigris, Supabase Storage, Google Cloud Storage, Azure Blob), cloud storage like Box, and filesystems like Archil, MesaFS, and TiDB Cloud Filesystem into a Daytona sandbox as a regular directory. The sandbox reads from and writes to the bucket as if it were a local directory, so existing tools, scripts, and agents work without changes. This is useful for bringing in datasets, model weights, or build artifacts that already live in your own cloud account.

External storage mounts and [Daytona Volumes](/docs/volumes/) are complementary FUSE-based mechanisms — both expose remote object storage as a regular sandbox directory, both can be shared across sandboxes, and both persist beyond any individual sandbox's lifetime. The main distinction is **where the data physically lives**: Daytona Volumes are hosted on Daytona's own S3-compatible object store, while external mounts connect to a bucket or filesystem hosted on another provider (Amazon S3, Cloudflare R2, Tigris, Supabase Storage, GCS, Azure Blob, Box, Archil, MesaFS, TiDB Cloud Filesystem).

External storage is mounted using FUSE. Daytona supports two approaches, and each provider section below shows both — pick whichever fits your workflow:

- **Pre-built snapshot** — build a [snapshot](/docs/snapshots/) once with the FUSE tool (`mount-s3`, `gcsfuse`, `blobfuse2`, `box-mount`) built-in, then launch every sandbox from that snapshot. Cold starts are fast and predictable. Best for production.
- **Runtime install** — launch a default sandbox and `apt-get install` the FUSE tool when the sandbox starts. Adds time to sandbox startup, but you don't manage snapshots. Best for quick experiments.

Both approaches end with the same mount command and the same usage — the only difference is when the FUSE tool gets installed.

## Mount an Amazon S3 bucket [#mount-an-amazon-s3-bucket]

Mount an S3 bucket using [Mountpoint for Amazon S3 ↗](https://github.com/awslabs/mountpoint-s3) — AWS's official FUSE client, optimized for high throughput on S3.

**Credentials** — set `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` in your local environment. The snippets below pass them into the sandbox via `envVars`, and `mount-s3` reads them from there.

### Pre-built snapshot [#pre-built-snapshot]

Build a snapshot with `mount-s3` preinstalled, then launch all S3-enabled sandboxes from that snapshot. This removes per-sandbox package install work, keeps cold starts predictable, and gives you a reusable baseline image for production workloads.

#### Build a snapshot [#build-a-snapshot]

Create a reusable snapshot that installs `mount-s3` and its system dependencies. After it finishes, every sandbox launched from `fuse-s3` already has the mount binary available.

<Tabs syncKey="language">
<TabItem label="Python" icon="seti:python">

```python
from daytona import CreateSnapshotParams, Daytona, Image

daytona = Daytona()

image = (
  Image.base("daytonaio/sandbox")
  .run_commands(
    "sudo apt-get update "
    "&& sudo apt-get install -y --no-install-recommends libfuse2 ca-certificates wget",
    'arch="$(dpkg --print-architecture | sed s/amd64/x86_64/)" '
    '&& wget -O /tmp/mount-s3.deb '
    '"https://s3.amazonaws.com/mountpoint-s3-release/latest/${arch}/mount-s3.deb" '
    "&& sudo apt-get install -y /tmp/mount-s3.deb "
    "&& rm /tmp/mount-s3.deb",
  )
)

daytona.snapshot.create(
    CreateSnapshotParams(name="fuse-s3", image=image),
    on_logs=print,
)
```

</TabItem>
<TabItem label="TypeScript" icon="seti:typescript">

```typescript
import { Daytona, Image } from '@daytona/sdk'

const daytona = new Daytona()

const image = Image.base('daytonaio/sandbox').runCommands(
  'sudo apt-get update ' +
    '&& sudo apt-get install -y --no-install-recommends libfuse2 ca-certificates wget',
  'arch="$(dpkg --print-architecture | sed s/amd64/x86_64/)" ' +
    '&& wget -O /tmp/mount-s3.deb ' +
    '"https://s3.amazonaws.com/mountpoint-s3-release/latest/${arch}/mount-s3.deb" ' +
    '&& sudo apt-get install -y /tmp/mount-s3.deb ' +
    '&& rm /tmp/mount-s3.deb',
)

await daytona.snapshot.create(
  { name: 'fuse-s3', image },
  { onLogs: console.log },
)
```

</TabItem>
<TabItem label="Ruby" icon="seti:ruby">

```ruby
require 'daytona'

daytona = Daytona::Daytona.new

image = Daytona::Image
  .base('daytonaio/sandbox')
  .run_commands(
    'sudo apt-get update ' \
    '&& sudo apt-get install -y --no-install-recommends libfuse2 ca-certificates wget',
    'arch="$(dpkg --print-architecture | sed s/amd64/x86_64/)" ' \
    '&& wget -O /tmp/mount-s3.deb ' \
    '"https://s3.amazonaws.com/mountpoint-s3-release/latest/${arch}/mount-s3.deb" ' \
    '&& sudo apt-get install -y /tmp/mount-s3.deb ' \
    '&& rm /tmp/mount-s3.deb'
  )

daytona.snapshot.create(
  Daytona::CreateSnapshotParams.new(name: 'fuse-s3', image: image),
  on_logs: proc { |chunk| print(chunk) }
)
```

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

```go
import (
	"context"
	"fmt"
	"log"

	"github.com/daytona/clients/sdk-go/pkg/daytona"
	"github.com/daytona/clients/sdk-go/pkg/types"
)

ctx := context.Background()
client, err := daytona.NewClient()
if err != nil {
	log.Fatal(err)
}

image := daytona.Base("daytonaio/sandbox").
	Run("sudo apt-get update && sudo apt-get install -y --no-install-recommends libfuse2 ca-certificates wget").
	Run(`arch="$(dpkg --print-architecture | sed s/amd64/x86_64/)" && ` +
		`wget -O /tmp/mount-s3.deb "https://s3.amazonaws.com/mountpoint-s3-release/latest/${arch}/mount-s3.deb" && ` +
		`sudo apt-get install -y /tmp/mount-s3.deb && rm /tmp/mount-s3.deb`)

_, logChan, err := client.Snapshot.Create(ctx, &types.CreateSnapshotParams{
	Name:  "fuse-s3",
	Image: image,
})
if err != nil {
	log.Fatal(err)
}
for line := range logChan {
	fmt.Print(line)
}
```

</TabItem>
<TabItem label="Java" icon="seti:java">

```java
import io.daytona.sdk.Daytona;
import io.daytona.sdk.Image;

public class App {
    public static void main(String[] args) {
        try (Daytona daytona = new Daytona()) {
            Image image = Image.base("daytonaio/sandbox")
                .runCommands(
                    "sudo apt-get update "
                        + "&& sudo apt-get install -y --no-install-recommends libfuse2 ca-certificates wget",
                    "arch=\"$(dpkg --print-architecture | sed s/amd64/x86_64/)\" "
                        + "&& wget -O /tmp/mount-s3.deb "
                        + "\"https://s3.amazonaws.com/mountpoint-s3-release/latest/${arch}/mount-s3.deb\" "
                        + "&& sudo apt-get install -y /tmp/mount-s3.deb "
                        + "&& rm /tmp/mount-s3.deb"
                );

            daytona.snapshot().create("fuse-s3", image, System.out::println);
        }
    }
}
```

</TabItem>
</Tabs>

#### Launch and mount [#launch-and-mount]

Pass AWS credentials as environment variables on sandbox creation. `mount-s3` reads them automatically.

<Tabs syncKey="language">
<TabItem label="Python" icon="seti:python">

```python
import os
from daytona import CreateSandboxFromSnapshotParams, Daytona

daytona = Daytona()

sandbox = daytona.create(
    CreateSandboxFromSnapshotParams(
        snapshot="fuse-s3",
        env_vars={
            "AWS_ACCESS_KEY_ID": os.environ["AWS_ACCESS_KEY_ID"],
            "AWS_SECRET_ACCESS_KEY": os.environ["AWS_SECRET_ACCESS_KEY"],
        },
    )
)

mount_path = "/home/daytona/s3"

# mount-s3 daemonizes by default and reads AWS_* from the environment
sandbox.process.exec(f"mkdir -p {mount_path}")
sandbox.process.exec(f"mount-s3 my-bucket {mount_path}")

# Read and write through the mount as if it were a local directory
sandbox.process.exec(f"echo 'hello from Daytona' > {mount_path}/hello.txt")
response = sandbox.process.exec(f"cat {mount_path}/hello.txt")
print(response.result)
```

</TabItem>
<TabItem label="TypeScript" icon="seti:typescript">

```typescript
import { Daytona } from '@daytona/sdk'

const daytona = new Daytona()

const sandbox = await daytona.create({
  snapshot: 'fuse-s3',
  envVars: {
    AWS_ACCESS_KEY_ID: process.env.AWS_ACCESS_KEY_ID!,
    AWS_SECRET_ACCESS_KEY: process.env.AWS_SECRET_ACCESS_KEY!,
  },
})

const mountPath = '/home/daytona/s3'

// mount-s3 daemonizes by default and reads AWS_* from the environment
await sandbox.process.executeCommand(`mkdir -p ${mountPath}`)
await sandbox.process.executeCommand(`mount-s3 my-bucket ${mountPath}`)

// Read and write through the mount as if it were a local directory
await sandbox.process.executeCommand(`echo 'hello from Daytona' > ${mountPath}/hello.txt`)
const response = await sandbox.process.executeCommand(`cat ${mountPath}/hello.txt`)
console.log(response.result)
```

</TabItem>
<TabItem label="Ruby" icon="seti:ruby">

```ruby
require 'daytona'

daytona = Daytona::Daytona.new

sandbox = daytona.create(
  Daytona::CreateSandboxFromSnapshotParams.new(
    snapshot: 'fuse-s3',
    env_vars: {
      'AWS_ACCESS_KEY_ID' => ENV.fetch('AWS_ACCESS_KEY_ID'),
      'AWS_SECRET_ACCESS_KEY' => ENV.fetch('AWS_SECRET_ACCESS_KEY')
    }
  )
)

mount_path = '/home/daytona/s3'

# mount-s3 daemonizes by default and reads AWS_* from the environment
sandbox.process.exec(command: "mkdir -p #{mount_path}")
sandbox.process.exec(command: "mount-s3 my-bucket #{mount_path}")

# Read and write through the mount as if it were a local directory
sandbox.process.exec(command: "echo 'hello from Daytona' > #{mount_path}/hello.txt")
response = sandbox.process.exec(command: "cat #{mount_path}/hello.txt")
puts response.result
```

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

```go
import (
	"context"
	"fmt"
	"log"
	"os"

	"github.com/daytona/clients/sdk-go/pkg/daytona"
	"github.com/daytona/clients/sdk-go/pkg/types"
)

ctx := context.Background()
client, err := daytona.NewClient()
if err != nil {
	log.Fatal(err)
}

sandbox, err := client.Create(ctx, types.SnapshotParams{
	Snapshot: "fuse-s3",
	SandboxBaseParams: types.SandboxBaseParams{
		EnvVars: map[string]string{
			"AWS_ACCESS_KEY_ID":     os.Getenv("AWS_ACCESS_KEY_ID"),
			"AWS_SECRET_ACCESS_KEY": os.Getenv("AWS_SECRET_ACCESS_KEY"),
		},
	},
})
if err != nil {
	log.Fatal(err)
}

mountPath := "/home/daytona/s3"

// mount-s3 daemonizes by default and reads AWS_* from the environment
if _, err := sandbox.Process.ExecuteCommand(ctx, "mkdir -p "+mountPath); err != nil {
	log.Fatal(err)
}
if _, err := sandbox.Process.ExecuteCommand(ctx, "mount-s3 my-bucket "+mountPath); err != nil {
	log.Fatal(err)
}

// Read and write through the mount as if it were a local directory
if _, err := sandbox.Process.ExecuteCommand(ctx, "echo 'hello from Daytona' > "+mountPath+"/hello.txt"); err != nil {
	log.Fatal(err)
}
response, err := sandbox.Process.ExecuteCommand(ctx, "cat "+mountPath+"/hello.txt")
if err != nil {
	log.Fatal(err)
}
fmt.Println(response.Result)
```

</TabItem>
<TabItem label="Java" icon="seti:java">

```java
import io.daytona.sdk.Daytona;
import io.daytona.sdk.Sandbox;
import io.daytona.sdk.model.CreateSandboxFromSnapshotParams;
import io.daytona.sdk.model.ExecuteResponse;

import java.util.Map;

public class App {
    public static void main(String[] args) {
        try (Daytona daytona = new Daytona()) {
            CreateSandboxFromSnapshotParams params = new CreateSandboxFromSnapshotParams();
            params.setSnapshot("fuse-s3");
            params.setEnvVars(Map.of(
                "AWS_ACCESS_KEY_ID", System.getenv("AWS_ACCESS_KEY_ID"),
                "AWS_SECRET_ACCESS_KEY", System.getenv("AWS_SECRET_ACCESS_KEY")
            ));
            Sandbox sandbox = daytona.create(params);

            String mountPath = "/home/daytona/s3";

            // mount-s3 daemonizes by default and reads AWS_* from the environment
            sandbox.getProcess().executeCommand("mkdir -p " + mountPath);
            sandbox.getProcess().executeCommand("mount-s3 my-bucket " + mountPath);

            // Read and write through the mount as if it were a local directory
            sandbox.getProcess().executeCommand(
                "echo 'hello from Daytona' > " + mountPath + "/hello.txt");
            ExecuteResponse response = sandbox.getProcess().executeCommand(
                "cat " + mountPath + "/hello.txt");
            System.out.println(response.getResult());
        }
    }
}
```

</TabItem>
</Tabs>

### Runtime install [#runtime-install]

Start from a default sandbox and install `mount-s3` during startup before running the mount command. This is useful for quick testing and temporary environments where you do not want to maintain a custom snapshot, with the tradeoff of slower cold starts.

<Tabs syncKey="language">
<TabItem label="Python" icon="seti:python">

```python
import os
from daytona import CreateSandboxBaseParams, Daytona

daytona = Daytona()

sandbox = daytona.create(
    CreateSandboxBaseParams(
        env_vars={
            "AWS_ACCESS_KEY_ID": os.environ["AWS_ACCESS_KEY_ID"],
            "AWS_SECRET_ACCESS_KEY": os.environ["AWS_SECRET_ACCESS_KEY"],
        },
    )
)

# Install mount-s3 at runtime
sandbox.process.exec(
    "sudo apt-get update "
    "&& sudo apt-get install -y --no-install-recommends libfuse2 ca-certificates wget"
)
sandbox.process.exec(
    'arch="$(dpkg --print-architecture | sed s/amd64/x86_64/)" '
    '&& wget -O /tmp/mount-s3.deb '
    '"https://s3.amazonaws.com/mountpoint-s3-release/latest/${arch}/mount-s3.deb" '
    "&& sudo apt-get install -y /tmp/mount-s3.deb"
)

# Mount and use
mount_path = "/home/daytona/s3"
sandbox.process.exec(f"mkdir -p {mount_path} && mount-s3 my-bucket {mount_path}")
response = sandbox.process.exec(f"ls {mount_path}")
print(response.result)
```

</TabItem>
<TabItem label="TypeScript" icon="seti:typescript">

```typescript
import { Daytona } from '@daytona/sdk'

const daytona = new Daytona()

const sandbox = await daytona.create({
  envVars: {
    AWS_ACCESS_KEY_ID: process.env.AWS_ACCESS_KEY_ID!,
    AWS_SECRET_ACCESS_KEY: process.env.AWS_SECRET_ACCESS_KEY!,
  },
})

// Install mount-s3 at runtime
await sandbox.process.executeCommand(
  'sudo apt-get update ' +
    '&& sudo apt-get install -y --no-install-recommends libfuse2 ca-certificates wget',
)
await sandbox.process.executeCommand(
  'arch="$(dpkg --print-architecture | sed s/amd64/x86_64/)" ' +
    '&& wget -O /tmp/mount-s3.deb ' +
    '"https://s3.amazonaws.com/mountpoint-s3-release/latest/${arch}/mount-s3.deb" ' +
    '&& sudo apt-get install -y /tmp/mount-s3.deb',
)

// Mount and use
const mountPath = '/home/daytona/s3'
await sandbox.process.executeCommand(`mkdir -p ${mountPath} && mount-s3 my-bucket ${mountPath}`)
const response = await sandbox.process.executeCommand(`ls ${mountPath}`)
console.log(response.result)
```

</TabItem>
<TabItem label="Ruby" icon="seti:ruby">

```ruby
require 'daytona'

daytona = Daytona::Daytona.new

sandbox = daytona.create(
  Daytona::CreateSandboxBaseParams.new(
    env_vars: {
      'AWS_ACCESS_KEY_ID' => ENV.fetch('AWS_ACCESS_KEY_ID'),
      'AWS_SECRET_ACCESS_KEY' => ENV.fetch('AWS_SECRET_ACCESS_KEY')
    }
  )
)

# Install mount-s3 at runtime
sandbox.process.exec(
  command: 'sudo apt-get update ' \
           '&& sudo apt-get install -y --no-install-recommends libfuse2 ca-certificates wget'
)
sandbox.process.exec(
  command: 'arch="$(dpkg --print-architecture | sed s/amd64/x86_64/)" ' \
           '&& wget -O /tmp/mount-s3.deb ' \
           '"https://s3.amazonaws.com/mountpoint-s3-release/latest/${arch}/mount-s3.deb" ' \
           '&& sudo apt-get install -y /tmp/mount-s3.deb'
)

# Mount and use
mount_path = '/home/daytona/s3'
sandbox.process.exec(command: "mkdir -p #{mount_path} && mount-s3 my-bucket #{mount_path}")
response = sandbox.process.exec(command: "ls #{mount_path}")
puts response.result
```

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

```go
import (
	"context"
	"fmt"
	"log"
	"os"

	"github.com/daytona/clients/sdk-go/pkg/daytona"
	"github.com/daytona/clients/sdk-go/pkg/types"
)

ctx := context.Background()
client, err := daytona.NewClient()
if err != nil {
	log.Fatal(err)
}

sandbox, err := client.Create(ctx, types.SnapshotParams{
	SandboxBaseParams: types.SandboxBaseParams{
		EnvVars: map[string]string{
			"AWS_ACCESS_KEY_ID":     os.Getenv("AWS_ACCESS_KEY_ID"),
			"AWS_SECRET_ACCESS_KEY": os.Getenv("AWS_SECRET_ACCESS_KEY"),
		},
	},
})
if err != nil {
	log.Fatal(err)
}

// Install mount-s3 at runtime
if _, err := sandbox.Process.ExecuteCommand(ctx,
	"sudo apt-get update && sudo apt-get install -y --no-install-recommends libfuse2 ca-certificates wget"); err != nil {
	log.Fatal(err)
}
if _, err := sandbox.Process.ExecuteCommand(ctx,
	`arch="$(dpkg --print-architecture | sed s/amd64/x86_64/)" && `+
		`wget -O /tmp/mount-s3.deb "https://s3.amazonaws.com/mountpoint-s3-release/latest/${arch}/mount-s3.deb" && `+
		`sudo apt-get install -y /tmp/mount-s3.deb`); err != nil {
	log.Fatal(err)
}

// Mount and use
mountPath := "/home/daytona/s3"
if _, err := sandbox.Process.ExecuteCommand(ctx,
	"mkdir -p "+mountPath+" && mount-s3 my-bucket "+mountPath); err != nil {
	log.Fatal(err)
}
response, err := sandbox.Process.ExecuteCommand(ctx, "ls "+mountPath)
if err != nil {
	log.Fatal(err)
}
fmt.Println(response.Result)
```

</TabItem>
<TabItem label="Java" icon="seti:java">

```java
import io.daytona.sdk.Daytona;
import io.daytona.sdk.Sandbox;
import io.daytona.sdk.model.CreateSandboxFromSnapshotParams;
import io.daytona.sdk.model.ExecuteResponse;

import java.util.Map;

public class App {
    public static void main(String[] args) {
        try (Daytona daytona = new Daytona()) {
            CreateSandboxFromSnapshotParams params = new CreateSandboxFromSnapshotParams();
            params.setEnvVars(Map.of(
                "AWS_ACCESS_KEY_ID", System.getenv("AWS_ACCESS_KEY_ID"),
                "AWS_SECRET_ACCESS_KEY", System.getenv("AWS_SECRET_ACCESS_KEY")
            ));
            Sandbox sandbox = daytona.create(params);

            // Install mount-s3 at runtime
            sandbox.getProcess().executeCommand(
                "sudo apt-get update "
                    + "&& sudo apt-get install -y --no-install-recommends libfuse2 ca-certificates wget");
            sandbox.getProcess().executeCommand(
                "arch=\"$(dpkg --print-architecture | sed s/amd64/x86_64/)\" "
                    + "&& wget -O /tmp/mount-s3.deb "
                    + "\"https://s3.amazonaws.com/mountpoint-s3-release/latest/${arch}/mount-s3.deb\" "
                    + "&& sudo apt-get install -y /tmp/mount-s3.deb");

            // Mount and use
            String mountPath = "/home/daytona/s3";
            sandbox.getProcess().executeCommand(
                "mkdir -p " + mountPath + " && mount-s3 my-bucket " + mountPath);
            ExecuteResponse response = sandbox.getProcess().executeCommand("ls " + mountPath);
            System.out.println(response.getResult());
        }
    }
}
```

</TabItem>
</Tabs>

## Mount a Cloudflare R2 bucket [#mount-a-cloudflare-r2-bucket]

Cloudflare R2 is S3-compatible, so the same `mount-s3` tool works. Pass an explicit `--endpoint-url` pointing at your R2 account.

**Credentials** — set `R2_ACCOUNT_ID`, `R2_ACCESS_KEY_ID`, and `R2_SECRET_ACCESS_KEY` in your local environment. R2 is S3-compatible, so the snippets below pass your R2 keys into the sandbox via `envVars` under the `AWS_ACCESS_KEY_ID` / `AWS_SECRET_ACCESS_KEY` names that `mount-s3` expects.

### Pre-built snapshot [#pre-built-snapshot-1]

Build a snapshot with `mount-s3` preinstalled, then launch all R2-enabled sandboxes from that snapshot. The mount flow stays identical to S3 except for the R2 `--endpoint-url`, and startup remains fast because installation is done once at snapshot build time.

#### Build a snapshot [#build-a-snapshot-1]

Create a reusable snapshot that installs the same `mount-s3` tool used for S3. R2 remains S3-compatible, so this snapshot is identical to S3 setup and only the runtime mount command changes.

<Tabs syncKey="language">
<TabItem label="Python" icon="seti:python">

```python
from daytona import CreateSnapshotParams, Daytona, Image

daytona = Daytona()

image = (
  Image.base("daytonaio/sandbox")
  .run_commands(
    "sudo apt-get update "
    "&& sudo apt-get install -y --no-install-recommends libfuse2 ca-certificates wget",
    'arch="$(dpkg --print-architecture | sed s/amd64/x86_64/)" '
    '&& wget -O /tmp/mount-s3.deb '
    '"https://s3.amazonaws.com/mountpoint-s3-release/latest/${arch}/mount-s3.deb" '
    "&& sudo apt-get install -y /tmp/mount-s3.deb "
    "&& rm /tmp/mount-s3.deb",
  )
)

daytona.snapshot.create(
    CreateSnapshotParams(name="fuse-r2", image=image),
    on_logs=print,
)
```

</TabItem>
<TabItem label="TypeScript" icon="seti:typescript">

```typescript
import { Daytona, Image } from '@daytona/sdk'

const daytona = new Daytona()

const image = Image.base('daytonaio/sandbox').runCommands(
  'sudo apt-get update ' +
    '&& sudo apt-get install -y --no-install-recommends libfuse2 ca-certificates wget',
  'arch="$(dpkg --print-architecture | sed s/amd64/x86_64/)" ' +
    '&& wget -O /tmp/mount-s3.deb ' +
    '"https://s3.amazonaws.com/mountpoint-s3-release/latest/${arch}/mount-s3.deb" ' +
    '&& sudo apt-get install -y /tmp/mount-s3.deb ' +
    '&& rm /tmp/mount-s3.deb',
)

await daytona.snapshot.create(
  { name: 'fuse-r2', image },
  { onLogs: console.log },
)
```

</TabItem>
<TabItem label="Ruby" icon="seti:ruby">

```ruby
require 'daytona'

daytona = Daytona::Daytona.new

image = Daytona::Image
  .base('daytonaio/sandbox')
  .run_commands(
    'sudo apt-get update ' \
    '&& sudo apt-get install -y --no-install-recommends libfuse2 ca-certificates wget',
    'arch="$(dpkg --print-architecture | sed s/amd64/x86_64/)" ' \
    '&& wget -O /tmp/mount-s3.deb ' \
    '"https://s3.amazonaws.com/mountpoint-s3-release/latest/${arch}/mount-s3.deb" ' \
    '&& sudo apt-get install -y /tmp/mount-s3.deb ' \
    '&& rm /tmp/mount-s3.deb'
  )

daytona.snapshot.create(
  Daytona::CreateSnapshotParams.new(name: 'fuse-r2', image: image),
  on_logs: proc { |chunk| print(chunk) }
)
```

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

```go
import (
	"context"
	"fmt"
	"log"

	"github.com/daytona/clients/sdk-go/pkg/daytona"
	"github.com/daytona/clients/sdk-go/pkg/types"
)

ctx := context.Background()
client, err := daytona.NewClient()
if err != nil {
	log.Fatal(err)
}

image := daytona.Base("daytonaio/sandbox").
	Run("sudo apt-get update && sudo apt-get install -y --no-install-recommends libfuse2 ca-certificates wget").
	Run(`arch="$(dpkg --print-architecture | sed s/amd64/x86_64/)" && ` +
		`wget -O /tmp/mount-s3.deb "https://s3.amazonaws.com/mountpoint-s3-release/latest/${arch}/mount-s3.deb" && ` +
		`sudo apt-get install -y /tmp/mount-s3.deb && rm /tmp/mount-s3.deb`)

_, logChan, err := client.Snapshot.Create(ctx, &types.CreateSnapshotParams{
	Name:  "fuse-r2",
	Image: image,
})
if err != nil {
	log.Fatal(err)
}
for line := range logChan {
	fmt.Print(line)
}
```

</TabItem>
<TabItem label="Java" icon="seti:java">

```java
import io.daytona.sdk.Daytona;
import io.daytona.sdk.Image;

public class App {
    public static void main(String[] args) {
        try (Daytona daytona = new Daytona()) {
            Image image = Image.base("daytonaio/sandbox")
                .runCommands(
                    "sudo apt-get update "
                        + "&& sudo apt-get install -y --no-install-recommends libfuse2 ca-certificates wget",
                    "arch=\"$(dpkg --print-architecture | sed s/amd64/x86_64/)\" "
                        + "&& wget -O /tmp/mount-s3.deb "
                        + "\"https://s3.amazonaws.com/mountpoint-s3-release/latest/${arch}/mount-s3.deb\" "
                        + "&& sudo apt-get install -y /tmp/mount-s3.deb "
                        + "&& rm /tmp/mount-s3.deb"
                );

            daytona.snapshot().create("fuse-r2", image, System.out::println);
        }
    }
}
```

</TabItem>
</Tabs>

#### Launch and mount [#launch-and-mount-1]

Pass your R2 credentials into the sandbox as `AWS_*` environment variables and mount with the R2 endpoint URL. This keeps the authentication flow compatible with `mount-s3` while targeting your Cloudflare account.

<Tabs syncKey="language">
<TabItem label="Python" icon="seti:python">

```python
import os
from daytona import CreateSandboxFromSnapshotParams, Daytona

daytona = Daytona()

# R2 credentials live in your Cloudflare dashboard under R2 > Manage API Tokens
account_id = os.environ["R2_ACCOUNT_ID"]

sandbox = daytona.create(
    CreateSandboxFromSnapshotParams(
        snapshot="fuse-r2",
        env_vars={
            "AWS_ACCESS_KEY_ID": os.environ["R2_ACCESS_KEY_ID"],
            "AWS_SECRET_ACCESS_KEY": os.environ["R2_SECRET_ACCESS_KEY"],
        },
    )
)

mount_path = "/home/daytona/r2"

sandbox.process.exec(f"mkdir -p {mount_path}")
sandbox.process.exec(
    f"mount-s3 --endpoint-url https://{account_id}.r2.cloudflarestorage.com "
    f"my-r2-bucket {mount_path}"
)

response = sandbox.process.exec(f"ls {mount_path}")
print(response.result)
```

</TabItem>
<TabItem label="TypeScript" icon="seti:typescript">

```typescript
import { Daytona } from '@daytona/sdk'

const daytona = new Daytona()

// R2 credentials live in your Cloudflare dashboard under R2 > Manage API Tokens
const accountId = process.env.R2_ACCOUNT_ID!

const sandbox = await daytona.create({
  snapshot: 'fuse-r2',
  envVars: {
    AWS_ACCESS_KEY_ID: process.env.R2_ACCESS_KEY_ID!,
    AWS_SECRET_ACCESS_KEY: process.env.R2_SECRET_ACCESS_KEY!,
  },
})

const mountPath = '/home/daytona/r2'

await sandbox.process.executeCommand(`mkdir -p ${mountPath}`)
await sandbox.process.executeCommand(
  `mount-s3 --endpoint-url https://${accountId}.r2.cloudflarestorage.com ` +
    `my-r2-bucket ${mountPath}`,
)

const response = await sandbox.process.executeCommand(`ls ${mountPath}`)
console.log(response.result)
```

</TabItem>
<TabItem label="Ruby" icon="seti:ruby">

```ruby
require 'daytona'

daytona = Daytona::Daytona.new

# R2 credentials live in your Cloudflare dashboard under R2 > Manage API Tokens
account_id = ENV.fetch('R2_ACCOUNT_ID')

sandbox = daytona.create(
  Daytona::CreateSandboxFromSnapshotParams.new(
    snapshot: 'fuse-r2',
    env_vars: {
      'AWS_ACCESS_KEY_ID' => ENV.fetch('R2_ACCESS_KEY_ID'),
      'AWS_SECRET_ACCESS_KEY' => ENV.fetch('R2_SECRET_ACCESS_KEY')
    }
  )
)

mount_path = '/home/daytona/r2'

sandbox.process.exec(command: "mkdir -p #{mount_path}")
sandbox.process.exec(
  command: "mount-s3 --endpoint-url https://#{account_id}.r2.cloudflarestorage.com " \
           "my-r2-bucket #{mount_path}"
)

response = sandbox.process.exec(command: "ls #{mount_path}")
puts response.result
```

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

```go
import (
	"context"
	"fmt"
	"log"
	"os"

	"github.com/daytona/clients/sdk-go/pkg/daytona"
	"github.com/daytona/clients/sdk-go/pkg/types"
)

ctx := context.Background()
client, err := daytona.NewClient()
if err != nil {
	log.Fatal(err)
}

// R2 credentials live in your Cloudflare dashboard under R2 > Manage API Tokens
accountID := os.Getenv("R2_ACCOUNT_ID")

sandbox, err := client.Create(ctx, types.SnapshotParams{
	Snapshot: "fuse-r2",
	SandboxBaseParams: types.SandboxBaseParams{
		EnvVars: map[string]string{
			"AWS_ACCESS_KEY_ID":     os.Getenv("R2_ACCESS_KEY_ID"),
			"AWS_SECRET_ACCESS_KEY": os.Getenv("R2_SECRET_ACCESS_KEY"),
		},
	},
})
if err != nil {
	log.Fatal(err)
}

mountPath := "/home/daytona/r2"

if _, err := sandbox.Process.ExecuteCommand(ctx, "mkdir -p "+mountPath); err != nil {
	log.Fatal(err)
}
if _, err := sandbox.Process.ExecuteCommand(ctx,
	"mount-s3 --endpoint-url https://"+accountID+".r2.cloudflarestorage.com "+
		"my-r2-bucket "+mountPath); err != nil {
	log.Fatal(err)
}

response, err := sandbox.Process.ExecuteCommand(ctx, "ls "+mountPath)
if err != nil {
	log.Fatal(err)
}
fmt.Println(response.Result)
```

</TabItem>
<TabItem label="Java" icon="seti:java">

```java
import io.daytona.sdk.Daytona;
import io.daytona.sdk.Sandbox;
import io.daytona.sdk.model.CreateSandboxFromSnapshotParams;
import io.daytona.sdk.model.ExecuteResponse;

import java.util.Map;

public class App {
    public static void main(String[] args) {
        try (Daytona daytona = new Daytona()) {
            // R2 credentials live in your Cloudflare dashboard under R2 > Manage API Tokens
            String accountId = System.getenv("R2_ACCOUNT_ID");

            CreateSandboxFromSnapshotParams params = new CreateSandboxFromSnapshotParams();
            params.setSnapshot("fuse-r2");
            params.setEnvVars(Map.of(
                "AWS_ACCESS_KEY_ID", System.getenv("R2_ACCESS_KEY_ID"),
                "AWS_SECRET_ACCESS_KEY", System.getenv("R2_SECRET_ACCESS_KEY")
            ));
            Sandbox sandbox = daytona.create(params);

            String mountPath = "/home/daytona/r2";

            sandbox.getProcess().executeCommand("mkdir -p " + mountPath);
            sandbox.getProcess().executeCommand(
                "mount-s3 --endpoint-url https://" + accountId + ".r2.cloudflarestorage.com "
                    + "my-r2-bucket " + mountPath);

            ExecuteResponse response = sandbox.getProcess().executeCommand("ls " + mountPath);
            System.out.println(response.getResult());
        }
    }
}
```

</TabItem>
</Tabs>

### Runtime install [#runtime-install-1]

Start from a default sandbox and install `mount-s3` during startup, then mount your bucket with the R2 `--endpoint-url`. This path is convenient for prototyping or one-off tasks, but each new sandbox pays the package installation cost.

<Tabs syncKey="language">
<TabItem label="Python" icon="seti:python">

```python
import os
from daytona import CreateSandboxBaseParams, Daytona

daytona = Daytona()

account_id = os.environ["R2_ACCOUNT_ID"]

sandbox = daytona.create(
    CreateSandboxBaseParams(
        env_vars={
            "AWS_ACCESS_KEY_ID": os.environ["R2_ACCESS_KEY_ID"],
            "AWS_SECRET_ACCESS_KEY": os.environ["R2_SECRET_ACCESS_KEY"],
        },
    )
)

# Install mount-s3
sandbox.process.exec(
    "sudo apt-get update "
    "&& sudo apt-get install -y --no-install-recommends libfuse2 ca-certificates wget"
)
sandbox.process.exec(
    'arch="$(dpkg --print-architecture | sed s/amd64/x86_64/)" '
    '&& wget -O /tmp/mount-s3.deb '
    '"https://s3.amazonaws.com/mountpoint-s3-release/latest/${arch}/mount-s3.deb" '
    "&& sudo apt-get install -y /tmp/mount-s3.deb"
)

# Mount with R2 endpoint
mount_path = "/home/daytona/r2"
sandbox.process.exec(
    f"mkdir -p {mount_path} && "
    f"mount-s3 --endpoint-url https://{account_id}.r2.cloudflarestorage.com "
    f"my-r2-bucket {mount_path}"
)
response = sandbox.process.exec(f"ls {mount_path}")
print(response.result)
```

</TabItem>
<TabItem label="TypeScript" icon="seti:typescript">

```typescript
import { Daytona } from '@daytona/sdk'

const daytona = new Daytona()

const accountId = process.env.R2_ACCOUNT_ID!

const sandbox = await daytona.create({
  envVars: {
    AWS_ACCESS_KEY_ID: process.env.R2_ACCESS_KEY_ID!,
    AWS_SECRET_ACCESS_KEY: process.env.R2_SECRET_ACCESS_KEY!,
  },
})

// Install mount-s3
await sandbox.process.executeCommand(
  'sudo apt-get update ' +
    '&& sudo apt-get install -y --no-install-recommends libfuse2 ca-certificates wget',
)
await sandbox.process.executeCommand(
  'arch="$(dpkg --print-architecture | sed s/amd64/x86_64/)" ' +
    '&& wget -O /tmp/mount-s3.deb ' +
    '"https://s3.amazonaws.com/mountpoint-s3-release/latest/${arch}/mount-s3.deb" ' +
    '&& sudo apt-get install -y /tmp/mount-s3.deb',
)

// Mount with R2 endpoint
const mountPath = '/home/daytona/r2'
await sandbox.process.executeCommand(
  `mkdir -p ${mountPath} && ` +
    `mount-s3 --endpoint-url https://${accountId}.r2.cloudflarestorage.com ` +
    `my-r2-bucket ${mountPath}`,
)
const response = await sandbox.process.executeCommand(`ls ${mountPath}`)
console.log(response.result)
```

</TabItem>
<TabItem label="Ruby" icon="seti:ruby">

```ruby
require 'daytona'

daytona = Daytona::Daytona.new

account_id = ENV.fetch('R2_ACCOUNT_ID')

sandbox = daytona.create(
  Daytona::CreateSandboxBaseParams.new(
    env_vars: {
      'AWS_ACCESS_KEY_ID' => ENV.fetch('R2_ACCESS_KEY_ID'),
      'AWS_SECRET_ACCESS_KEY' => ENV.fetch('R2_SECRET_ACCESS_KEY')
    }
  )
)

# Install mount-s3
sandbox.process.exec(
  command: 'sudo apt-get update ' \
           '&& sudo apt-get install -y --no-install-recommends libfuse2 ca-certificates wget'
)
sandbox.process.exec(
  command: 'arch="$(dpkg --print-architecture | sed s/amd64/x86_64/)" ' \
           '&& wget -O /tmp/mount-s3.deb ' \
           '"https://s3.amazonaws.com/mountpoint-s3-release/latest/${arch}/mount-s3.deb" ' \
           '&& sudo apt-get install -y /tmp/mount-s3.deb'
)

# Mount with R2 endpoint
mount_path = '/home/daytona/r2'
sandbox.process.exec(
  command: "mkdir -p #{mount_path} && " \
           "mount-s3 --endpoint-url https://#{account_id}.r2.cloudflarestorage.com " \
           "my-r2-bucket #{mount_path}"
)
response = sandbox.process.exec(command: "ls #{mount_path}")
puts response.result
```

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

```go
import (
	"context"
	"fmt"
	"log"
	"os"

	"github.com/daytona/clients/sdk-go/pkg/daytona"
	"github.com/daytona/clients/sdk-go/pkg/types"
)

ctx := context.Background()
client, err := daytona.NewClient()
if err != nil {
	log.Fatal(err)
}

accountID := os.Getenv("R2_ACCOUNT_ID")

sandbox, err := client.Create(ctx, types.SnapshotParams{
	SandboxBaseParams: types.SandboxBaseParams{
		EnvVars: map[string]string{
			"AWS_ACCESS_KEY_ID":     os.Getenv("R2_ACCESS_KEY_ID"),
			"AWS_SECRET_ACCESS_KEY": os.Getenv("R2_SECRET_ACCESS_KEY"),
		},
	},
})
if err != nil {
	log.Fatal(err)
}

// Install mount-s3
if _, err := sandbox.Process.ExecuteCommand(ctx,
	"sudo apt-get update && sudo apt-get install -y --no-install-recommends libfuse2 ca-certificates wget"); err != nil {
	log.Fatal(err)
}
if _, err := sandbox.Process.ExecuteCommand(ctx,
	`arch="$(dpkg --print-architecture | sed s/amd64/x86_64/)" && `+
		`wget -O /tmp/mount-s3.deb "https://s3.amazonaws.com/mountpoint-s3-release/latest/${arch}/mount-s3.deb" && `+
		`sudo apt-get install -y /tmp/mount-s3.deb`); err != nil {
	log.Fatal(err)
}

// Mount with R2 endpoint
mountPath := "/home/daytona/r2"
if _, err := sandbox.Process.ExecuteCommand(ctx,
	"mkdir -p "+mountPath+" && "+
		"mount-s3 --endpoint-url https://"+accountID+".r2.cloudflarestorage.com "+
		"my-r2-bucket "+mountPath); err != nil {
	log.Fatal(err)
}
response, err := sandbox.Process.ExecuteCommand(ctx, "ls "+mountPath)
if err != nil {
	log.Fatal(err)
}
fmt.Println(response.Result)
```

</TabItem>
<TabItem label="Java" icon="seti:java">

```java
import io.daytona.sdk.Daytona;
import io.daytona.sdk.Sandbox;
import io.daytona.sdk.model.CreateSandboxFromSnapshotParams;
import io.daytona.sdk.model.ExecuteResponse;

import java.util.Map;

public class App {
    public static void main(String[] args) {
        try (Daytona daytona = new Daytona()) {
            String accountId = System.getenv("R2_ACCOUNT_ID");

            CreateSandboxFromSnapshotParams params = new CreateSandboxFromSnapshotParams();
            params.setEnvVars(Map.of(
                "AWS_ACCESS_KEY_ID", System.getenv("R2_ACCESS_KEY_ID"),
                "AWS_SECRET_ACCESS_KEY", System.getenv("R2_SECRET_ACCESS_KEY")
            ));
            Sandbox sandbox = daytona.create(params);

            // Install mount-s3
            sandbox.getProcess().executeCommand(
                "sudo apt-get update "
                    + "&& sudo apt-get install -y --no-install-recommends libfuse2 ca-certificates wget");
            sandbox.getProcess().executeCommand(
                "arch=\"$(dpkg --print-architecture | sed s/amd64/x86_64/)\" "
                    + "&& wget -O /tmp/mount-s3.deb "
                    + "\"https://s3.amazonaws.com/mountpoint-s3-release/latest/${arch}/mount-s3.deb\" "
                    + "&& sudo apt-get install -y /tmp/mount-s3.deb");

            // Mount with R2 endpoint
            String mountPath = "/home/daytona/r2";
            sandbox.getProcess().executeCommand(
                "mkdir -p " + mountPath + " && "
                    + "mount-s3 --endpoint-url https://" + accountId + ".r2.cloudflarestorage.com "
                    + "my-r2-bucket " + mountPath);

            ExecuteResponse response = sandbox.getProcess().executeCommand("ls " + mountPath);
            System.out.println(response.getResult());
        }
    }
}
```

</TabItem>
</Tabs>

## Mount a Tigris bucket [#mount-a-tigris-bucket]

Mount a Tigris bucket with the same `mount-s3` tool used for S3. Pass `--endpoint-url https://t3.storage.dev`, because Tigris uses one global endpoint with no per-account subdomain. Tigris also supports bucket snapshots and copy-on-write forks through request headers, so each sandbox can use an isolated writable bucket without duplicating source data.

**Credentials** — set `TIGRIS_STORAGE_ACCESS_KEY_ID` and `TIGRIS_STORAGE_SECRET_ACCESS_KEY` in your local environment. The snippets below pass these into the sandbox via `envVars` under the `AWS_ACCESS_KEY_ID` / `AWS_SECRET_ACCESS_KEY` names that `mount-s3` expects.

### Pre-built snapshot [#pre-built-snapshot-2]

Build a snapshot with `mount-s3` preinstalled, then launch Tigris sandboxes from that snapshot. The mount flow matches S3 except for the Tigris `--endpoint-url`, and startup stays fast because installation happens once during snapshot build.

#### Build a snapshot [#build-a-snapshot-2]

Create a reusable snapshot that installs `mount-s3`. Because Tigris is S3-compatible, this setup matches S3 and only the runtime mount command changes.

<Tabs syncKey="language">
<TabItem label="Python" icon="seti:python">

```python
from daytona import CreateSnapshotParams, Daytona, Image

daytona = Daytona()

image = (
  Image.base("daytonaio/sandbox")
  .run_commands(
    "sudo apt-get update "
    "&& sudo apt-get install -y --no-install-recommends libfuse2 ca-certificates wget",
    'arch="$(dpkg --print-architecture | sed s/amd64/x86_64/)" '
    '&& wget -O /tmp/mount-s3.deb '
    '"https://s3.amazonaws.com/mountpoint-s3-release/latest/${arch}/mount-s3.deb" '
    "&& sudo apt-get install -y /tmp/mount-s3.deb "
    "&& rm /tmp/mount-s3.deb",
  )
)

daytona.snapshot.create(
    CreateSnapshotParams(name="fuse-tigris", image=image),
    on_logs=print,
)
```

</TabItem>
<TabItem label="TypeScript" icon="seti:typescript">

```typescript
import { Daytona, Image } from '@daytona/sdk'

const daytona = new Daytona()

const image = Image.base('daytonaio/sandbox').runCommands(
  'sudo apt-get update ' +
    '&& sudo apt-get install -y --no-install-recommends libfuse2 ca-certificates wget',
  'arch="$(dpkg --print-architecture | sed s/amd64/x86_64/)" ' +
    '&& wget -O /tmp/mount-s3.deb ' +
    '"https://s3.amazonaws.com/mountpoint-s3-release/latest/${arch}/mount-s3.deb" ' +
    '&& sudo apt-get install -y /tmp/mount-s3.deb ' +
    '&& rm /tmp/mount-s3.deb',
)

await daytona.snapshot.create(
  { name: 'fuse-tigris', image },
  { onLogs: console.log },
)
```

</TabItem>
<TabItem label="Ruby" icon="seti:ruby">

```ruby
require 'daytona'

daytona = Daytona::Daytona.new

image = Daytona::Image
  .base('daytonaio/sandbox')
  .run_commands(
    'sudo apt-get update ' \
    '&& sudo apt-get install -y --no-install-recommends libfuse2 ca-certificates wget',
    'arch="$(dpkg --print-architecture | sed s/amd64/x86_64/)" ' \
    '&& wget -O /tmp/mount-s3.deb ' \
    '"https://s3.amazonaws.com/mountpoint-s3-release/latest/${arch}/mount-s3.deb" ' \
    '&& sudo apt-get install -y /tmp/mount-s3.deb ' \
    '&& rm /tmp/mount-s3.deb'
  )

daytona.snapshot.create(
  Daytona::CreateSnapshotParams.new(name: 'fuse-tigris', image: image),
  on_logs: proc { |chunk| print(chunk) }
)
```

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

```go
import (
	"context"
	"fmt"
	"log"

	"github.com/daytona/clients/sdk-go/pkg/daytona"
	"github.com/daytona/clients/sdk-go/pkg/types"
)

ctx := context.Background()
client, err := daytona.NewClient()
if err != nil {
	log.Fatal(err)
}

image := daytona.Base("daytonaio/sandbox").
	Run("sudo apt-get update && sudo apt-get install -y --no-install-recommends libfuse2 ca-certificates wget").
	Run(`arch="$(dpkg --print-architecture | sed s/amd64/x86_64/)" && ` +
		`wget -O /tmp/mount-s3.deb "https://s3.amazonaws.com/mountpoint-s3-release/latest/${arch}/mount-s3.deb" && ` +
		`sudo apt-get install -y /tmp/mount-s3.deb && rm /tmp/mount-s3.deb`)

_, logChan, err := client.Snapshot.Create(ctx, &types.CreateSnapshotParams{
	Name:  "fuse-tigris",
	Image: image,
})
if err != nil {
	log.Fatal(err)
}
for line := range logChan {
	fmt.Print(line)
}
```

</TabItem>
<TabItem label="Java" icon="seti:java">

```java
import io.daytona.sdk.Daytona;
import io.daytona.sdk.Image;

public class App {
    public static void main(String[] args) {
        try (Daytona daytona = new Daytona()) {
            Image image = Image.base("daytonaio/sandbox")
                .runCommands(
                    "sudo apt-get update "
                        + "&& sudo apt-get install -y --no-install-recommends libfuse2 ca-certificates wget",
                    "arch=\"$(dpkg --print-architecture | sed s/amd64/x86_64/)\" "
                        + "&& wget -O /tmp/mount-s3.deb "
                        + "\"https://s3.amazonaws.com/mountpoint-s3-release/latest/${arch}/mount-s3.deb\" "
                        + "&& sudo apt-get install -y /tmp/mount-s3.deb "
                        + "&& rm /tmp/mount-s3.deb"
                );

            daytona.snapshot().create("fuse-tigris", image, System.out::println);
        }
    }
}
```

</TabItem>
</Tabs>

#### Launch and mount [#launch-and-mount-2]

Pass Tigris credentials into the sandbox as `AWS_*` environment variables, then mount with the Tigris endpoint URL. This keeps authentication compatible with `mount-s3` while targeting your Tigris account.

<Tabs syncKey="language">
<TabItem label="Python" icon="seti:python">

```python
import os
from daytona import CreateSandboxFromSnapshotParams, Daytona

daytona = Daytona()

sandbox = daytona.create(
    CreateSandboxFromSnapshotParams(
        snapshot="fuse-tigris",
        env_vars={
            "AWS_ACCESS_KEY_ID": os.environ["TIGRIS_STORAGE_ACCESS_KEY_ID"],
            "AWS_SECRET_ACCESS_KEY": os.environ["TIGRIS_STORAGE_SECRET_ACCESS_KEY"],
        },
    )
)

mount_path = "/home/daytona/tigris"

sandbox.process.exec(f"mkdir -p {mount_path}")
sandbox.process.exec(
    f"mount-s3 --endpoint-url https://t3.storage.dev "
    f"my-tigris-bucket {mount_path}"
)

response = sandbox.process.exec(f"ls {mount_path}")
print(response.result)
```

</TabItem>
<TabItem label="TypeScript" icon="seti:typescript">

```typescript
import { Daytona } from '@daytona/sdk'

const daytona = new Daytona()

const sandbox = await daytona.create({
  snapshot: 'fuse-tigris',
  envVars: {
    AWS_ACCESS_KEY_ID: process.env.TIGRIS_STORAGE_ACCESS_KEY_ID!,
    AWS_SECRET_ACCESS_KEY: process.env.TIGRIS_STORAGE_SECRET_ACCESS_KEY!,
  },
})

const mountPath = '/home/daytona/tigris'

await sandbox.process.executeCommand(`mkdir -p ${mountPath}`)
await sandbox.process.executeCommand(
  `mount-s3 --endpoint-url https://t3.storage.dev ` +
    `my-tigris-bucket ${mountPath}`,
)

const response = await sandbox.process.executeCommand(`ls ${mountPath}`)
console.log(response.result)
```

</TabItem>
<TabItem label="Ruby" icon="seti:ruby">

```ruby
require 'daytona'

daytona = Daytona::Daytona.new

sandbox = daytona.create(
  Daytona::CreateSandboxFromSnapshotParams.new(
    snapshot: 'fuse-tigris',
    env_vars: {
      'AWS_ACCESS_KEY_ID' => ENV.fetch('TIGRIS_STORAGE_ACCESS_KEY_ID'),
      'AWS_SECRET_ACCESS_KEY' => ENV.fetch('TIGRIS_STORAGE_SECRET_ACCESS_KEY')
    }
  )
)

mount_path = '/home/daytona/tigris'

sandbox.process.exec(command: "mkdir -p #{mount_path}")
sandbox.process.exec(
  command: 'mount-s3 --endpoint-url https://t3.storage.dev ' \
           "my-tigris-bucket #{mount_path}"
)

response = sandbox.process.exec(command: "ls #{mount_path}")
puts response.result
```

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

```go
import (
	"context"
	"fmt"
	"log"
	"os"

	"github.com/daytona/clients/sdk-go/pkg/daytona"
	"github.com/daytona/clients/sdk-go/pkg/types"
)

ctx := context.Background()
client, err := daytona.NewClient()
if err != nil {
	log.Fatal(err)
}

sandbox, err := client.Create(ctx, types.SnapshotParams{
	Snapshot: "fuse-tigris",
	SandboxBaseParams: types.SandboxBaseParams{
		EnvVars: map[string]string{
			"AWS_ACCESS_KEY_ID":     os.Getenv("TIGRIS_STORAGE_ACCESS_KEY_ID"),
			"AWS_SECRET_ACCESS_KEY": os.Getenv("TIGRIS_STORAGE_SECRET_ACCESS_KEY"),
		},
	},
})
if err != nil {
	log.Fatal(err)
}

mountPath := "/home/daytona/tigris"

if _, err := sandbox.Process.ExecuteCommand(ctx, "mkdir -p "+mountPath); err != nil {
	log.Fatal(err)
}
if _, err := sandbox.Process.ExecuteCommand(ctx,
	"mount-s3 --endpoint-url https://t3.storage.dev "+
		"my-tigris-bucket "+mountPath); err != nil {
	log.Fatal(err)
}

response, err := sandbox.Process.ExecuteCommand(ctx, "ls "+mountPath)
if err != nil {
	log.Fatal(err)
}
fmt.Println(response.Result)
```

</TabItem>
<TabItem label="Java" icon="seti:java">

```java
import io.daytona.sdk.Daytona;
import io.daytona.sdk.Sandbox;
import io.daytona.sdk.model.CreateSandboxFromSnapshotParams;
import io.daytona.sdk.model.ExecuteResponse;

import java.util.Map;

public class App {
    public static void main(String[] args) {
        try (Daytona daytona = new Daytona()) {
            CreateSandboxFromSnapshotParams params = new CreateSandboxFromSnapshotParams();
            params.setSnapshot("fuse-tigris");
            params.setEnvVars(Map.of(
                "AWS_ACCESS_KEY_ID", System.getenv("TIGRIS_STORAGE_ACCESS_KEY_ID"),
                "AWS_SECRET_ACCESS_KEY", System.getenv("TIGRIS_STORAGE_SECRET_ACCESS_KEY")
            ));
            Sandbox sandbox = daytona.create(params);

            String mountPath = "/home/daytona/tigris";

            sandbox.getProcess().executeCommand("mkdir -p " + mountPath);
            sandbox.getProcess().executeCommand(
                "mount-s3 --endpoint-url https://t3.storage.dev "
                    + "my-tigris-bucket " + mountPath);

            ExecuteResponse response = sandbox.getProcess().executeCommand("ls " + mountPath);
            System.out.println(response.getResult());
        }
    }
}
```

</TabItem>
</Tabs>

### Runtime install [#runtime-install-2]

Start from a default sandbox, install `mount-s3` during startup, then mount with the Tigris `--endpoint-url`. This path is convenient for prototyping or one-off tasks, but each new sandbox repeats package installation.

<Tabs syncKey="language">
<TabItem label="Python" icon="seti:python">

```python
import os
from daytona import CreateSandboxBaseParams, Daytona

daytona = Daytona()

sandbox = daytona.create(
    CreateSandboxBaseParams(
        env_vars={
            "AWS_ACCESS_KEY_ID": os.environ["TIGRIS_STORAGE_ACCESS_KEY_ID"],
            "AWS_SECRET_ACCESS_KEY": os.environ["TIGRIS_STORAGE_SECRET_ACCESS_KEY"],
        },
    )
)

# Install mount-s3
sandbox.process.exec(
    "sudo apt-get update "
    "&& sudo apt-get install -y --no-install-recommends libfuse2 ca-certificates wget"
)
sandbox.process.exec(
    'arch="$(dpkg --print-architecture | sed s/amd64/x86_64/)" '
    '&& wget -O /tmp/mount-s3.deb '
    '"https://s3.amazonaws.com/mountpoint-s3-release/latest/${arch}/mount-s3.deb" '
    "&& sudo apt-get install -y /tmp/mount-s3.deb"
)

# Mount with Tigris endpoint
mount_path = "/home/daytona/tigris"
sandbox.process.exec(
    f"mkdir -p {mount_path} && "
    f"mount-s3 --endpoint-url https://t3.storage.dev "
    f"my-tigris-bucket {mount_path}"
)
response = sandbox.process.exec(f"ls {mount_path}")
print(response.result)
```

</TabItem>
<TabItem label="TypeScript" icon="seti:typescript">

```typescript
import { Daytona } from '@daytona/sdk'

const daytona = new Daytona()

const sandbox = await daytona.create({
  envVars: {
    AWS_ACCESS_KEY_ID: process.env.TIGRIS_STORAGE_ACCESS_KEY_ID!,
    AWS_SECRET_ACCESS_KEY: process.env.TIGRIS_STORAGE_SECRET_ACCESS_KEY!,
  },
})

// Install mount-s3
await sandbox.process.executeCommand(
  'sudo apt-get update ' +
    '&& sudo apt-get install -y --no-install-recommends libfuse2 ca-certificates wget',
)
await sandbox.process.executeCommand(
  'arch="$(dpkg --print-architecture | sed s/amd64/x86_64/)" ' +
    '&& wget -O /tmp/mount-s3.deb ' +
    '"https://s3.amazonaws.com/mountpoint-s3-release/latest/${arch}/mount-s3.deb" ' +
    '&& sudo apt-get install -y /tmp/mount-s3.deb',
)

// Mount with Tigris endpoint
const mountPath = '/home/daytona/tigris'
await sandbox.process.executeCommand(
  `mkdir -p ${mountPath} && ` +
    `mount-s3 --endpoint-url https://t3.storage.dev ` +
    `my-tigris-bucket ${mountPath}`,
)
const response = await sandbox.process.executeCommand(`ls ${mountPath}`)
console.log(response.result)
```

</TabItem>
<TabItem label="Ruby" icon="seti:ruby">

```ruby
require 'daytona'

daytona = Daytona::Daytona.new

sandbox = daytona.create(
  Daytona::CreateSandboxBaseParams.new(
    env_vars: {
      'AWS_ACCESS_KEY_ID' => ENV.fetch('TIGRIS_STORAGE_ACCESS_KEY_ID'),
      'AWS_SECRET_ACCESS_KEY' => ENV.fetch('TIGRIS_STORAGE_SECRET_ACCESS_KEY')
    }
  )
)

# Install mount-s3
sandbox.process.exec(
  command: 'sudo apt-get update ' \
           '&& sudo apt-get install -y --no-install-recommends libfuse2 ca-certificates wget'
)
sandbox.process.exec(
  command: 'arch="$(dpkg --print-architecture | sed s/amd64/x86_64/)" ' \
           '&& wget -O /tmp/mount-s3.deb ' \
           '"https://s3.amazonaws.com/mountpoint-s3-release/latest/${arch}/mount-s3.deb" ' \
           '&& sudo apt-get install -y /tmp/mount-s3.deb'
)

# Mount with Tigris endpoint
mount_path = '/home/daytona/tigris'
sandbox.process.exec(
  command: "mkdir -p #{mount_path} && " \
           'mount-s3 --endpoint-url https://t3.storage.dev ' \
           "my-tigris-bucket #{mount_path}"
)
response = sandbox.process.exec(command: "ls #{mount_path}")
puts response.result
```

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

```go
import (
	"context"
	"fmt"
	"log"
	"os"

	"github.com/daytona/clients/sdk-go/pkg/daytona"
	"github.com/daytona/clients/sdk-go/pkg/types"
)

ctx := context.Background()
client, err := daytona.NewClient()
if err != nil {
	log.Fatal(err)
}

sandbox, err := client.Create(ctx, types.SnapshotParams{
	SandboxBaseParams: types.SandboxBaseParams{
		EnvVars: map[string]string{
			"AWS_ACCESS_KEY_ID":     os.Getenv("TIGRIS_STORAGE_ACCESS_KEY_ID"),
			"AWS_SECRET_ACCESS_KEY": os.Getenv("TIGRIS_STORAGE_SECRET_ACCESS_KEY"),
		},
	},
})
if err != nil {
	log.Fatal(err)
}

// Install mount-s3
if _, err := sandbox.Process.ExecuteCommand(ctx,
	"sudo apt-get update && sudo apt-get install -y --no-install-recommends libfuse2 ca-certificates wget"); err != nil {
	log.Fatal(err)
}
if _, err := sandbox.Process.ExecuteCommand(ctx,
	`arch="$(dpkg --print-architecture | sed s/amd64/x86_64/)" && `+
		`wget -O /tmp/mount-s3.deb "https://s3.amazonaws.com/mountpoint-s3-release/latest/${arch}/mount-s3.deb" && `+
		`sudo apt-get install -y /tmp/mount-s3.deb`); err != nil {
	log.Fatal(err)
}

// Mount with Tigris endpoint
mountPath := "/home/daytona/tigris"
if _, err := sandbox.Process.ExecuteCommand(ctx,
	"mkdir -p "+mountPath+" && "+
		"mount-s3 --endpoint-url https://t3.storage.dev "+
		"my-tigris-bucket "+mountPath); err != nil {
	log.Fatal(err)
}
response, err := sandbox.Process.ExecuteCommand(ctx, "ls "+mountPath)
if err != nil {
	log.Fatal(err)
}
fmt.Println(response.Result)
```

</TabItem>
<TabItem label="Java" icon="seti:java">

```java
import io.daytona.sdk.Daytona;
import io.daytona.sdk.Sandbox;
import io.daytona.sdk.model.CreateSandboxFromSnapshotParams;
import io.daytona.sdk.model.ExecuteResponse;

import java.util.Map;

public class App {
    public static void main(String[] args) {
        try (Daytona daytona = new Daytona()) {
            CreateSandboxFromSnapshotParams params = new CreateSandboxFromSnapshotParams();
            params.setEnvVars(Map.of(
                "AWS_ACCESS_KEY_ID", System.getenv("TIGRIS_STORAGE_ACCESS_KEY_ID"),
                "AWS_SECRET_ACCESS_KEY", System.getenv("TIGRIS_STORAGE_SECRET_ACCESS_KEY")
            ));
            Sandbox sandbox = daytona.create(params);

            // Install mount-s3
            sandbox.getProcess().executeCommand(
                "sudo apt-get update "
                    + "&& sudo apt-get install -y --no-install-recommends libfuse2 ca-certificates wget");
            sandbox.getProcess().executeCommand(
                "arch=\"$(dpkg --print-architecture | sed s/amd64/x86_64/)\" "
                    + "&& wget -O /tmp/mount-s3.deb "
                    + "\"https://s3.amazonaws.com/mountpoint-s3-release/latest/${arch}/mount-s3.deb\" "
                    + "&& sudo apt-get install -y /tmp/mount-s3.deb");

            // Mount with Tigris endpoint
            String mountPath = "/home/daytona/tigris";
            sandbox.getProcess().executeCommand(
                "mkdir -p " + mountPath + " && "
                    + "mount-s3 --endpoint-url https://t3.storage.dev "
                    + "my-tigris-bucket " + mountPath);

            ExecuteResponse response = sandbox.getProcess().executeCommand("ls " + mountPath);
            System.out.println(response.getResult());
        }
    }
}
```

</TabItem>
</Tabs>

### Mount a copy-on-write fork per sandbox [#mount-a-copy-on-write-fork-per-sandbox]

A Tigris bucket fork is a new bucket created from a snapshot of a source bucket. The fork shares underlying storage with the source until written to — new writes go only to the fork, and the source bucket and other forks are unaffected. Fork creation is constant-time regardless of source bucket size.

This pattern fits Daytona sandboxes when each sandbox needs a writable copy of a shared dataset (model weights, fixtures, golden data) without duplicating it on every launch.

**Prerequisite** — the source bucket must be created with snapshots enabled. This is a one-time setup, done outside the per-sandbox flow:

```typescript
import { createBucket } from '@tigrisdata/storage'

await createBucket('my-source-bucket', { enableSnapshot: true })
```

In any S3 SDK, send a `CreateBucket` request with the header `X-Tigris-Enable-Snapshot: true`.

The `@tigrisdata/agent-kit` package wraps this workflow as `createForks()`. It snapshots the source bucket and creates one or more forks in a single call. Passing `credentials: { role: 'Editor' }` also creates a scoped access key per fork, so each sandbox can read and write only its own fork bucket instead of the full account.

```typescript
import { createForks, teardownForks } from '@tigrisdata/agent-kit'
import { Daytona } from '@daytona/sdk'

const SOURCE_BUCKET = 'my-source-bucket'

// 1. Snapshot the source and create a fork with a scoped access key
const { data: forkSet, error } = await createForks(SOURCE_BUCKET, 1, {
  credentials: { role: 'Editor' },
})
if (error) throw error
const fork = forkSet.forks[0]

// 2. Launch the sandbox with the fork's scoped credentials
const daytona = new Daytona()
const sandbox = await daytona.create({
  snapshot: 'fuse-tigris',
  envVars: {
    AWS_ACCESS_KEY_ID: fork.credentials!.accessKeyId,
    AWS_SECRET_ACCESS_KEY: fork.credentials!.secretAccessKey,
  },
})

// 3. Mount the fork bucket
const mountPath = '/home/daytona/tigris'
await sandbox.process.executeCommand(`mkdir -p ${mountPath}`)
await sandbox.process.executeCommand(
  `mount-s3 --endpoint-url https://t3.storage.dev ${fork.bucket} ${mountPath}`,
)

try {
  const response = await sandbox.process.executeCommand(`ls ${mountPath}`)
  console.log(response.result)
} finally {
  await daytona.delete(sandbox)
  await teardownForks(forkSet) // revokes the scoped key and deletes the fork bucket
}
```

To run the same workflow from a language without an `agent-kit` equivalent, use any S3 SDK and send the headers documented below. Send `X-Tigris-Snapshot: true` on `CreateBucket` for the source name, then capture `X-Tigris-Snapshot-Version` from the response. Next, send `CreateBucket` for the fork name with `X-Tigris-Fork-Source-Bucket` and `X-Tigris-Fork-Source-Bucket-Snapshot`. Mount the fork with the same `mount-s3` snippets shown above, replacing the bucket name with the fork bucket.

#### Reference: Tigris-specific headers [#reference-tigris-specific-headers]

These headers drive snapshot and fork operations over the S3 API. Use them with any AWS SDK (boto3, aws-sdk-go-v2, aws-sdk-java-v2, aws-sdk-ruby) by attaching a request interceptor.

| Header                                     | Sent on                                   | Purpose                                                                        |
| ------------------------------------------ | ----------------------------------------- | ------------------------------------------------------------------------------ |
| **`X-Tigris-Enable-Snapshot: true`**       | **`CreateBucket`** (new source)           | Enable snapshots on a new bucket. Required before snapshotting it.             |
| **`X-Tigris-Snapshot: true`**              | **`CreateBucket`** (existing source name) | Take a snapshot of the bucket. Optional **`; name=<label>`** suffix labels it. |
| **`X-Tigris-Snapshot-Version`**            | Response to snapshot create               | Snapshot version ID returned to the caller — pass to fork creation.            |
| **`X-Tigris-Fork-Source-Bucket`**          | **`CreateBucket`** (new fork name)        | Source bucket to fork from.                                                    |
| **`X-Tigris-Fork-Source-Bucket-Snapshot`** | **`CreateBucket`** (new fork name)        | Source snapshot version to fork from.                                          |

Source: [**`tigrisdata/storage`**](https://github.com/tigrisdata/storage). See **`shared/headers.ts`** for the full header set.

## Mount a Supabase Storage bucket [#mount-a-supabase-storage-bucket]

Mount a Supabase Storage bucket with the same `mount-s3` tool used for S3. Supabase Storage is [S3-compatible ↗](https://supabase.com/docs/guides/storage/s3/compatibility) and serves each project at `https://<project_ref>.storage.supabase.co/storage/v1/s3`, so pass that endpoint with `--endpoint-url`, the project's region with `--region`, and `--force-path-style`. The path-style flag is required — Supabase serves all buckets from the project hostname rather than per-bucket subdomains, so `mount-s3`'s default virtual-hosted-style addressing (which prepends the bucket name to the endpoint hostname) fails during TLS negotiation.

**Credentials** — in the [Supabase dashboard ↗](https://supabase.com/dashboard/project/_/storage/s3), enable **S3 protocol connection** on your project's **Storage** > **S3** page and generate a pair of S3 access keys. Set `SUPABASE_PROJECT_REF` (the `<project_ref>` subdomain of the endpoint shown on that page), `SUPABASE_REGION` (the project's region, e.g. `us-east-1`), `SUPABASE_S3_ACCESS_KEY_ID`, and `SUPABASE_S3_SECRET_ACCESS_KEY` in your local environment. The snippets below pass the keys into the sandbox via `envVars` under the `AWS_ACCESS_KEY_ID` / `AWS_SECRET_ACCESS_KEY` names that `mount-s3` expects. S3 access keys grant full access to every bucket in the project and bypass [Row Level Security ↗](https://supabase.com/docs/guides/storage/security/access-control) policies, so treat them as server-side secrets.

### Pre-built snapshot [#pre-built-snapshot-3]

Build a snapshot with `mount-s3` preinstalled, then launch Supabase-enabled sandboxes from that snapshot. The mount flow matches S3 except for the Supabase `--endpoint-url`, `--region`, and `--force-path-style` flags, and startup stays fast because installation happens once during snapshot build.

#### Build a snapshot [#build-a-snapshot-3]

Create a reusable snapshot that installs `mount-s3`. Because Supabase Storage is S3-compatible, this setup matches S3 and only the runtime mount command changes.

<Tabs syncKey="language">
<TabItem label="Python" icon="seti:python">

```python
from daytona import CreateSnapshotParams, Daytona, Image

daytona = Daytona()

image = (
  Image.base("daytonaio/sandbox")
  .run_commands(
    "sudo apt-get update "
    "&& sudo apt-get install -y --no-install-recommends libfuse2 ca-certificates wget",
    'arch="$(dpkg --print-architecture | sed s/amd64/x86_64/)" '
    '&& wget -O /tmp/mount-s3.deb '
    '"https://s3.amazonaws.com/mountpoint-s3-release/latest/${arch}/mount-s3.deb" '
    "&& sudo apt-get install -y /tmp/mount-s3.deb "
    "&& rm /tmp/mount-s3.deb",
  )
)

daytona.snapshot.create(
    CreateSnapshotParams(name="fuse-supabase", image=image),
    on_logs=print,
)
```

</TabItem>
<TabItem label="TypeScript" icon="seti:typescript">

```typescript
import { Daytona, Image } from '@daytona/sdk'

const daytona = new Daytona()

const image = Image.base('daytonaio/sandbox').runCommands(
  'sudo apt-get update ' +
    '&& sudo apt-get install -y --no-install-recommends libfuse2 ca-certificates wget',
  'arch="$(dpkg --print-architecture | sed s/amd64/x86_64/)" ' +
    '&& wget -O /tmp/mount-s3.deb ' +
    '"https://s3.amazonaws.com/mountpoint-s3-release/latest/${arch}/mount-s3.deb" ' +
    '&& sudo apt-get install -y /tmp/mount-s3.deb ' +
    '&& rm /tmp/mount-s3.deb',
)

await daytona.snapshot.create(
  { name: 'fuse-supabase', image },
  { onLogs: console.log },
)
```

</TabItem>
<TabItem label="Ruby" icon="seti:ruby">

```ruby
require 'daytona'

daytona = Daytona::Daytona.new

image = Daytona::Image
  .base('daytonaio/sandbox')
  .run_commands(
    'sudo apt-get update ' \
    '&& sudo apt-get install -y --no-install-recommends libfuse2 ca-certificates wget',
    'arch="$(dpkg --print-architecture | sed s/amd64/x86_64/)" ' \
    '&& wget -O /tmp/mount-s3.deb ' \
    '"https://s3.amazonaws.com/mountpoint-s3-release/latest/${arch}/mount-s3.deb" ' \
    '&& sudo apt-get install -y /tmp/mount-s3.deb ' \
    '&& rm /tmp/mount-s3.deb'
  )

daytona.snapshot.create(
  Daytona::CreateSnapshotParams.new(name: 'fuse-supabase', image: image),
  on_logs: proc { |chunk| print(chunk) }
)
```

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

```go
import (
	"context"
	"fmt"
	"log"

	"github.com/daytona/clients/sdk-go/pkg/daytona"
	"github.com/daytona/clients/sdk-go/pkg/types"
)

ctx := context.Background()
client, err := daytona.NewClient()
if err != nil {
	log.Fatal(err)
}

image := daytona.Base("daytonaio/sandbox").
	Run("sudo apt-get update && sudo apt-get install -y --no-install-recommends libfuse2 ca-certificates wget").
	Run(`arch="$(dpkg --print-architecture | sed s/amd64/x86_64/)" && ` +
		`wget -O /tmp/mount-s3.deb "https://s3.amazonaws.com/mountpoint-s3-release/latest/${arch}/mount-s3.deb" && ` +
		`sudo apt-get install -y /tmp/mount-s3.deb && rm /tmp/mount-s3.deb`)

_, logChan, err := client.Snapshot.Create(ctx, &types.CreateSnapshotParams{
	Name:  "fuse-supabase",
	Image: image,
})
if err != nil {
	log.Fatal(err)
}
for line := range logChan {
	fmt.Print(line)
}
```

</TabItem>
<TabItem label="Java" icon="seti:java">

```java
import io.daytona.sdk.Daytona;
import io.daytona.sdk.Image;

public class App {
    public static void main(String[] args) {
        try (Daytona daytona = new Daytona()) {
            Image image = Image.base("daytonaio/sandbox")
                .runCommands(
                    "sudo apt-get update "
                        + "&& sudo apt-get install -y --no-install-recommends libfuse2 ca-certificates wget",
                    "arch=\"$(dpkg --print-architecture | sed s/amd64/x86_64/)\" "
                        + "&& wget -O /tmp/mount-s3.deb "
                        + "\"https://s3.amazonaws.com/mountpoint-s3-release/latest/${arch}/mount-s3.deb\" "
                        + "&& sudo apt-get install -y /tmp/mount-s3.deb "
                        + "&& rm /tmp/mount-s3.deb"
                );

            daytona.snapshot().create("fuse-supabase", image, System.out::println);
        }
    }
}
```

</TabItem>
</Tabs>

#### Launch and mount [#launch-and-mount-3]

Pass your Supabase S3 access keys into the sandbox as `AWS_*` environment variables, then mount with the project's S3 endpoint, region, and path-style addressing. This keeps authentication compatible with `mount-s3` while targeting your Supabase project.

<Tabs syncKey="language">
<TabItem label="Python" icon="seti:python">

```python
import os
from daytona import CreateSandboxFromSnapshotParams, Daytona

daytona = Daytona()

# S3 endpoint, region, and access keys live in your Supabase dashboard under Storage > S3
project_ref = os.environ["SUPABASE_PROJECT_REF"]
region = os.environ["SUPABASE_REGION"]

sandbox = daytona.create(
    CreateSandboxFromSnapshotParams(
        snapshot="fuse-supabase",
        env_vars={
            "AWS_ACCESS_KEY_ID": os.environ["SUPABASE_S3_ACCESS_KEY_ID"],
            "AWS_SECRET_ACCESS_KEY": os.environ["SUPABASE_S3_SECRET_ACCESS_KEY"],
        },
    )
)

mount_path = "/home/daytona/supabase"

sandbox.process.exec(f"mkdir -p {mount_path}")
sandbox.process.exec(
    f"mount-s3 --endpoint-url https://{project_ref}.storage.supabase.co/storage/v1/s3 "
    f"--region {region} --force-path-style "
    f"my-supabase-bucket {mount_path}"
)

response = sandbox.process.exec(f"ls {mount_path}")
print(response.result)
```

</TabItem>
<TabItem label="TypeScript" icon="seti:typescript">

```typescript
import { Daytona } from '@daytona/sdk'

const daytona = new Daytona()

// S3 endpoint, region, and access keys live in your Supabase dashboard under Storage > S3
const projectRef = process.env.SUPABASE_PROJECT_REF!
const region = process.env.SUPABASE_REGION!

const sandbox = await daytona.create({
  snapshot: 'fuse-supabase',
  envVars: {
    AWS_ACCESS_KEY_ID: process.env.SUPABASE_S3_ACCESS_KEY_ID!,
    AWS_SECRET_ACCESS_KEY: process.env.SUPABASE_S3_SECRET_ACCESS_KEY!,
  },
})

const mountPath = '/home/daytona/supabase'

await sandbox.process.executeCommand(`mkdir -p ${mountPath}`)
await sandbox.process.executeCommand(
  `mount-s3 --endpoint-url https://${projectRef}.storage.supabase.co/storage/v1/s3 ` +
    `--region ${region} --force-path-style ` +
    `my-supabase-bucket ${mountPath}`,
)

const response = await sandbox.process.executeCommand(`ls ${mountPath}`)
console.log(response.result)
```

</TabItem>
<TabItem label="Ruby" icon="seti:ruby">

```ruby
require 'daytona'

daytona = Daytona::Daytona.new

# S3 endpoint, region, and access keys live in your Supabase dashboard under Storage > S3
project_ref = ENV.fetch('SUPABASE_PROJECT_REF')
region = ENV.fetch('SUPABASE_REGION')

sandbox = daytona.create(
  Daytona::CreateSandboxFromSnapshotParams.new(
    snapshot: 'fuse-supabase',
    env_vars: {
      'AWS_ACCESS_KEY_ID' => ENV.fetch('SUPABASE_S3_ACCESS_KEY_ID'),
      'AWS_SECRET_ACCESS_KEY' => ENV.fetch('SUPABASE_S3_SECRET_ACCESS_KEY')
    }
  )
)

mount_path = '/home/daytona/supabase'

sandbox.process.exec(command: "mkdir -p #{mount_path}")
sandbox.process.exec(
  command: "mount-s3 --endpoint-url https://#{project_ref}.storage.supabase.co/storage/v1/s3 " \
           "--region #{region} --force-path-style " \
           "my-supabase-bucket #{mount_path}"
)

response = sandbox.process.exec(command: "ls #{mount_path}")
puts response.result
```

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

```go
import (
	"context"
	"fmt"
	"log"
	"os"

	"github.com/daytona/clients/sdk-go/pkg/daytona"
	"github.com/daytona/clients/sdk-go/pkg/types"
)

ctx := context.Background()
client, err := daytona.NewClient()
if err != nil {
	log.Fatal(err)
}

// S3 endpoint, region, and access keys live in your Supabase dashboard under Storage > S3
projectRef := os.Getenv("SUPABASE_PROJECT_REF")
region := os.Getenv("SUPABASE_REGION")

sandbox, err := client.Create(ctx, types.SnapshotParams{
	Snapshot: "fuse-supabase",
	SandboxBaseParams: types.SandboxBaseParams{
		EnvVars: map[string]string{
			"AWS_ACCESS_KEY_ID":     os.Getenv("SUPABASE_S3_ACCESS_KEY_ID"),
			"AWS_SECRET_ACCESS_KEY": os.Getenv("SUPABASE_S3_SECRET_ACCESS_KEY"),
		},
	},
})
if err != nil {
	log.Fatal(err)
}

mountPath := "/home/daytona/supabase"

if _, err := sandbox.Process.ExecuteCommand(ctx, "mkdir -p "+mountPath); err != nil {
	log.Fatal(err)
}
if _, err := sandbox.Process.ExecuteCommand(ctx,
	"mount-s3 --endpoint-url https://"+projectRef+".storage.supabase.co/storage/v1/s3 "+
		"--region "+region+" --force-path-style "+
		"my-supabase-bucket "+mountPath); err != nil {
	log.Fatal(err)
}

response, err := sandbox.Process.ExecuteCommand(ctx, "ls "+mountPath)
if err != nil {
	log.Fatal(err)
}
fmt.Println(response.Result)
```

</TabItem>
<TabItem label="Java" icon="seti:java">

```java
import io.daytona.sdk.Daytona;
import io.daytona.sdk.Sandbox;
import io.daytona.sdk.model.CreateSandboxFromSnapshotParams;
import io.daytona.sdk.model.ExecuteResponse;

import java.util.Map;

public class App {
    public static void main(String[] args) {
        try (Daytona daytona = new Daytona()) {
            // S3 endpoint, region, and access keys live in your Supabase dashboard under Storage > S3
            String projectRef = System.getenv("SUPABASE_PROJECT_REF");
            String region = System.getenv("SUPABASE_REGION");

            CreateSandboxFromSnapshotParams params = new CreateSandboxFromSnapshotParams();
            params.setSnapshot("fuse-supabase");
            params.setEnvVars(Map.of(
                "AWS_ACCESS_KEY_ID", System.getenv("SUPABASE_S3_ACCESS_KEY_ID"),
                "AWS_SECRET_ACCESS_KEY", System.getenv("SUPABASE_S3_SECRET_ACCESS_KEY")
            ));
            Sandbox sandbox = daytona.create(params);

            String mountPath = "/home/daytona/supabase";

            sandbox.getProcess().executeCommand("mkdir -p " + mountPath);
            sandbox.getProcess().executeCommand(
                "mount-s3 --endpoint-url https://" + projectRef + ".storage.supabase.co/storage/v1/s3 "
                    + "--region " + region + " --force-path-style "
                    + "my-supabase-bucket " + mountPath);

            ExecuteResponse response = sandbox.getProcess().executeCommand("ls " + mountPath);
            System.out.println(response.getResult());
        }
    }
}
```

</TabItem>
</Tabs>

### Runtime install [#runtime-install-3]

Start from a default sandbox, install `mount-s3` during startup, then mount with the Supabase `--endpoint-url`, `--region`, and `--force-path-style` flags. This path is convenient for prototyping or one-off tasks, but each new sandbox repeats package installation.

<Tabs syncKey="language">
<TabItem label="Python" icon="seti:python">

```python
import os
from daytona import CreateSandboxBaseParams, Daytona

daytona = Daytona()

project_ref = os.environ["SUPABASE_PROJECT_REF"]
region = os.environ["SUPABASE_REGION"]

sandbox = daytona.create(
    CreateSandboxBaseParams(
        env_vars={
            "AWS_ACCESS_KEY_ID": os.environ["SUPABASE_S3_ACCESS_KEY_ID"],
            "AWS_SECRET_ACCESS_KEY": os.environ["SUPABASE_S3_SECRET_ACCESS_KEY"],
        },
    )
)

# Install mount-s3
sandbox.process.exec(
    "sudo apt-get update "
    "&& sudo apt-get install -y --no-install-recommends libfuse2 ca-certificates wget"
)
sandbox.process.exec(
    'arch="$(dpkg --print-architecture | sed s/amd64/x86_64/)" '
    '&& wget -O /tmp/mount-s3.deb '
    '"https://s3.amazonaws.com/mountpoint-s3-release/latest/${arch}/mount-s3.deb" '
    "&& sudo apt-get install -y /tmp/mount-s3.deb"
)

# Mount with Supabase endpoint
mount_path = "/home/daytona/supabase"
sandbox.process.exec(
    f"mkdir -p {mount_path} && "
    f"mount-s3 --endpoint-url https://{project_ref}.storage.supabase.co/storage/v1/s3 "
    f"--region {region} --force-path-style "
    f"my-supabase-bucket {mount_path}"
)
response = sandbox.process.exec(f"ls {mount_path}")
print(response.result)
```

</TabItem>
<TabItem label="TypeScript" icon="seti:typescript">

```typescript
import { Daytona } from '@daytona/sdk'

const daytona = new Daytona()

const projectRef = process.env.SUPABASE_PROJECT_REF!
const region = process.env.SUPABASE_REGION!

const sandbox = await daytona.create({
  envVars: {
    AWS_ACCESS_KEY_ID: process.env.SUPABASE_S3_ACCESS_KEY_ID!,
    AWS_SECRET_ACCESS_KEY: process.env.SUPABASE_S3_SECRET_ACCESS_KEY!,
  },
})

// Install mount-s3
await sandbox.process.executeCommand(
  'sudo apt-get update ' +
    '&& sudo apt-get install -y --no-install-recommends libfuse2 ca-certificates wget',
)
await sandbox.process.executeCommand(
  'arch="$(dpkg --print-architecture | sed s/amd64/x86_64/)" ' +
    '&& wget -O /tmp/mount-s3.deb ' +
    '"https://s3.amazonaws.com/mountpoint-s3-release/latest/${arch}/mount-s3.deb" ' +
    '&& sudo apt-get install -y /tmp/mount-s3.deb',
)

// Mount with Supabase endpoint
const mountPath = '/home/daytona/supabase'
await sandbox.process.executeCommand(
  `mkdir -p ${mountPath} && ` +
    `mount-s3 --endpoint-url https://${projectRef}.storage.supabase.co/storage/v1/s3 ` +
    `--region ${region} --force-path-style ` +
    `my-supabase-bucket ${mountPath}`,
)
const response = await sandbox.process.executeCommand(`ls ${mountPath}`)
console.log(response.result)
```

</TabItem>
<TabItem label="Ruby" icon="seti:ruby">

```ruby
require 'daytona'

daytona = Daytona::Daytona.new

project_ref = ENV.fetch('SUPABASE_PROJECT_REF')
region = ENV.fetch('SUPABASE_REGION')

sandbox = daytona.create(
  Daytona::CreateSandboxBaseParams.new(
    env_vars: {
      'AWS_ACCESS_KEY_ID' => ENV.fetch('SUPABASE_S3_ACCESS_KEY_ID'),
      'AWS_SECRET_ACCESS_KEY' => ENV.fetch('SUPABASE_S3_SECRET_ACCESS_KEY')
    }
  )
)

# Install mount-s3
sandbox.process.exec(
  command: 'sudo apt-get update ' \
           '&& sudo apt-get install -y --no-install-recommends libfuse2 ca-certificates wget'
)
sandbox.process.exec(
  command: 'arch="$(dpkg --print-architecture | sed s/amd64/x86_64/)" ' \
           '&& wget -O /tmp/mount-s3.deb ' \
           '"https://s3.amazonaws.com/mountpoint-s3-release/latest/${arch}/mount-s3.deb" ' \
           '&& sudo apt-get install -y /tmp/mount-s3.deb'
)

# Mount with Supabase endpoint
mount_path = '/home/daytona/supabase'
sandbox.process.exec(
  command: "mkdir -p #{mount_path} && " \
           "mount-s3 --endpoint-url https://#{project_ref}.storage.supabase.co/storage/v1/s3 " \
           "--region #{region} --force-path-style " \
           "my-supabase-bucket #{mount_path}"
)
response = sandbox.process.exec(command: "ls #{mount_path}")
puts response.result
```

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

```go
import (
	"context"
	"fmt"
	"log"
	"os"

	"github.com/daytona/clients/sdk-go/pkg/daytona"
	"github.com/daytona/clients/sdk-go/pkg/types"
)

ctx := context.Background()
client, err := daytona.NewClient()
if err != nil {
	log.Fatal(err)
}

projectRef := os.Getenv("SUPABASE_PROJECT_REF")
region := os.Getenv("SUPABASE_REGION")

sandbox, err := client.Create(ctx, types.SnapshotParams{
	SandboxBaseParams: types.SandboxBaseParams{
		EnvVars: map[string]string{
			"AWS_ACCESS_KEY_ID":     os.Getenv("SUPABASE_S3_ACCESS_KEY_ID"),
			"AWS_SECRET_ACCESS_KEY": os.Getenv("SUPABASE_S3_SECRET_ACCESS_KEY"),
		},
	},
})
if err != nil {
	log.Fatal(err)
}

// Install mount-s3
if _, err := sandbox.Process.ExecuteCommand(ctx,
	"sudo apt-get update && sudo apt-get install -y --no-install-recommends libfuse2 ca-certificates wget"); err != nil {
	log.Fatal(err)
}
if _, err := sandbox.Process.ExecuteCommand(ctx,
	`arch="$(dpkg --print-architecture | sed s/amd64/x86_64/)" && `+
		`wget -O /tmp/mount-s3.deb "https://s3.amazonaws.com/mountpoint-s3-release/latest/${arch}/mount-s3.deb" && `+
		`sudo apt-get install -y /tmp/mount-s3.deb`); err != nil {
	log.Fatal(err)
}

// Mount with Supabase endpoint
mountPath := "/home/daytona/supabase"
if _, err := sandbox.Process.ExecuteCommand(ctx,
	"mkdir -p "+mountPath+" && "+
		"mount-s3 --endpoint-url https://"+projectRef+".storage.supabase.co/storage/v1/s3 "+
		"--region "+region+" --force-path-style "+
		"my-supabase-bucket "+mountPath); err != nil {
	log.Fatal(err)
}
response, err := sandbox.Process.ExecuteCommand(ctx, "ls "+mountPath)
if err != nil {
	log.Fatal(err)
}
fmt.Println(response.Result)
```

</TabItem>
<TabItem label="Java" icon="seti:java">

```java
import io.daytona.sdk.Daytona;
import io.daytona.sdk.Sandbox;
import io.daytona.sdk.model.CreateSandboxFromSnapshotParams;
import io.daytona.sdk.model.ExecuteResponse;

import java.util.Map;

public class App {
    public static void main(String[] args) {
        try (Daytona daytona = new Daytona()) {
            String projectRef = System.getenv("SUPABASE_PROJECT_REF");
            String region = System.getenv("SUPABASE_REGION");

            CreateSandboxFromSnapshotParams params = new CreateSandboxFromSnapshotParams();
            params.setEnvVars(Map.of(
                "AWS_ACCESS_KEY_ID", System.getenv("SUPABASE_S3_ACCESS_KEY_ID"),
                "AWS_SECRET_ACCESS_KEY", System.getenv("SUPABASE_S3_SECRET_ACCESS_KEY")
            ));
            Sandbox sandbox = daytona.create(params);

            // Install mount-s3
            sandbox.getProcess().executeCommand(
                "sudo apt-get update "
                    + "&& sudo apt-get install -y --no-install-recommends libfuse2 ca-certificates wget");
            sandbox.getProcess().executeCommand(
                "arch=\"$(dpkg --print-architecture | sed s/amd64/x86_64/)\" "
                    + "&& wget -O /tmp/mount-s3.deb "
                    + "\"https://s3.amazonaws.com/mountpoint-s3-release/latest/${arch}/mount-s3.deb\" "
                    + "&& sudo apt-get install -y /tmp/mount-s3.deb");

            // Mount with Supabase endpoint
            String mountPath = "/home/daytona/supabase";
            sandbox.getProcess().executeCommand(
                "mkdir -p " + mountPath + " && "
                    + "mount-s3 --endpoint-url https://" + projectRef + ".storage.supabase.co/storage/v1/s3 "
                    + "--region " + region + " --force-path-style "
                    + "my-supabase-bucket " + mountPath);

            ExecuteResponse response = sandbox.getProcess().executeCommand("ls " + mountPath);
            System.out.println(response.getResult());
        }
    }
}
```

</TabItem>
</Tabs>

## Mount a Google Cloud Storage bucket [#mount-a-google-cloud-storage-bucket]

Mount a GCS bucket using [gcsfuse ↗](https://github.com/GoogleCloudPlatform/gcsfuse) — Google's official FUSE client.

**Credentials** — `gcsfuse` reads a service account JSON key file. The snippets below read the key from a local path on your host and upload it into the sandbox via `sandbox.fs`.

<Callout type="note">
Daytona's base image is Debian Trixie, but Google has not published a Trixie-specific gcsfuse repository yet. The `gcsfuse-bookworm` repo packages run cleanly on Trixie, so we use them in the snippets below.
</Callout>

### Pre-built snapshot [#pre-built-snapshot-4]

Build a snapshot with `gcsfuse` preinstalled, then launch all GCS-enabled sandboxes from that snapshot. This avoids repeating apt repository setup and package installation for every sandbox, which makes startup behavior more consistent.

#### Build a snapshot [#build-a-snapshot-4]

Create a reusable snapshot that installs `gcsfuse` plus its apt repository configuration. After this step, GCS-enabled sandboxes can mount immediately without repeating package setup.

<Tabs syncKey="language">
<TabItem label="Python" icon="seti:python">

```python
from daytona import CreateSnapshotParams, Daytona, Image

daytona = Daytona()

image = (
  Image.base("daytonaio/sandbox")
  .run_commands(
    "sudo apt-get update "
    "&& sudo apt-get install -y --no-install-recommends ca-certificates curl gnupg",
    "sudo mkdir -p /etc/apt/keyrings "
    "&& curl -fsSL https://packages.cloud.google.com/apt/doc/apt-key.gpg "
    "| sudo gpg --dearmor -o /etc/apt/keyrings/gcsfuse.gpg",
    'echo "deb [signed-by=/etc/apt/keyrings/gcsfuse.gpg] '
    'https://packages.cloud.google.com/apt gcsfuse-bookworm main" '
    "| sudo tee /etc/apt/sources.list.d/gcsfuse.list",
    "sudo apt-get update && sudo apt-get install -y gcsfuse",
  )
)

daytona.snapshot.create(
    CreateSnapshotParams(name="fuse-gcs", image=image),
    on_logs=print,
)
```

</TabItem>
<TabItem label="TypeScript" icon="seti:typescript">

```typescript
import { Daytona, Image } from '@daytona/sdk'

const daytona = new Daytona()

const image = Image.base('daytonaio/sandbox').runCommands(
  'sudo apt-get update ' +
    '&& sudo apt-get install -y --no-install-recommends ca-certificates curl gnupg',
  'sudo mkdir -p /etc/apt/keyrings ' +
    '&& curl -fsSL https://packages.cloud.google.com/apt/doc/apt-key.gpg ' +
    '| sudo gpg --dearmor -o /etc/apt/keyrings/gcsfuse.gpg',
  'echo "deb [signed-by=/etc/apt/keyrings/gcsfuse.gpg] ' +
    'https://packages.cloud.google.com/apt gcsfuse-bookworm main" ' +
    '| sudo tee /etc/apt/sources.list.d/gcsfuse.list',
  'sudo apt-get update && sudo apt-get install -y gcsfuse',
)

await daytona.snapshot.create(
  { name: 'fuse-gcs', image },
  { onLogs: console.log },
)
```

</TabItem>
<TabItem label="Ruby" icon="seti:ruby">

```ruby
require 'daytona'

daytona = Daytona::Daytona.new

image = Daytona::Image
  .base('daytonaio/sandbox')
  .run_commands(
    'sudo apt-get update ' \
    '&& sudo apt-get install -y --no-install-recommends ca-certificates curl gnupg',
    'sudo mkdir -p /etc/apt/keyrings ' \
    '&& curl -fsSL https://packages.cloud.google.com/apt/doc/apt-key.gpg ' \
    '| sudo gpg --dearmor -o /etc/apt/keyrings/gcsfuse.gpg',
    'echo "deb [signed-by=/etc/apt/keyrings/gcsfuse.gpg] ' \
    'https://packages.cloud.google.com/apt gcsfuse-bookworm main" ' \
    '| sudo tee /etc/apt/sources.list.d/gcsfuse.list',
    'sudo apt-get update && sudo apt-get install -y gcsfuse'
  )

daytona.snapshot.create(
  Daytona::CreateSnapshotParams.new(name: 'fuse-gcs', image: image),
  on_logs: proc { |chunk| print(chunk) }
)
```

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

```go
import (
	"context"
	"fmt"
	"log"

	"github.com/daytona/clients/sdk-go/pkg/daytona"
	"github.com/daytona/clients/sdk-go/pkg/types"
)

ctx := context.Background()
client, err := daytona.NewClient()
if err != nil {
	log.Fatal(err)
}

image := daytona.Base("daytonaio/sandbox").
	Run("sudo apt-get update && sudo apt-get install -y --no-install-recommends ca-certificates curl gnupg").
	Run("sudo mkdir -p /etc/apt/keyrings && " +
		"curl -fsSL https://packages.cloud.google.com/apt/doc/apt-key.gpg | " +
		"sudo gpg --dearmor -o /etc/apt/keyrings/gcsfuse.gpg").
	Run(`echo "deb [signed-by=/etc/apt/keyrings/gcsfuse.gpg] ` +
		`https://packages.cloud.google.com/apt gcsfuse-bookworm main" | ` +
		`sudo tee /etc/apt/sources.list.d/gcsfuse.list`).
	Run("sudo apt-get update && sudo apt-get install -y gcsfuse")

_, logChan, err := client.Snapshot.Create(ctx, &types.CreateSnapshotParams{
	Name:  "fuse-gcs",
	Image: image,
})
if err != nil {
	log.Fatal(err)
}
for line := range logChan {
	fmt.Print(line)
}
```

</TabItem>
<TabItem label="Java" icon="seti:java">

```java
import io.daytona.sdk.Daytona;
import io.daytona.sdk.Image;

public class App {
    public static void main(String[] args) {
        try (Daytona daytona = new Daytona()) {
            Image image = Image.base("daytonaio/sandbox")
                .runCommands(
                    "sudo apt-get update "
                        + "&& sudo apt-get install -y --no-install-recommends ca-certificates curl gnupg",
                    "sudo mkdir -p /etc/apt/keyrings "
                        + "&& curl -fsSL https://packages.cloud.google.com/apt/doc/apt-key.gpg "
                        + "| sudo gpg --dearmor -o /etc/apt/keyrings/gcsfuse.gpg",
                    "echo \"deb [signed-by=/etc/apt/keyrings/gcsfuse.gpg] "
                        + "https://packages.cloud.google.com/apt gcsfuse-bookworm main\" "
                        + "| sudo tee /etc/apt/sources.list.d/gcsfuse.list",
                    "sudo apt-get update && sudo apt-get install -y gcsfuse"
                );

            daytona.snapshot().create("fuse-gcs", image, System.out::println);
        }
    }
}
```

</TabItem>
</Tabs>

#### Launch and mount [#launch-and-mount-4]

`gcsfuse` authenticates to GCS with a service account JSON key. Upload it into the sandbox via `sandbox.fs` and point `gcsfuse` at it with `--key-file`.

<Tabs syncKey="language">
<TabItem label="Python" icon="seti:python">

```python
import os
from daytona import CreateSandboxFromSnapshotParams, Daytona

daytona = Daytona()

# GCS_SERVICE_ACCOUNT_KEY holds the full service account JSON as a string
service_account_key = os.environ["GCS_SERVICE_ACCOUNT_KEY"].encode()

sandbox = daytona.create(CreateSandboxFromSnapshotParams(snapshot="fuse-gcs"))

mount_path = "/home/daytona/gcs"
key_path = "/home/daytona/.gcs-key.json"

# Upload the key file into the sandbox
sandbox.fs.upload_file(service_account_key, key_path)
sandbox.process.exec(f"chmod 600 {key_path}")

# Mount the bucket
sandbox.process.exec(f"mkdir -p {mount_path}")
sandbox.process.exec(f"gcsfuse --key-file={key_path} my-gcs-bucket {mount_path}")

# Use the mount
response = sandbox.process.exec(f"ls {mount_path}")
print(response.result)
```

</TabItem>
<TabItem label="TypeScript" icon="seti:typescript">

```typescript
import { Daytona } from '@daytona/sdk'

const daytona = new Daytona()

// GCS_SERVICE_ACCOUNT_KEY holds the full service account JSON as a string
const serviceAccountKey = Buffer.from(process.env.GCS_SERVICE_ACCOUNT_KEY!)

const sandbox = await daytona.create({ snapshot: 'fuse-gcs' })

const mountPath = '/home/daytona/gcs'
const keyPath = '/home/daytona/.gcs-key.json'

// Upload the key file into the sandbox
await sandbox.fs.uploadFile(serviceAccountKey, keyPath)
await sandbox.process.executeCommand(`chmod 600 ${keyPath}`)

// Mount the bucket
await sandbox.process.executeCommand(`mkdir -p ${mountPath}`)
await sandbox.process.executeCommand(`gcsfuse --key-file=${keyPath} my-gcs-bucket ${mountPath}`)

// Use the mount
const response = await sandbox.process.executeCommand(`ls ${mountPath}`)
console.log(response.result)
```

</TabItem>
<TabItem label="Ruby" icon="seti:ruby">

```ruby
require 'daytona'

daytona = Daytona::Daytona.new

# GCS_SERVICE_ACCOUNT_KEY holds the full service account JSON as a string
service_account_key = ENV.fetch('GCS_SERVICE_ACCOUNT_KEY')

sandbox = daytona.create(
  Daytona::CreateSandboxFromSnapshotParams.new(snapshot: 'fuse-gcs')
)

mount_path = '/home/daytona/gcs'
key_path = '/home/daytona/.gcs-key.json'

# Upload the key file into the sandbox
sandbox.fs.upload_file(service_account_key, key_path)
sandbox.process.exec(command: "chmod 600 #{key_path}")

# Mount the bucket
sandbox.process.exec(command: "mkdir -p #{mount_path}")
sandbox.process.exec(command: "gcsfuse --key-file=#{key_path} my-gcs-bucket #{mount_path}")

# Use the mount
response = sandbox.process.exec(command: "ls #{mount_path}")
puts response.result
```

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

```go
import (
	"context"
	"fmt"
	"log"
	"os"

	"github.com/daytona/clients/sdk-go/pkg/daytona"
	"github.com/daytona/clients/sdk-go/pkg/types"
)

ctx := context.Background()
client, err := daytona.NewClient()
if err != nil {
	log.Fatal(err)
}

// GCS_SERVICE_ACCOUNT_KEY holds the full service account JSON as a string
serviceAccountKey := []byte(os.Getenv("GCS_SERVICE_ACCOUNT_KEY"))

sandbox, err := client.Create(ctx, types.SnapshotParams{
	Snapshot: "fuse-gcs",
})
if err != nil {
	log.Fatal(err)
}

mountPath := "/home/daytona/gcs"
keyPath := "/home/daytona/.gcs-key.json"

// Upload the key file into the sandbox
if err := sandbox.FileSystem.UploadFile(ctx, serviceAccountKey, keyPath); err != nil {
	log.Fatal(err)
}
if _, err := sandbox.Process.ExecuteCommand(ctx, "chmod 600 "+keyPath); err != nil {
	log.Fatal(err)
}

// Mount the bucket
if _, err := sandbox.Process.ExecuteCommand(ctx, "mkdir -p "+mountPath); err != nil {
	log.Fatal(err)
}
if _, err := sandbox.Process.ExecuteCommand(ctx,
	"gcsfuse --key-file="+keyPath+" my-gcs-bucket "+mountPath); err != nil {
	log.Fatal(err)
}

// Use the mount
response, err := sandbox.Process.ExecuteCommand(ctx, "ls "+mountPath)
if err != nil {
	log.Fatal(err)
}
fmt.Println(response.Result)
```

</TabItem>
<TabItem label="Java" icon="seti:java">

```java
import io.daytona.sdk.Daytona;
import io.daytona.sdk.Sandbox;
import io.daytona.sdk.model.CreateSandboxFromSnapshotParams;
import io.daytona.sdk.model.ExecuteResponse;

import java.nio.charset.StandardCharsets;

public class App {
    public static void main(String[] args) {
        try (Daytona daytona = new Daytona()) {
            // GCS_SERVICE_ACCOUNT_KEY holds the full service account JSON as a string
            byte[] serviceAccountKey = System.getenv("GCS_SERVICE_ACCOUNT_KEY")
                .getBytes(StandardCharsets.UTF_8);

            CreateSandboxFromSnapshotParams params = new CreateSandboxFromSnapshotParams();
            params.setSnapshot("fuse-gcs");
            Sandbox sandbox = daytona.create(params);

            String mountPath = "/home/daytona/gcs";
            String keyPath = "/home/daytona/.gcs-key.json";

            // Upload the key file into the sandbox
            sandbox.fs.uploadFile(serviceAccountKey, keyPath);
            sandbox.getProcess().executeCommand("chmod 600 " + keyPath);

            // Mount the bucket
            sandbox.getProcess().executeCommand("mkdir -p " + mountPath);
            sandbox.getProcess().executeCommand(
                "gcsfuse --key-file=" + keyPath + " my-gcs-bucket " + mountPath);

            // Use the mount
            ExecuteResponse response = sandbox.getProcess().executeCommand("ls " + mountPath);
            System.out.println(response.getResult());
        }
    }
}
```

</TabItem>
</Tabs>

### Runtime install [#runtime-install-4]

Start from a default sandbox and install `gcsfuse` when the sandbox starts, then upload the service account key and mount the bucket. This is the fastest way to iterate on setup, but every sandbox repeats install and key staging steps.

<Tabs syncKey="language">
<TabItem label="Python" icon="seti:python">

```python
import os
from daytona import CreateSandboxBaseParams, Daytona

daytona = Daytona()

# GCS_SERVICE_ACCOUNT_KEY holds the full service account JSON as a string
service_account_key = os.environ["GCS_SERVICE_ACCOUNT_KEY"].encode()

sandbox = daytona.create(CreateSandboxBaseParams())

# Install gcsfuse from the bookworm repo (works on Trixie)
sandbox.process.exec(
    "sudo apt-get update "
    "&& sudo apt-get install -y --no-install-recommends ca-certificates curl gnupg"
)
sandbox.process.exec(
    "sudo mkdir -p /etc/apt/keyrings "
    "&& curl -fsSL https://packages.cloud.google.com/apt/doc/apt-key.gpg "
    "| sudo gpg --dearmor -o /etc/apt/keyrings/gcsfuse.gpg"
)
sandbox.process.exec(
    'echo "deb [signed-by=/etc/apt/keyrings/gcsfuse.gpg] '
    'https://packages.cloud.google.com/apt gcsfuse-bookworm main" '
    "| sudo tee /etc/apt/sources.list.d/gcsfuse.list "
    "&& sudo apt-get update && sudo apt-get install -y gcsfuse"
)

# Upload the key and mount
mount_path = "/home/daytona/gcs"
key_path = "/home/daytona/.gcs-key.json"
sandbox.fs.upload_file(service_account_key, key_path)
sandbox.process.exec(f"chmod 600 {key_path}")
sandbox.process.exec(f"mkdir -p {mount_path}")
sandbox.process.exec(f"gcsfuse --key-file={key_path} my-gcs-bucket {mount_path}")

response = sandbox.process.exec(f"ls {mount_path}")
print(response.result)
```

</TabItem>
<TabItem label="TypeScript" icon="seti:typescript">

```typescript
import { Daytona } from '@daytona/sdk'

const daytona = new Daytona()

// GCS_SERVICE_ACCOUNT_KEY holds the full service account JSON as a string
const serviceAccountKey = Buffer.from(process.env.GCS_SERVICE_ACCOUNT_KEY!)

const sandbox = await daytona.create()

// Install gcsfuse from the bookworm repo (works on Trixie)
await sandbox.process.executeCommand(
  'sudo apt-get update ' +
    '&& sudo apt-get install -y --no-install-recommends ca-certificates curl gnupg',
)
await sandbox.process.executeCommand(
  'sudo mkdir -p /etc/apt/keyrings ' +
    '&& curl -fsSL https://packages.cloud.google.com/apt/doc/apt-key.gpg ' +
    '| sudo gpg --dearmor -o /etc/apt/keyrings/gcsfuse.gpg',
)
await sandbox.process.executeCommand(
  'echo "deb [signed-by=/etc/apt/keyrings/gcsfuse.gpg] ' +
    'https://packages.cloud.google.com/apt gcsfuse-bookworm main" ' +
    '| sudo tee /etc/apt/sources.list.d/gcsfuse.list ' +
    '&& sudo apt-get update && sudo apt-get install -y gcsfuse',
)

// Upload the key and mount
const mountPath = '/home/daytona/gcs'
const keyPath = '/home/daytona/.gcs-key.json'
await sandbox.fs.uploadFile(serviceAccountKey, keyPath)
await sandbox.process.executeCommand(`chmod 600 ${keyPath}`)
await sandbox.process.executeCommand(`mkdir -p ${mountPath}`)
await sandbox.process.executeCommand(`gcsfuse --key-file=${keyPath} my-gcs-bucket ${mountPath}`)

const response = await sandbox.process.executeCommand(`ls ${mountPath}`)
console.log(response.result)
```

</TabItem>
<TabItem label="Ruby" icon="seti:ruby">

```ruby
require 'daytona'

daytona = Daytona::Daytona.new

# GCS_SERVICE_ACCOUNT_KEY holds the full service account JSON as a string
service_account_key = ENV.fetch('GCS_SERVICE_ACCOUNT_KEY')

sandbox = daytona.create(Daytona::CreateSandboxBaseParams.new)

# Install gcsfuse from the bookworm repo (works on Trixie)
sandbox.process.exec(
  command: 'sudo apt-get update ' \
           '&& sudo apt-get install -y --no-install-recommends ca-certificates curl gnupg'
)
sandbox.process.exec(
  command: 'sudo mkdir -p /etc/apt/keyrings ' \
           '&& curl -fsSL https://packages.cloud.google.com/apt/doc/apt-key.gpg ' \
           '| sudo gpg --dearmor -o /etc/apt/keyrings/gcsfuse.gpg'
)
sandbox.process.exec(
  command: 'echo "deb [signed-by=/etc/apt/keyrings/gcsfuse.gpg] ' \
           'https://packages.cloud.google.com/apt gcsfuse-bookworm main" ' \
           '| sudo tee /etc/apt/sources.list.d/gcsfuse.list ' \
           '&& sudo apt-get update && sudo apt-get install -y gcsfuse'
)

# Upload the key and mount
mount_path = '/home/daytona/gcs'
key_path = '/home/daytona/.gcs-key.json'
sandbox.fs.upload_file(service_account_key, key_path)
sandbox.process.exec(command: "chmod 600 #{key_path}")
sandbox.process.exec(command: "mkdir -p #{mount_path}")
sandbox.process.exec(command: "gcsfuse --key-file=#{key_path} my-gcs-bucket #{mount_path}")

response = sandbox.process.exec(command: "ls #{mount_path}")
puts response.result
```

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

```go
import (
	"context"
	"fmt"
	"log"
	"os"

	"github.com/daytona/clients/sdk-go/pkg/daytona"
	"github.com/daytona/clients/sdk-go/pkg/types"
)

ctx := context.Background()
client, err := daytona.NewClient()
if err != nil {
	log.Fatal(err)
}

// GCS_SERVICE_ACCOUNT_KEY holds the full service account JSON as a string
serviceAccountKey := []byte(os.Getenv("GCS_SERVICE_ACCOUNT_KEY"))

sandbox, err := client.Create(ctx, types.SnapshotParams{})
if err != nil {
	log.Fatal(err)
}

// Install gcsfuse from the bookworm repo (works on Trixie)
if _, err := sandbox.Process.ExecuteCommand(ctx,
	"sudo apt-get update && sudo apt-get install -y --no-install-recommends ca-certificates curl gnupg"); err != nil {
	log.Fatal(err)
}
if _, err := sandbox.Process.ExecuteCommand(ctx,
	"sudo mkdir -p /etc/apt/keyrings && "+
		"curl -fsSL https://packages.cloud.google.com/apt/doc/apt-key.gpg | "+
		"sudo gpg --dearmor -o /etc/apt/keyrings/gcsfuse.gpg"); err != nil {
	log.Fatal(err)
}
if _, err := sandbox.Process.ExecuteCommand(ctx,
	`echo "deb [signed-by=/etc/apt/keyrings/gcsfuse.gpg] `+
		`https://packages.cloud.google.com/apt gcsfuse-bookworm main" | `+
		`sudo tee /etc/apt/sources.list.d/gcsfuse.list && `+
		`sudo apt-get update && sudo apt-get install -y gcsfuse`); err != nil {
	log.Fatal(err)
}

// Upload the key and mount
mountPath := "/home/daytona/gcs"
keyPath := "/home/daytona/.gcs-key.json"
if err := sandbox.FileSystem.UploadFile(ctx, serviceAccountKey, keyPath); err != nil {
	log.Fatal(err)
}
if _, err := sandbox.Process.ExecuteCommand(ctx, "chmod 600 "+keyPath); err != nil {
	log.Fatal(err)
}
if _, err := sandbox.Process.ExecuteCommand(ctx, "mkdir -p "+mountPath); err != nil {
	log.Fatal(err)
}
if _, err := sandbox.Process.ExecuteCommand(ctx,
	"gcsfuse --key-file="+keyPath+" my-gcs-bucket "+mountPath); err != nil {
	log.Fatal(err)
}

response, err := sandbox.Process.ExecuteCommand(ctx, "ls "+mountPath)
if err != nil {
	log.Fatal(err)
}
fmt.Println(response.Result)
```

</TabItem>
<TabItem label="Java" icon="seti:java">

```java
import io.daytona.sdk.Daytona;
import io.daytona.sdk.Sandbox;
import io.daytona.sdk.model.CreateSandboxFromSnapshotParams;
import io.daytona.sdk.model.ExecuteResponse;

import java.nio.charset.StandardCharsets;

public class App {
    public static void main(String[] args) {
        try (Daytona daytona = new Daytona()) {
            // GCS_SERVICE_ACCOUNT_KEY holds the full service account JSON as a string
            byte[] serviceAccountKey = System.getenv("GCS_SERVICE_ACCOUNT_KEY")
                .getBytes(StandardCharsets.UTF_8);

            Sandbox sandbox = daytona.create(new CreateSandboxFromSnapshotParams());

            // Install gcsfuse from the bookworm repo (works on Trixie)
            sandbox.getProcess().executeCommand(
                "sudo apt-get update "
                    + "&& sudo apt-get install -y --no-install-recommends ca-certificates curl gnupg");
            sandbox.getProcess().executeCommand(
                "sudo mkdir -p /etc/apt/keyrings "
                    + "&& curl -fsSL https://packages.cloud.google.com/apt/doc/apt-key.gpg "
                    + "| sudo gpg --dearmor -o /etc/apt/keyrings/gcsfuse.gpg");
            sandbox.getProcess().executeCommand(
                "echo \"deb [signed-by=/etc/apt/keyrings/gcsfuse.gpg] "
                    + "https://packages.cloud.google.com/apt gcsfuse-bookworm main\" "
                    + "| sudo tee /etc/apt/sources.list.d/gcsfuse.list "
                    + "&& sudo apt-get update && sudo apt-get install -y gcsfuse");

            // Upload the key and mount
            String mountPath = "/home/daytona/gcs";
            String keyPath = "/home/daytona/.gcs-key.json";
            sandbox.fs.uploadFile(serviceAccountKey, keyPath);
            sandbox.getProcess().executeCommand("chmod 600 " + keyPath);
            sandbox.getProcess().executeCommand("mkdir -p " + mountPath);
            sandbox.getProcess().executeCommand(
                "gcsfuse --key-file=" + keyPath + " my-gcs-bucket " + mountPath);

            ExecuteResponse response = sandbox.getProcess().executeCommand("ls " + mountPath);
            System.out.println(response.getResult());
        }
    }
}
```

</TabItem>
</Tabs>

## Mount an Azure Blob container [#mount-an-azure-blob-container]

Mount an Azure Blob container using [blobfuse2 ↗](https://github.com/Azure/azure-storage-fuse) — Microsoft's official FUSE client.

**Credentials** — set `AZURE_STORAGE_ACCOUNT`, `AZURE_STORAGE_CONTAINER`, and `AZURE_STORAGE_ACCOUNT_KEY` in your local environment. The snippets below pass them into the sandbox via `envVars`, and `blobfuse2` reads them from its YAML config.

<Callout type="caution">
Daytona's base image is Debian Trixie. Microsoft's `blobfuse2` Bookworm binary links against `libfuse3.so.3`, but Trixie's `fuse3` package bumped the SONAME to `.4`, so `libfuse3.so.3` is missing on disk. The snapshot recipe below creates a compatibility symlink. Without it, `blobfuse2` fails with `libfuse3.so.3: cannot open shared object file`.
</Callout>

### Pre-built snapshot [#pre-built-snapshot-5]

Build a snapshot with `blobfuse2` and required FUSE compatibility setup preinstalled, then launch all Azure-enabled sandboxes from that snapshot. This is the recommended path for stable environments because dependency and compatibility work runs once during snapshot creation.

#### Build a snapshot [#build-a-snapshot-5]

Create a reusable snapshot that installs `blobfuse2`, configures required FUSE dependencies, and applies the Trixie compatibility steps. This ensures Azure mounts work out of the box in sandboxes launched from `fuse-azure`.

<Tabs syncKey="language">
<TabItem label="Python" icon="seti:python">

```python
from daytona import CreateSnapshotParams, Daytona, Image

daytona = Daytona()

image = (
  Image.base("daytonaio/sandbox")
  .run_commands(
    "sudo apt-get update "
    "&& sudo apt-get install -y --no-install-recommends ca-certificates curl gnupg wget",
    # Microsoft's apt repo (use bookworm packages on Trixie)
    "wget -qO- https://packages.microsoft.com/keys/microsoft.asc "
    "| sudo gpg --dearmor -o /etc/apt/trusted.gpg.d/microsoft.gpg",
    'echo "deb [arch=$(dpkg --print-architecture) '
    'signed-by=/etc/apt/trusted.gpg.d/microsoft.gpg] '
    'https://packages.microsoft.com/debian/12/prod bookworm main" '
    "| sudo tee /etc/apt/sources.list.d/microsoft-prod.list",
    "sudo apt-get update && sudo apt-get install -y blobfuse2 fuse3",
    # libfuse3.so.3 compat symlink for Trixie (see :::caution above)
    'src=$(find /usr/lib /lib -name "libfuse3.so.3.*" -type f 2>/dev/null '
    "| sort -V | tail -1) "
    '&& sudo ln -sfn "$src" "$(dirname "$src")/libfuse3.so.3" '
    "&& sudo ldconfig",
    "sudo touch /etc/fuse.conf "
    '&& grep -qxF "user_allow_other" /etc/fuse.conf '
    '|| echo "user_allow_other" | sudo tee -a /etc/fuse.conf',
  )
)

daytona.snapshot.create(
    CreateSnapshotParams(name="fuse-azure", image=image),
    on_logs=print,
)
```

</TabItem>
<TabItem label="TypeScript" icon="seti:typescript">

```typescript
import { Daytona, Image } from '@daytona/sdk'

const daytona = new Daytona()

const image = Image.base('daytonaio/sandbox').runCommands(
  'sudo apt-get update ' +
    '&& sudo apt-get install -y --no-install-recommends ca-certificates curl gnupg wget',
  // Microsoft's apt repo (use bookworm packages on Trixie)
  'wget -qO- https://packages.microsoft.com/keys/microsoft.asc ' +
    '| sudo gpg --dearmor -o /etc/apt/trusted.gpg.d/microsoft.gpg',
  'echo "deb [arch=$(dpkg --print-architecture) ' +
    'signed-by=/etc/apt/trusted.gpg.d/microsoft.gpg] ' +
    'https://packages.microsoft.com/debian/12/prod bookworm main" ' +
    '| sudo tee /etc/apt/sources.list.d/microsoft-prod.list',
  'sudo apt-get update && sudo apt-get install -y blobfuse2 fuse3',
  // libfuse3.so.3 compat symlink for Trixie (see :::caution above)
  'src=$(find /usr/lib /lib -name "libfuse3.so.3.*" -type f 2>/dev/null ' +
    '| sort -V | tail -1) ' +
    '&& sudo ln -sfn "$src" "$(dirname "$src")/libfuse3.so.3" ' +
    '&& sudo ldconfig',
  'sudo touch /etc/fuse.conf ' +
    '&& grep -qxF "user_allow_other" /etc/fuse.conf ' +
    '|| echo "user_allow_other" | sudo tee -a /etc/fuse.conf',
)

await daytona.snapshot.create(
  { name: 'fuse-azure', image },
  { onLogs: console.log },
)
```

</TabItem>
<TabItem label="Ruby" icon="seti:ruby">

```ruby
require 'daytona'

daytona = Daytona::Daytona.new

image = Daytona::Image
  .base('daytonaio/sandbox')
  .run_commands(
    'sudo apt-get update ' \
    '&& sudo apt-get install -y --no-install-recommends ca-certificates curl gnupg wget',
    # Microsoft's apt repo (use bookworm packages on Trixie)
    'wget -qO- https://packages.microsoft.com/keys/microsoft.asc ' \
    '| sudo gpg --dearmor -o /etc/apt/trusted.gpg.d/microsoft.gpg',
    'echo "deb [arch=$(dpkg --print-architecture) ' \
    'signed-by=/etc/apt/trusted.gpg.d/microsoft.gpg] ' \
    'https://packages.microsoft.com/debian/12/prod bookworm main" ' \
    '| sudo tee /etc/apt/sources.list.d/microsoft-prod.list',
    'sudo apt-get update && sudo apt-get install -y blobfuse2 fuse3',
    # libfuse3.so.3 compat symlink for Trixie
    'src=$(find /usr/lib /lib -name "libfuse3.so.3.*" -type f 2>/dev/null ' \
    '| sort -V | tail -1) ' \
    '&& sudo ln -sfn "$src" "$(dirname "$src")/libfuse3.so.3" ' \
    '&& sudo ldconfig',
    'sudo touch /etc/fuse.conf ' \
    '&& grep -qxF "user_allow_other" /etc/fuse.conf ' \
    '|| echo "user_allow_other" | sudo tee -a /etc/fuse.conf'
  )

daytona.snapshot.create(
  Daytona::CreateSnapshotParams.new(name: 'fuse-azure', image: image),
  on_logs: proc { |chunk| print(chunk) }
)
```

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

```go
import (
	"context"
	"fmt"
	"log"

	"github.com/daytona/clients/sdk-go/pkg/daytona"
	"github.com/daytona/clients/sdk-go/pkg/types"
)

ctx := context.Background()
client, err := daytona.NewClient()
if err != nil {
	log.Fatal(err)
}

image := daytona.Base("daytonaio/sandbox").
	Run("sudo apt-get update && sudo apt-get install -y --no-install-recommends ca-certificates curl gnupg wget").
	// Microsoft's apt repo (use bookworm packages on Trixie)
	Run("wget -qO- https://packages.microsoft.com/keys/microsoft.asc | " +
		"sudo gpg --dearmor -o /etc/apt/trusted.gpg.d/microsoft.gpg").
	Run(`echo "deb [arch=$(dpkg --print-architecture) ` +
		`signed-by=/etc/apt/trusted.gpg.d/microsoft.gpg] ` +
		`https://packages.microsoft.com/debian/12/prod bookworm main" | ` +
		`sudo tee /etc/apt/sources.list.d/microsoft-prod.list`).
	Run("sudo apt-get update && sudo apt-get install -y blobfuse2 fuse3").
	// libfuse3.so.3 compat symlink for Trixie
	Run(`src=$(find /usr/lib /lib -name "libfuse3.so.3.*" -type f 2>/dev/null | sort -V | tail -1) && ` +
		`sudo ln -sfn "$src" "$(dirname "$src")/libfuse3.so.3" && sudo ldconfig`).
	Run(`sudo touch /etc/fuse.conf && grep -qxF "user_allow_other" /etc/fuse.conf || ` +
		`echo "user_allow_other" | sudo tee -a /etc/fuse.conf`)

_, logChan, err := client.Snapshot.Create(ctx, &types.CreateSnapshotParams{
	Name:  "fuse-azure",
	Image: image,
})
if err != nil {
	log.Fatal(err)
}
for line := range logChan {
	fmt.Print(line)
}
```

</TabItem>
<TabItem label="Java" icon="seti:java">

```java
import io.daytona.sdk.Daytona;
import io.daytona.sdk.Image;

public class App {
    public static void main(String[] args) {
        try (Daytona daytona = new Daytona()) {
            Image image = Image.base("daytonaio/sandbox")
                .runCommands(
                    "sudo apt-get update "
                        + "&& sudo apt-get install -y --no-install-recommends ca-certificates curl gnupg wget",
                    // Microsoft's apt repo (use bookworm packages on Trixie)
                    "wget -qO- https://packages.microsoft.com/keys/microsoft.asc "
                        + "| sudo gpg --dearmor -o /etc/apt/trusted.gpg.d/microsoft.gpg",
                    "echo \"deb [arch=$(dpkg --print-architecture) "
                        + "signed-by=/etc/apt/trusted.gpg.d/microsoft.gpg] "
                        + "https://packages.microsoft.com/debian/12/prod bookworm main\" "
                        + "| sudo tee /etc/apt/sources.list.d/microsoft-prod.list",
                    "sudo apt-get update && sudo apt-get install -y blobfuse2 fuse3",
                    // libfuse3.so.3 compat symlink for Trixie
                    "src=$(find /usr/lib /lib -name \"libfuse3.so.3.*\" -type f 2>/dev/null "
                        + "| sort -V | tail -1) "
                        + "&& sudo ln -sfn \"$src\" \"$(dirname \"$src\")/libfuse3.so.3\" "
                        + "&& sudo ldconfig",
                    "sudo touch /etc/fuse.conf "
                        + "&& grep -qxF \"user_allow_other\" /etc/fuse.conf "
                        + "|| echo \"user_allow_other\" | sudo tee -a /etc/fuse.conf"
                );

            daytona.snapshot().create("fuse-azure", image, System.out::println);
        }
    }
}
```

</TabItem>
</Tabs>

#### Launch and mount [#launch-and-mount-5]

`blobfuse2` reads its configuration from a YAML file. Build it with your account credentials and upload it into the sandbox.

The YAML below tells `blobfuse2` three things: **where to connect** (the `azstorage:` block — your storage account, the container you want to mount, the endpoint URL, and the auth method), **what to enable** (the `components:` list — the FUSE interface itself, a content cache, a metadata cache, and the Azure backend), and **how to log**. The cache components use sensible defaults when listed without their own top-level config blocks; add explicit `block_cache:` / `attr_cache:` blocks later if you need to tune cache sizes or timeouts. Note that in Azure terminology, a "container" is the equivalent of an S3 bucket — it's specified inside the YAML rather than passed as a command-line argument.

<Tabs syncKey="language">
<TabItem label="Python" icon="seti:python">

```python
import os
from daytona import CreateSandboxFromSnapshotParams, Daytona

daytona = Daytona()

sandbox = daytona.create(CreateSandboxFromSnapshotParams(snapshot="fuse-azure"))

mount_path = "/home/daytona/azure"
config_path = "/home/daytona/.blobfuse2.yaml"

account = os.environ["AZURE_STORAGE_ACCOUNT"]
container = os.environ["AZURE_STORAGE_CONTAINER"]
account_key = os.environ["AZURE_STORAGE_ACCOUNT_KEY"]

config = f"""\
allow-other: true
logging:
  type: syslog
  level: log_warning
components:
  - libfuse
  - block_cache
  - attr_cache
  - azstorage
azstorage:
  type: block
  account-name: {account}
  container: {container}
  endpoint: https://{account}.blob.core.windows.net
  auth-type: key
  account-key: {account_key}
"""

sandbox.fs.upload_file(config.encode(), config_path)
sandbox.process.exec(f"chmod 600 {config_path}")

# Mount the container
sandbox.process.exec(f"mkdir -p {mount_path}")
sandbox.process.exec(f"blobfuse2 mount --config-file={config_path} {mount_path}")

# Use the mount
response = sandbox.process.exec(f"ls {mount_path}")
print(response.result)
```

</TabItem>
<TabItem label="TypeScript" icon="seti:typescript">

```typescript
import { Daytona } from '@daytona/sdk'

const daytona = new Daytona()

const sandbox = await daytona.create({ snapshot: 'fuse-azure' })

const mountPath = '/home/daytona/azure'
const configPath = '/home/daytona/.blobfuse2.yaml'

const account = process.env.AZURE_STORAGE_ACCOUNT!
const container = process.env.AZURE_STORAGE_CONTAINER!
const accountKey = process.env.AZURE_STORAGE_ACCOUNT_KEY!

const config = `allow-other: true
logging:
  type: syslog
  level: log_warning
components:
  - libfuse
  - block_cache
  - attr_cache
  - azstorage
azstorage:
  type: block
  account-name: ${account}
  container: ${container}
  endpoint: https://${account}.blob.core.windows.net
  auth-type: key
  account-key: ${accountKey}
`

await sandbox.fs.uploadFile(Buffer.from(config), configPath)
await sandbox.process.executeCommand(`chmod 600 ${configPath}`)

// Mount the container
await sandbox.process.executeCommand(`mkdir -p ${mountPath}`)
await sandbox.process.executeCommand(`blobfuse2 mount --config-file=${configPath} ${mountPath}`)

// Use the mount
const response = await sandbox.process.executeCommand(`ls ${mountPath}`)
console.log(response.result)
```

</TabItem>
<TabItem label="Ruby" icon="seti:ruby">

```ruby
require 'daytona'

daytona = Daytona::Daytona.new

sandbox = daytona.create(
  Daytona::CreateSandboxFromSnapshotParams.new(snapshot: 'fuse-azure')
)

mount_path = '/home/daytona/azure'
config_path = '/home/daytona/.blobfuse2.yaml'

account = ENV.fetch('AZURE_STORAGE_ACCOUNT')
container = ENV.fetch('AZURE_STORAGE_CONTAINER')
account_key = ENV.fetch('AZURE_STORAGE_ACCOUNT_KEY')

config = <<~YAML
  allow-other: true
  logging:
    type: syslog
    level: log_warning
  components:
    - libfuse
    - block_cache
    - attr_cache
    - azstorage
  azstorage:
    type: block
    account-name: #{account}
    container: #{container}
    endpoint: https://#{account}.blob.core.windows.net
    auth-type: key
    account-key: #{account_key}
YAML

sandbox.fs.upload_file(config, config_path)
sandbox.process.exec(command: "chmod 600 #{config_path}")

# Mount the container
sandbox.process.exec(command: "mkdir -p #{mount_path}")
sandbox.process.exec(command: "blobfuse2 mount --config-file=#{config_path} #{mount_path}")

# Use the mount
response = sandbox.process.exec(command: "ls #{mount_path}")
puts response.result
```

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

```go
import (
	"context"
	"fmt"
	"log"
	"os"

	"github.com/daytona/clients/sdk-go/pkg/daytona"
	"github.com/daytona/clients/sdk-go/pkg/types"
)

ctx := context.Background()
client, err := daytona.NewClient()
if err != nil {
	log.Fatal(err)
}

sandbox, err := client.Create(ctx, types.SnapshotParams{
	Snapshot: "fuse-azure",
})
if err != nil {
	log.Fatal(err)
}

mountPath := "/home/daytona/azure"
configPath := "/home/daytona/.blobfuse2.yaml"

account := os.Getenv("AZURE_STORAGE_ACCOUNT")
container := os.Getenv("AZURE_STORAGE_CONTAINER")
accountKey := os.Getenv("AZURE_STORAGE_ACCOUNT_KEY")

config := fmt.Sprintf(`allow-other: true
logging:
  type: syslog
  level: log_warning
components:
  - libfuse
  - block_cache
  - attr_cache
  - azstorage
azstorage:
  type: block
  account-name: %s
  container: %s
  endpoint: https://%s.blob.core.windows.net
  auth-type: key
  account-key: %s
`, account, container, account, accountKey)

if err := sandbox.FileSystem.UploadFile(ctx, []byte(config), configPath); err != nil {
	log.Fatal(err)
}
if _, err := sandbox.Process.ExecuteCommand(ctx, "chmod 600 "+configPath); err != nil {
	log.Fatal(err)
}

// Mount the container
if _, err := sandbox.Process.ExecuteCommand(ctx, "mkdir -p "+mountPath); err != nil {
	log.Fatal(err)
}
if _, err := sandbox.Process.ExecuteCommand(ctx,
	"blobfuse2 mount --config-file="+configPath+" "+mountPath); err != nil {
	log.Fatal(err)
}

// Use the mount
response, err := sandbox.Process.ExecuteCommand(ctx, "ls "+mountPath)
if err != nil {
	log.Fatal(err)
}
fmt.Println(response.Result)
```

</TabItem>
<TabItem label="Java" icon="seti:java">

```java
import io.daytona.sdk.Daytona;
import io.daytona.sdk.Sandbox;
import io.daytona.sdk.model.CreateSandboxFromSnapshotParams;
import io.daytona.sdk.model.ExecuteResponse;

import java.nio.charset.StandardCharsets;

public class App {
    public static void main(String[] args) {
        try (Daytona daytona = new Daytona()) {
            CreateSandboxFromSnapshotParams params = new CreateSandboxFromSnapshotParams();
            params.setSnapshot("fuse-azure");
            Sandbox sandbox = daytona.create(params);

            String mountPath = "/home/daytona/azure";
            String configPath = "/home/daytona/.blobfuse2.yaml";

            String account = System.getenv("AZURE_STORAGE_ACCOUNT");
            String container = System.getenv("AZURE_STORAGE_CONTAINER");
            String accountKey = System.getenv("AZURE_STORAGE_ACCOUNT_KEY");

            String config = "allow-other: true\n"
                + "logging:\n"
                + "  type: syslog\n"
                + "  level: log_warning\n"
                + "components:\n"
                + "  - libfuse\n"
                + "  - block_cache\n"
                + "  - attr_cache\n"
                + "  - azstorage\n"
                + "azstorage:\n"
                + "  type: block\n"
                + "  account-name: " + account + "\n"
                + "  container: " + container + "\n"
                + "  endpoint: https://" + account + ".blob.core.windows.net\n"
                + "  auth-type: key\n"
                + "  account-key: " + accountKey + "\n";

            sandbox.fs.uploadFile(config.getBytes(StandardCharsets.UTF_8), configPath);
            sandbox.getProcess().executeCommand("chmod 600 " + configPath);

            // Mount the container
            sandbox.getProcess().executeCommand("mkdir -p " + mountPath);
            sandbox.getProcess().executeCommand(
                "blobfuse2 mount --config-file=" + configPath + " " + mountPath);

            // Use the mount
            ExecuteResponse response = sandbox.getProcess().executeCommand("ls " + mountPath);
            System.out.println(response.getResult());
        }
    }
}
```

</TabItem>
</Tabs>

### Runtime install [#runtime-install-5]

Start from a default sandbox and install `blobfuse2` during startup before writing the config and mounting the container. This is useful for quick validation and experiments, with the tradeoff of slower cold starts and repeated setup on each sandbox launch.

<Tabs syncKey="language">
<TabItem label="Python" icon="seti:python">

```python
import os
from daytona import CreateSandboxBaseParams, Daytona

daytona = Daytona()

sandbox = daytona.create(CreateSandboxBaseParams())

# Install blobfuse2
sandbox.process.exec(
    "sudo apt-get update "
    "&& sudo apt-get install -y --no-install-recommends ca-certificates curl gnupg wget"
)
sandbox.process.exec(
    "wget -qO- https://packages.microsoft.com/keys/microsoft.asc "
    "| sudo gpg --dearmor -o /etc/apt/trusted.gpg.d/microsoft.gpg"
)
sandbox.process.exec(
    'echo "deb [arch=$(dpkg --print-architecture) '
    'signed-by=/etc/apt/trusted.gpg.d/microsoft.gpg] '
    'https://packages.microsoft.com/debian/12/prod bookworm main" '
    "| sudo tee /etc/apt/sources.list.d/microsoft-prod.list "
    "&& sudo apt-get update && sudo apt-get install -y blobfuse2 fuse3"
)
# libfuse3.so.3 compat symlink for Trixie
sandbox.process.exec(
    'src=$(find /usr/lib /lib -name "libfuse3.so.3.*" -type f 2>/dev/null '
    "| sort -V | tail -1) "
    '&& sudo ln -sfn "$src" "$(dirname "$src")/libfuse3.so.3" '
    "&& sudo ldconfig"
)

# Build config and mount
mount_path = "/home/daytona/azure"
config_path = "/home/daytona/.blobfuse2.yaml"
account = os.environ["AZURE_STORAGE_ACCOUNT"]
container = os.environ["AZURE_STORAGE_CONTAINER"]
account_key = os.environ["AZURE_STORAGE_ACCOUNT_KEY"]

config = f"""\
allow-other: true
components:
  - libfuse
  - block_cache
  - attr_cache
  - azstorage
azstorage:
  type: block
  account-name: {account}
  container: {container}
  endpoint: https://{account}.blob.core.windows.net
  auth-type: key
  account-key: {account_key}
"""

sandbox.fs.upload_file(config.encode(), config_path)
sandbox.process.exec(f"chmod 600 {config_path}")
sandbox.process.exec(f"mkdir -p {mount_path}")
sandbox.process.exec(f"blobfuse2 mount --config-file={config_path} {mount_path}")

response = sandbox.process.exec(f"ls {mount_path}")
print(response.result)
```

</TabItem>
<TabItem label="TypeScript" icon="seti:typescript">

```typescript
import { Daytona } from '@daytona/sdk'

const daytona = new Daytona()

const sandbox = await daytona.create()

// Install blobfuse2
await sandbox.process.executeCommand(
  'sudo apt-get update ' +
    '&& sudo apt-get install -y --no-install-recommends ca-certificates curl gnupg wget',
)
await sandbox.process.executeCommand(
  'wget -qO- https://packages.microsoft.com/keys/microsoft.asc ' +
    '| sudo gpg --dearmor -o /etc/apt/trusted.gpg.d/microsoft.gpg',
)
await sandbox.process.executeCommand(
  'echo "deb [arch=$(dpkg --print-architecture) ' +
    'signed-by=/etc/apt/trusted.gpg.d/microsoft.gpg] ' +
    'https://packages.microsoft.com/debian/12/prod bookworm main" ' +
    '| sudo tee /etc/apt/sources.list.d/microsoft-prod.list ' +
    '&& sudo apt-get update && sudo apt-get install -y blobfuse2 fuse3',
)
// libfuse3.so.3 compat symlink for Trixie
await sandbox.process.executeCommand(
  'src=$(find /usr/lib /lib -name "libfuse3.so.3.*" -type f 2>/dev/null ' +
    '| sort -V | tail -1) ' +
    '&& sudo ln -sfn "$src" "$(dirname "$src")/libfuse3.so.3" ' +
    '&& sudo ldconfig',
)

// Build config and mount
const mountPath = '/home/daytona/azure'
const configPath = '/home/daytona/.blobfuse2.yaml'
const account = process.env.AZURE_STORAGE_ACCOUNT!
const container = process.env.AZURE_STORAGE_CONTAINER!
const accountKey = process.env.AZURE_STORAGE_ACCOUNT_KEY!

const config = `allow-other: true
components:
  - libfuse
  - block_cache
  - attr_cache
  - azstorage
azstorage:
  type: block
  account-name: ${account}
  container: ${container}
  endpoint: https://${account}.blob.core.windows.net
  auth-type: key
  account-key: ${accountKey}
`

await sandbox.fs.uploadFile(Buffer.from(config), configPath)
await sandbox.process.executeCommand(`chmod 600 ${configPath}`)
await sandbox.process.executeCommand(`mkdir -p ${mountPath}`)
await sandbox.process.executeCommand(`blobfuse2 mount --config-file=${configPath} ${mountPath}`)

const response = await sandbox.process.executeCommand(`ls ${mountPath}`)
console.log(response.result)
```

</TabItem>
<TabItem label="Ruby" icon="seti:ruby">

```ruby
require 'daytona'

daytona = Daytona::Daytona.new

sandbox = daytona.create(Daytona::CreateSandboxBaseParams.new)

# Install blobfuse2
sandbox.process.exec(
  command: 'sudo apt-get update ' \
           '&& sudo apt-get install -y --no-install-recommends ca-certificates curl gnupg wget'
)
sandbox.process.exec(
  command: 'wget -qO- https://packages.microsoft.com/keys/microsoft.asc ' \
           '| sudo gpg --dearmor -o /etc/apt/trusted.gpg.d/microsoft.gpg'
)
sandbox.process.exec(
  command: 'echo "deb [arch=$(dpkg --print-architecture) ' \
           'signed-by=/etc/apt/trusted.gpg.d/microsoft.gpg] ' \
           'https://packages.microsoft.com/debian/12/prod bookworm main" ' \
           '| sudo tee /etc/apt/sources.list.d/microsoft-prod.list ' \
           '&& sudo apt-get update && sudo apt-get install -y blobfuse2 fuse3'
)
# libfuse3.so.3 compat symlink for Trixie
sandbox.process.exec(
  command: 'src=$(find /usr/lib /lib -name "libfuse3.so.3.*" -type f 2>/dev/null ' \
           '| sort -V | tail -1) ' \
           '&& sudo ln -sfn "$src" "$(dirname "$src")/libfuse3.so.3" ' \
           '&& sudo ldconfig'
)

# Build config and mount
mount_path = '/home/daytona/azure'
config_path = '/home/daytona/.blobfuse2.yaml'
account = ENV.fetch('AZURE_STORAGE_ACCOUNT')
container = ENV.fetch('AZURE_STORAGE_CONTAINER')
account_key = ENV.fetch('AZURE_STORAGE_ACCOUNT_KEY')

config = <<~YAML
  allow-other: true
  components:
    - libfuse
    - block_cache
    - attr_cache
    - azstorage
  azstorage:
    type: block
    account-name: #{account}
    container: #{container}
    endpoint: https://#{account}.blob.core.windows.net
    auth-type: key
    account-key: #{account_key}
YAML

sandbox.fs.upload_file(config, config_path)
sandbox.process.exec(command: "chmod 600 #{config_path}")
sandbox.process.exec(command: "mkdir -p #{mount_path}")
sandbox.process.exec(command: "blobfuse2 mount --config-file=#{config_path} #{mount_path}")

response = sandbox.process.exec(command: "ls #{mount_path}")
puts response.result
```

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

```go
import (
	"context"
	"fmt"
	"log"
	"os"

	"github.com/daytona/clients/sdk-go/pkg/daytona"
	"github.com/daytona/clients/sdk-go/pkg/types"
)

ctx := context.Background()
client, err := daytona.NewClient()
if err != nil {
	log.Fatal(err)
}

sandbox, err := client.Create(ctx, types.SnapshotParams{})
if err != nil {
	log.Fatal(err)
}

// Install blobfuse2
if _, err := sandbox.Process.ExecuteCommand(ctx,
	"sudo apt-get update && sudo apt-get install -y --no-install-recommends ca-certificates curl gnupg wget"); err != nil {
	log.Fatal(err)
}
if _, err := sandbox.Process.ExecuteCommand(ctx,
	"wget -qO- https://packages.microsoft.com/keys/microsoft.asc | "+
		"sudo gpg --dearmor -o /etc/apt/trusted.gpg.d/microsoft.gpg"); err != nil {
	log.Fatal(err)
}
if _, err := sandbox.Process.ExecuteCommand(ctx,
	`echo "deb [arch=$(dpkg --print-architecture) `+
		`signed-by=/etc/apt/trusted.gpg.d/microsoft.gpg] `+
		`https://packages.microsoft.com/debian/12/prod bookworm main" | `+
		`sudo tee /etc/apt/sources.list.d/microsoft-prod.list && `+
		`sudo apt-get update && sudo apt-get install -y blobfuse2 fuse3`); err != nil {
	log.Fatal(err)
}
// libfuse3.so.3 compat symlink for Trixie
if _, err := sandbox.Process.ExecuteCommand(ctx,
	`src=$(find /usr/lib /lib -name "libfuse3.so.3.*" -type f 2>/dev/null | sort -V | tail -1) && `+
		`sudo ln -sfn "$src" "$(dirname "$src")/libfuse3.so.3" && sudo ldconfig`); err != nil {
	log.Fatal(err)
}

// Build config and mount
mountPath := "/home/daytona/azure"
configPath := "/home/daytona/.blobfuse2.yaml"
account := os.Getenv("AZURE_STORAGE_ACCOUNT")
container := os.Getenv("AZURE_STORAGE_CONTAINER")
accountKey := os.Getenv("AZURE_STORAGE_ACCOUNT_KEY")

config := fmt.Sprintf(`allow-other: true
components:
  - libfuse
  - block_cache
  - attr_cache
  - azstorage
azstorage:
  type: block
  account-name: %s
  container: %s
  endpoint: https://%s.blob.core.windows.net
  auth-type: key
  account-key: %s
`, account, container, account, accountKey)

if err := sandbox.FileSystem.UploadFile(ctx, []byte(config), configPath); err != nil {
	log.Fatal(err)
}
if _, err := sandbox.Process.ExecuteCommand(ctx, "chmod 600 "+configPath); err != nil {
	log.Fatal(err)
}
if _, err := sandbox.Process.ExecuteCommand(ctx, "mkdir -p "+mountPath); err != nil {
	log.Fatal(err)
}
if _, err := sandbox.Process.ExecuteCommand(ctx,
	"blobfuse2 mount --config-file="+configPath+" "+mountPath); err != nil {
	log.Fatal(err)
}

response, err := sandbox.Process.ExecuteCommand(ctx, "ls "+mountPath)
if err != nil {
	log.Fatal(err)
}
fmt.Println(response.Result)
```

</TabItem>
<TabItem label="Java" icon="seti:java">

```java
import io.daytona.sdk.Daytona;
import io.daytona.sdk.Sandbox;
import io.daytona.sdk.model.CreateSandboxFromSnapshotParams;
import io.daytona.sdk.model.ExecuteResponse;

import java.nio.charset.StandardCharsets;

public class App {
    public static void main(String[] args) {
        try (Daytona daytona = new Daytona()) {
            Sandbox sandbox = daytona.create(new CreateSandboxFromSnapshotParams());

            // Install blobfuse2
            sandbox.getProcess().executeCommand(
                "sudo apt-get update "
                    + "&& sudo apt-get install -y --no-install-recommends ca-certificates curl gnupg wget");
            sandbox.getProcess().executeCommand(
                "wget -qO- https://packages.microsoft.com/keys/microsoft.asc "
                    + "| sudo gpg --dearmor -o /etc/apt/trusted.gpg.d/microsoft.gpg");
            sandbox.getProcess().executeCommand(
                "echo \"deb [arch=$(dpkg --print-architecture) "
                    + "signed-by=/etc/apt/trusted.gpg.d/microsoft.gpg] "
                    + "https://packages.microsoft.com/debian/12/prod bookworm main\" "
                    + "| sudo tee /etc/apt/sources.list.d/microsoft-prod.list "
                    + "&& sudo apt-get update && sudo apt-get install -y blobfuse2 fuse3");
            // libfuse3.so.3 compat symlink for Trixie
            sandbox.getProcess().executeCommand(
                "src=$(find /usr/lib /lib -name \"libfuse3.so.3.*\" -type f 2>/dev/null "
                    + "| sort -V | tail -1) "
                    + "&& sudo ln -sfn \"$src\" \"$(dirname \"$src\")/libfuse3.so.3\" "
                    + "&& sudo ldconfig");

            // Build config and mount
            String mountPath = "/home/daytona/azure";
            String configPath = "/home/daytona/.blobfuse2.yaml";
            String account = System.getenv("AZURE_STORAGE_ACCOUNT");
            String container = System.getenv("AZURE_STORAGE_CONTAINER");
            String accountKey = System.getenv("AZURE_STORAGE_ACCOUNT_KEY");

            String config = "allow-other: true\n"
                + "components:\n"
                + "  - libfuse\n"
                + "  - block_cache\n"
                + "  - attr_cache\n"
                + "  - azstorage\n"
                + "azstorage:\n"
                + "  type: block\n"
                + "  account-name: " + account + "\n"
                + "  container: " + container + "\n"
                + "  endpoint: https://" + account + ".blob.core.windows.net\n"
                + "  auth-type: key\n"
                + "  account-key: " + accountKey + "\n";

            sandbox.fs.uploadFile(config.getBytes(StandardCharsets.UTF_8), configPath);
            sandbox.getProcess().executeCommand("chmod 600 " + configPath);
            sandbox.getProcess().executeCommand("mkdir -p " + mountPath);
            sandbox.getProcess().executeCommand(
                "blobfuse2 mount --config-file=" + configPath + " " + mountPath);

            ExecuteResponse response = sandbox.getProcess().executeCommand("ls " + mountPath);
            System.out.println(response.getResult());
        }
    }
}
```

</TabItem>
</Tabs>

## Mount a Box folder [#mount-a-box-folder]

Mount a Box folder using [Box Mount ↗](https://developer.box.com/guides/box-mount) — Box's official client for agent runtimes. Box Mount runs a background daemon that maps a Box folder to a local directory and keeps it in continuous two-way sync: writes in the sandbox upload to Box, changes made in Box appear in the sandbox, and simultaneous edits are preserved as conflict copies instead of overwritten. Every operation flows through the Box API as the authenticated identity, so Box permissions, retention, and audit trails apply to everything the sandbox does.

<Callout type="note">
Box Mount is in private preview: Box provides the binary and setup guidance to preview participants — request access via the [Box Mount preview form ↗](https://bit.ly/box-mount-preview). Preview workloads are limited to 5 GB and 10,000 files per mount, and applications that save files by replacing them (write-temp-then-rename) create new files instead of new versions. See [known limitations ↗](https://developer.box.com/guides/box-mount#known-limitations).
</Callout>

Box Mount supports two authentication methods: **JWT server authentication** for enterprise-owned Platform Apps — fully non-interactive, the recommended mode for sandboxes — and **OAuth**, which acts as the user who authorized the app. Credentials are supplied through environment variables or a config file; see [configure credentials ↗](https://developer.box.com/guides/box-mount#configure-credentials).

### Install Box Mount [#install-box-mount]

Box Mount ships as a single Linux binary (x86_64 and ARM64). Bake it into a snapshot from your local copy, or upload it into a running sandbox — both end with `box-mount` on the sandbox `PATH`.

#### Pre-built snapshot [#pre-built-snapshot-6]

Create a reusable snapshot that copies the binary from your local machine into the image — the snippets assume it sits at `./box-mount`; adjust the local path to match the file Box provided. The binary is installed as `agent-mount` with a `box-mount` symlink because current preview binaries resolve their sync engine by the `agent-mount` name. Sandboxes launched from `box-mount` need no further installation. The Java SDK supports `addLocalFile` from `io.daytona:sdk` version `0.214.0`.

<Tabs syncKey="language">
<TabItem label="Python" icon="seti:python">

```python
from daytona import CreateSnapshotParams, Daytona, Image

daytona = Daytona()

image = (
  Image.base("daytonaio/sandbox")
  .add_local_file("./box-mount", "/usr/local/bin/agent-mount")
  .run_commands(
    "sudo chmod +x /usr/local/bin/agent-mount",
    "sudo ln -s agent-mount /usr/local/bin/box-mount",
  )
)

daytona.snapshot.create(
    CreateSnapshotParams(name="box-mount", image=image),
    on_logs=print,
)
```

</TabItem>
<TabItem label="TypeScript" icon="seti:typescript">

```typescript
import { Daytona, Image } from '@daytona/sdk'

const daytona = new Daytona()

const image = Image.base('daytonaio/sandbox')
  .addLocalFile('./box-mount', '/usr/local/bin/agent-mount')
  .runCommands(
    'sudo chmod +x /usr/local/bin/agent-mount',
    'sudo ln -s agent-mount /usr/local/bin/box-mount',
  )

await daytona.snapshot.create(
  { name: 'box-mount', image },
  { onLogs: console.log },
)
```

</TabItem>
<TabItem label="Ruby" icon="seti:ruby">

```ruby
require 'daytona'

daytona = Daytona::Daytona.new

image = Daytona::Image
  .base('daytonaio/sandbox')
  .add_local_file('./box-mount', '/usr/local/bin/agent-mount')
  .run_commands(
    'sudo chmod +x /usr/local/bin/agent-mount',
    'sudo ln -s agent-mount /usr/local/bin/box-mount'
  )

daytona.snapshot.create(
  Daytona::CreateSnapshotParams.new(name: 'box-mount', image: image),
  on_logs: proc { |chunk| print(chunk) }
)
```

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

```go
import (
	"context"
	"fmt"
	"log"

	"github.com/daytona/clients/sdk-go/pkg/daytona"
	"github.com/daytona/clients/sdk-go/pkg/types"
)

ctx := context.Background()
client, err := daytona.NewClient()
if err != nil {
	log.Fatal(err)
}

image := daytona.Base("daytonaio/sandbox").
	AddLocalFile("./box-mount", "/usr/local/bin/agent-mount").
	Run("sudo chmod +x /usr/local/bin/agent-mount").
	Run("sudo ln -s agent-mount /usr/local/bin/box-mount")

_, logChan, err := client.Snapshot.Create(ctx, &types.CreateSnapshotParams{
	Name:  "box-mount",
	Image: image,
})
if err != nil {
	log.Fatal(err)
}
for line := range logChan {
	fmt.Print(line)
}
```

</TabItem>
<TabItem label="Java" icon="seti:java">

```java
import io.daytona.sdk.Daytona;
import io.daytona.sdk.Image;

public class App {
    public static void main(String[] args) {
        try (Daytona daytona = new Daytona()) {
            Image image = Image.base("daytonaio/sandbox")
                .addLocalFile("./box-mount", "/usr/local/bin/agent-mount")
                .runCommands(
                    "sudo chmod +x /usr/local/bin/agent-mount",
                    "sudo ln -s agent-mount /usr/local/bin/box-mount"
                );

            daytona.snapshot().create("box-mount", image, System.out::println);
        }
    }
}
```

</TabItem>
</Tabs>

#### Runtime install [#runtime-install-6]

To skip snapshot management, upload the binary into a running sandbox instead — slower per sandbox, but with no snapshot to maintain:

<Tabs syncKey="language">
<TabItem label="Python" icon="seti:python">

```python
from daytona import CreateSandboxBaseParams, Daytona

daytona = Daytona()

sandbox = daytona.create(CreateSandboxBaseParams())

with open("./box-mount", "rb") as f:
    sandbox.fs.upload_file(f.read(), "/tmp/box-mount")
sandbox.process.exec(
    "sudo mv /tmp/box-mount /usr/local/bin/agent-mount "
    "&& sudo chmod +x /usr/local/bin/agent-mount "
    "&& sudo ln -s agent-mount /usr/local/bin/box-mount"
)

response = sandbox.process.exec("box-mount --version")
print(response.result)
```

</TabItem>
<TabItem label="TypeScript" icon="seti:typescript">

```typescript
import { readFileSync } from 'node:fs'
import { Daytona } from '@daytona/sdk'

const daytona = new Daytona()

const sandbox = await daytona.create()

await sandbox.fs.uploadFile(readFileSync('./box-mount'), '/tmp/box-mount')
await sandbox.process.executeCommand(
  'sudo mv /tmp/box-mount /usr/local/bin/agent-mount ' +
    '&& sudo chmod +x /usr/local/bin/agent-mount ' +
    '&& sudo ln -s agent-mount /usr/local/bin/box-mount',
)

const response = await sandbox.process.executeCommand('box-mount --version')
console.log(response.result)
```

</TabItem>
<TabItem label="Ruby" icon="seti:ruby">

```ruby
require 'daytona'

daytona = Daytona::Daytona.new

sandbox = daytona.create(Daytona::CreateSandboxBaseParams.new)

sandbox.fs.upload_file('./box-mount', '/tmp/box-mount')
sandbox.process.exec(
  command: 'sudo mv /tmp/box-mount /usr/local/bin/agent-mount ' \
           '&& sudo chmod +x /usr/local/bin/agent-mount ' \
           '&& sudo ln -s agent-mount /usr/local/bin/box-mount'
)

response = sandbox.process.exec(command: 'box-mount --version')
puts response.result
```

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

```go
import (
	"context"
	"fmt"
	"log"
	"os"

	"github.com/daytona/clients/sdk-go/pkg/daytona"
	"github.com/daytona/clients/sdk-go/pkg/types"
)

ctx := context.Background()
client, err := daytona.NewClient()
if err != nil {
	log.Fatal(err)
}

sandbox, err := client.Create(ctx, types.SnapshotParams{})
if err != nil {
	log.Fatal(err)
}

binary, err := os.ReadFile("./box-mount")
if err != nil {
	log.Fatal(err)
}
if err := sandbox.FileSystem.UploadFile(ctx, binary, "/tmp/box-mount"); err != nil {
	log.Fatal(err)
}
if _, err := sandbox.Process.ExecuteCommand(ctx,
	"sudo mv /tmp/box-mount /usr/local/bin/agent-mount && sudo chmod +x /usr/local/bin/agent-mount && sudo ln -s agent-mount /usr/local/bin/box-mount"); err != nil {
	log.Fatal(err)
}

response, err := sandbox.Process.ExecuteCommand(ctx, "box-mount --version")
if err != nil {
	log.Fatal(err)
}
fmt.Println(response.Result)
```

</TabItem>
<TabItem label="Java" icon="seti:java">

```java
import io.daytona.sdk.Daytona;
import io.daytona.sdk.Sandbox;
import io.daytona.sdk.model.CreateSandboxFromSnapshotParams;
import io.daytona.sdk.model.ExecuteResponse;

import java.nio.file.Files;
import java.nio.file.Paths;

public class App {
    public static void main(String[] args) throws Exception {
        try (Daytona daytona = new Daytona()) {
            Sandbox sandbox = daytona.create(new CreateSandboxFromSnapshotParams());

            byte[] binary = Files.readAllBytes(Paths.get("./box-mount"));
            sandbox.fs.uploadFile(binary, "/tmp/box-mount");
            sandbox.getProcess().executeCommand(
                "sudo mv /tmp/box-mount /usr/local/bin/agent-mount "
                    + "&& sudo chmod +x /usr/local/bin/agent-mount "
                    + "&& sudo ln -s agent-mount /usr/local/bin/box-mount");

            ExecuteResponse response = sandbox.getProcess().executeCommand("box-mount --version");
            System.out.println(response.getResult());
        }
    }
}
```

</TabItem>
</Tabs>

### Mount with JWT server authentication [#mount-with-jwt-server-authentication]

JWT is the recommended mode for sandboxes: authentication is fully non-interactive, tokens are minted and refreshed in memory from a static config file, and there is nothing to rotate or hand out per sandbox. It requires an app owned by a Box enterprise — on a personal account the generated config contains `"enterpriseID": "0"` and authentication fails.

**Credentials** — create a [Platform App with JWT authentication ↗](https://developer.box.com/guides/authentication/jwt/jwt-setup/) in the Box Developer Console — select **JWT**, not the Client Credentials Grant the console recommends, because Box Mount supports OAuth and JWT only. Go to **Configuration** → **Add and Manage Public Keys**, and click **Generate a Public/Private Keypair** — this downloads the app's config JSON (keypair generation requires 2FA on your Box account). Then have a Box admin [authorize the app ↗](https://developer.box.com/guides/authorization/) for your enterprise. Set `BOX_CONFIG_JSON` in your local environment to the full contents of that JSON file. The snippets below upload it into the sandbox via `sandbox.fs` and point Box Mount at it with the `BOX_AUTH_TYPE` and `BOX_JWT_CONFIG_PATH` environment variables.

<Callout type="note">
A JWT app authenticates as a [service account ↗](https://developer.box.com/platform/user-types/) with its own content tree — it does not see any user's files by default. To mount an existing folder, invite the service account's email address (shown in the Developer Console under **General Settings**) as a collaborator on that folder, and pass the folder's ID — the number at the end of its Box URL — to `box-mount mount`.
</Callout>

<Tabs syncKey="language">
<TabItem label="Python" icon="seti:python">

```python
import os
from daytona import CreateSandboxFromSnapshotParams, Daytona

daytona = Daytona()

# BOX_CONFIG_JSON holds the full Platform App config JSON as a string
box_config = os.environ["BOX_CONFIG_JSON"].encode()

sandbox = daytona.create(
    CreateSandboxFromSnapshotParams(
        snapshot="box-mount",
        env_vars={
            "BOX_AUTH_TYPE": "jwt",
            "BOX_JWT_CONFIG_PATH": "/home/daytona/.box-jwt-config.json",
        },
    )
)

mount_path = "/home/daytona/box"

# Upload the config file into the sandbox
sandbox.fs.upload_file(box_config, "/home/daytona/.box-jwt-config.json")
sandbox.process.exec("chmod 600 /home/daytona/.box-jwt-config.json")

# Mount the Box folder (the daemon starts and syncs both ways)
sandbox.process.exec(f"mkdir -p {mount_path}")
sandbox.process.exec(f'box-mount mount "{mount_path}" "123456789"')
sandbox.process.exec("box-mount status")

# Use the mount
sandbox.process.exec(f"echo 'hello from Daytona' > {mount_path}/hello.txt")
response = sandbox.process.exec(f"ls {mount_path}")
print(response.result)
```

</TabItem>
<TabItem label="TypeScript" icon="seti:typescript">

```typescript
import { Daytona } from '@daytona/sdk'

const daytona = new Daytona()

// BOX_CONFIG_JSON holds the full Platform App config JSON as a string
const boxConfig = Buffer.from(process.env.BOX_CONFIG_JSON!)

const sandbox = await daytona.create({
  snapshot: 'box-mount',
  envVars: {
    BOX_AUTH_TYPE: 'jwt',
    BOX_JWT_CONFIG_PATH: '/home/daytona/.box-jwt-config.json',
  },
})

const mountPath = '/home/daytona/box'

// Upload the config file into the sandbox
await sandbox.fs.uploadFile(boxConfig, '/home/daytona/.box-jwt-config.json')
await sandbox.process.executeCommand('chmod 600 /home/daytona/.box-jwt-config.json')

// Mount the Box folder (the daemon starts and syncs both ways)
await sandbox.process.executeCommand(`mkdir -p ${mountPath}`)
await sandbox.process.executeCommand(`box-mount mount "${mountPath}" "123456789"`)
await sandbox.process.executeCommand('box-mount status')

// Use the mount
await sandbox.process.executeCommand(`echo 'hello from Daytona' > ${mountPath}/hello.txt`)
const response = await sandbox.process.executeCommand(`ls ${mountPath}`)
console.log(response.result)
```

</TabItem>
<TabItem label="Ruby" icon="seti:ruby">

```ruby
require 'daytona'

daytona = Daytona::Daytona.new

# BOX_CONFIG_JSON holds the full Platform App config JSON as a string
box_config = ENV.fetch('BOX_CONFIG_JSON')

sandbox = daytona.create(
  Daytona::CreateSandboxFromSnapshotParams.new(
    snapshot: 'box-mount',
    env_vars: {
      'BOX_AUTH_TYPE' => 'jwt',
      'BOX_JWT_CONFIG_PATH' => '/home/daytona/.box-jwt-config.json'
    }
  )
)

mount_path = '/home/daytona/box'

# Upload the config file into the sandbox
sandbox.fs.upload_file(box_config, '/home/daytona/.box-jwt-config.json')
sandbox.process.exec(command: 'chmod 600 /home/daytona/.box-jwt-config.json')

# Mount the Box folder (the daemon starts and syncs both ways)
sandbox.process.exec(command: "mkdir -p #{mount_path}")
sandbox.process.exec(command: "box-mount mount \"#{mount_path}\" \"123456789\"")
sandbox.process.exec(command: 'box-mount status')

# Use the mount
sandbox.process.exec(command: "echo 'hello from Daytona' > #{mount_path}/hello.txt")
response = sandbox.process.exec(command: "ls #{mount_path}")
puts response.result
```

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

```go
import (
	"context"
	"fmt"
	"log"
	"os"

	"github.com/daytona/clients/sdk-go/pkg/daytona"
	"github.com/daytona/clients/sdk-go/pkg/types"
)

ctx := context.Background()
client, err := daytona.NewClient()
if err != nil {
	log.Fatal(err)
}

// BOX_CONFIG_JSON holds the full Platform App config JSON as a string
boxConfig := []byte(os.Getenv("BOX_CONFIG_JSON"))

sandbox, err := client.Create(ctx, types.SnapshotParams{
	Snapshot: "box-mount",
	SandboxBaseParams: types.SandboxBaseParams{
		EnvVars: map[string]string{
			"BOX_AUTH_TYPE":       "jwt",
			"BOX_JWT_CONFIG_PATH": "/home/daytona/.box-jwt-config.json",
		},
	},
})
if err != nil {
	log.Fatal(err)
}

mountPath := "/home/daytona/box"

// Upload the config file into the sandbox
if err := sandbox.FileSystem.UploadFile(ctx, boxConfig, "/home/daytona/.box-jwt-config.json"); err != nil {
	log.Fatal(err)
}
if _, err := sandbox.Process.ExecuteCommand(ctx, "chmod 600 /home/daytona/.box-jwt-config.json"); err != nil {
	log.Fatal(err)
}

// Mount the Box folder (the daemon starts and syncs both ways)
if _, err := sandbox.Process.ExecuteCommand(ctx, "mkdir -p "+mountPath); err != nil {
	log.Fatal(err)
}
if _, err := sandbox.Process.ExecuteCommand(ctx,
	`box-mount mount "`+mountPath+`" "123456789"`); err != nil {
	log.Fatal(err)
}
if _, err := sandbox.Process.ExecuteCommand(ctx, "box-mount status"); err != nil {
	log.Fatal(err)
}

// Use the mount
if _, err := sandbox.Process.ExecuteCommand(ctx,
	"echo 'hello from Daytona' > "+mountPath+"/hello.txt"); err != nil {
	log.Fatal(err)
}
response, err := sandbox.Process.ExecuteCommand(ctx, "ls "+mountPath)
if err != nil {
	log.Fatal(err)
}
fmt.Println(response.Result)
```

</TabItem>
<TabItem label="Java" icon="seti:java">

```java
import io.daytona.sdk.Daytona;
import io.daytona.sdk.Sandbox;
import io.daytona.sdk.model.CreateSandboxFromSnapshotParams;
import io.daytona.sdk.model.ExecuteResponse;

import java.nio.charset.StandardCharsets;
import java.util.Map;

public class App {
    public static void main(String[] args) {
        try (Daytona daytona = new Daytona()) {
            // BOX_CONFIG_JSON holds the full Platform App config JSON as a string
            byte[] boxConfig = System.getenv("BOX_CONFIG_JSON")
                .getBytes(StandardCharsets.UTF_8);

            CreateSandboxFromSnapshotParams params = new CreateSandboxFromSnapshotParams();
            params.setSnapshot("box-mount");
            params.setEnvVars(Map.of(
                "BOX_AUTH_TYPE", "jwt",
                "BOX_JWT_CONFIG_PATH", "/home/daytona/.box-jwt-config.json"
            ));
            Sandbox sandbox = daytona.create(params);

            String mountPath = "/home/daytona/box";

            // Upload the config file into the sandbox
            sandbox.fs.uploadFile(boxConfig, "/home/daytona/.box-jwt-config.json");
            sandbox.getProcess().executeCommand("chmod 600 /home/daytona/.box-jwt-config.json");

            // Mount the Box folder (the daemon starts and syncs both ways)
            sandbox.getProcess().executeCommand("mkdir -p " + mountPath);
            sandbox.getProcess().executeCommand(
                "box-mount mount \"" + mountPath + "\" \"123456789\"");
            sandbox.getProcess().executeCommand("box-mount status");

            // Use the mount
            sandbox.getProcess().executeCommand(
                "echo 'hello from Daytona' > " + mountPath + "/hello.txt");
            ExecuteResponse response = sandbox.getProcess().executeCommand("ls " + mountPath);
            System.out.println(response.getResult());
        }
    }
}
```

</TabItem>
</Tabs>

To unmount during the sandbox's lifetime, run `box-mount unmount "/home/daytona/box"`; deleting the sandbox tears the daemon down automatically.

### Mount with OAuth [#mount-with-oauth]

OAuth mode acts as the user who authorized the app, so the mount can never reach content that user cannot. Box Mount reads the OAuth credentials from environment variables — no config upload is needed. To obtain the initial token pair, complete your app's OAuth flow once outside the sandbox; see [Box Mount credentials ↗](https://developer.box.com/guides/box-mount#configure-credentials) and the [OAuth 2.0 setup guide ↗](https://developer.box.com/guides/authentication/oauth2/oauth2-setup/). Set `BOX_CLIENT_ID`, `BOX_CLIENT_SECRET`, `BOX_ACCESS_TOKEN`, and `BOX_REFRESH_TOKEN` in your local environment; the snippets below pass them into the sandbox via `envVars`.

<Tabs syncKey="language">
<TabItem label="Python" icon="seti:python">

```python
import os
from daytona import CreateSandboxFromSnapshotParams, Daytona

daytona = Daytona()

sandbox = daytona.create(
    CreateSandboxFromSnapshotParams(
        snapshot="box-mount",
        env_vars={
            "BOX_CLIENT_ID": os.environ["BOX_CLIENT_ID"],
            "BOX_CLIENT_SECRET": os.environ["BOX_CLIENT_SECRET"],
            "BOX_ACCESS_TOKEN": os.environ["BOX_ACCESS_TOKEN"],
            "BOX_REFRESH_TOKEN": os.environ["BOX_REFRESH_TOKEN"],
        },
    )
)

mount_path = "/home/daytona/box"

# Mount the Box folder (the daemon starts and syncs both ways)
sandbox.process.exec(f"mkdir -p {mount_path}")
sandbox.process.exec(f'box-mount mount "{mount_path}" "123456789"')
sandbox.process.exec("box-mount status")

# Use the mount
sandbox.process.exec(f"echo 'hello from Daytona' > {mount_path}/hello.txt")
response = sandbox.process.exec(f"ls {mount_path}")
print(response.result)
```

</TabItem>
<TabItem label="TypeScript" icon="seti:typescript">

```typescript
import { Daytona } from '@daytona/sdk'

const daytona = new Daytona()

const sandbox = await daytona.create({
  snapshot: 'box-mount',
  envVars: {
    BOX_CLIENT_ID: process.env.BOX_CLIENT_ID!,
    BOX_CLIENT_SECRET: process.env.BOX_CLIENT_SECRET!,
    BOX_ACCESS_TOKEN: process.env.BOX_ACCESS_TOKEN!,
    BOX_REFRESH_TOKEN: process.env.BOX_REFRESH_TOKEN!,
  },
})

const mountPath = '/home/daytona/box'

// Mount the Box folder (the daemon starts and syncs both ways)
await sandbox.process.executeCommand(`mkdir -p ${mountPath}`)
await sandbox.process.executeCommand(`box-mount mount "${mountPath}" "123456789"`)
await sandbox.process.executeCommand('box-mount status')

// Use the mount
await sandbox.process.executeCommand(`echo 'hello from Daytona' > ${mountPath}/hello.txt`)
const response = await sandbox.process.executeCommand(`ls ${mountPath}`)
console.log(response.result)
```

</TabItem>
<TabItem label="Ruby" icon="seti:ruby">

```ruby
require 'daytona'

daytona = Daytona::Daytona.new

sandbox = daytona.create(
  Daytona::CreateSandboxFromSnapshotParams.new(
    snapshot: 'box-mount',
    env_vars: {
      'BOX_CLIENT_ID' => ENV.fetch('BOX_CLIENT_ID'),
      'BOX_CLIENT_SECRET' => ENV.fetch('BOX_CLIENT_SECRET'),
      'BOX_ACCESS_TOKEN' => ENV.fetch('BOX_ACCESS_TOKEN'),
      'BOX_REFRESH_TOKEN' => ENV.fetch('BOX_REFRESH_TOKEN')
    }
  )
)

mount_path = '/home/daytona/box'

# Mount the Box folder (the daemon starts and syncs both ways)
sandbox.process.exec(command: "mkdir -p #{mount_path}")
sandbox.process.exec(command: "box-mount mount \"#{mount_path}\" \"123456789\"")
sandbox.process.exec(command: 'box-mount status')

# Use the mount
sandbox.process.exec(command: "echo 'hello from Daytona' > #{mount_path}/hello.txt")
response = sandbox.process.exec(command: "ls #{mount_path}")
puts response.result
```

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

```go
import (
	"context"
	"fmt"
	"log"
	"os"

	"github.com/daytona/clients/sdk-go/pkg/daytona"
	"github.com/daytona/clients/sdk-go/pkg/types"
)

ctx := context.Background()
client, err := daytona.NewClient()
if err != nil {
	log.Fatal(err)
}

sandbox, err := client.Create(ctx, types.SnapshotParams{
	Snapshot: "box-mount",
	SandboxBaseParams: types.SandboxBaseParams{
		EnvVars: map[string]string{
			"BOX_CLIENT_ID":     os.Getenv("BOX_CLIENT_ID"),
			"BOX_CLIENT_SECRET": os.Getenv("BOX_CLIENT_SECRET"),
			"BOX_ACCESS_TOKEN":  os.Getenv("BOX_ACCESS_TOKEN"),
			"BOX_REFRESH_TOKEN": os.Getenv("BOX_REFRESH_TOKEN"),
		},
	},
})
if err != nil {
	log.Fatal(err)
}

mountPath := "/home/daytona/box"

// Mount the Box folder (the daemon starts and syncs both ways)
if _, err := sandbox.Process.ExecuteCommand(ctx, "mkdir -p "+mountPath); err != nil {
	log.Fatal(err)
}
if _, err := sandbox.Process.ExecuteCommand(ctx,
	`box-mount mount "`+mountPath+`" "123456789"`); err != nil {
	log.Fatal(err)
}
if _, err := sandbox.Process.ExecuteCommand(ctx, "box-mount status"); err != nil {
	log.Fatal(err)
}

// Use the mount
if _, err := sandbox.Process.ExecuteCommand(ctx,
	"echo 'hello from Daytona' > "+mountPath+"/hello.txt"); err != nil {
	log.Fatal(err)
}
response, err := sandbox.Process.ExecuteCommand(ctx, "ls "+mountPath)
if err != nil {
	log.Fatal(err)
}
fmt.Println(response.Result)
```

</TabItem>
<TabItem label="Java" icon="seti:java">

```java
import io.daytona.sdk.Daytona;
import io.daytona.sdk.Sandbox;
import io.daytona.sdk.model.CreateSandboxFromSnapshotParams;
import io.daytona.sdk.model.ExecuteResponse;

import java.util.Map;

public class App {
    public static void main(String[] args) {
        try (Daytona daytona = new Daytona()) {
            CreateSandboxFromSnapshotParams params = new CreateSandboxFromSnapshotParams();
            params.setSnapshot("box-mount");
            params.setEnvVars(Map.of(
                "BOX_CLIENT_ID", System.getenv("BOX_CLIENT_ID"),
                "BOX_CLIENT_SECRET", System.getenv("BOX_CLIENT_SECRET"),
                "BOX_ACCESS_TOKEN", System.getenv("BOX_ACCESS_TOKEN"),
                "BOX_REFRESH_TOKEN", System.getenv("BOX_REFRESH_TOKEN")
            ));
            Sandbox sandbox = daytona.create(params);

            String mountPath = "/home/daytona/box";

            // Mount the Box folder (the daemon starts and syncs both ways)
            sandbox.getProcess().executeCommand("mkdir -p " + mountPath);
            sandbox.getProcess().executeCommand(
                "box-mount mount \"" + mountPath + "\" \"123456789\"");
            sandbox.getProcess().executeCommand("box-mount status");

            // Use the mount
            sandbox.getProcess().executeCommand(
                "echo 'hello from Daytona' > " + mountPath + "/hello.txt");
            ExecuteResponse response = sandbox.getProcess().executeCommand("ls " + mountPath);
            System.out.println(response.getResult());
        }
    }
}
```

</TabItem>
</Tabs>

<Callout type="note">
Box rotates the refresh token on every renewal (roughly hourly), and Box Mount writes the rotated pair back to its config inside the sandbox — the values you injected go stale after the first refresh. Keep one token chain per sandbox and don't reuse a chain that another integration relies on. For fleets of sandboxes sharing one identity, JWT avoids per-sandbox tokens entirely. A [developer token ↗](https://developer.box.com/guides/authentication/tokens/developer-tokens) passed as `BOX_ACCESS_TOKEN` alone also works for quick tests: it lasts 60 minutes and cannot refresh, so syncing stops when it expires.
</Callout>

## Mount an Archil disk [#mount-an-archil-disk]

[Archil ↗](https://archil.com) is an infinite, elastic, POSIX file system that automatically synchronizes to object storage like S3, R2, and Azure Blob. You should use Archil when you need to mount a bucket to your Daytona sandbox, but need higher out-of-the-box performance than traditional FUSE mounts. Archil achieves higher performance by using shared SSD read and write caching in front of your object storage bucket. Archil disks are mounted as regular directories, scale to whatever your sandbox writes (you pay only for what you use), and can be [mounted by many sandboxes at once ↗](https://docs.archil.com/concepts/shared-disks), making them a natural fit for parallel agents that share state.

**Credentials** — set `ARCHIL_MOUNT_TOKEN` (a disk-scoped [mount token ↗](https://docs.archil.com/concepts/disk-users#disk-token-authorization) generated from the disk's **Details** page in the [Archil console ↗](https://console.archil.com)), `ARCHIL_REGION` (the disk's region, e.g. `aws-us-east-1`), and `ARCHIL_DISK` (the owner-qualified disk name, e.g. `myorg/my-disk`, or disk ID like `dsk-0123456789abcdef`) in your local environment. The snippets below pass them into the sandbox via `envVars`, and the `archil` CLI reads them from there.

### Pre-built snapshot [#pre-built-snapshot-7]

Build a snapshot with the `archil` CLI preinstalled, then launch Archil-enabled sandboxes from that snapshot. You still authenticate and mount at runtime, but installation is no longer part of each sandbox startup sequence.

#### Build a snapshot [#build-a-snapshot-6]

Create a reusable snapshot that installs the `archil` CLI. The `daytonaio/sandbox` base image ships Debian Trixie which doesn't include `libfuse2`, and the `archil` CLI links against it, so we apt-install it before the Archil installer. On Trixie the package is named `libfuse2t64`; installing `libfuse2` also works because it is a transitional package that pulls in `libfuse2t64`.

<Tabs syncKey="language">
<TabItem label="Python" icon="seti:python">

```python
from daytona import CreateSnapshotParams, Daytona, Image

daytona = Daytona()

image = (
  Image.base("daytonaio/sandbox")
  .run_commands(
    "sudo apt-get update "
    "&& sudo apt-get install -y --no-install-recommends libfuse2 ca-certificates",
    "curl -fsSL https://archil.com/install | sh",
  )
)

daytona.snapshot.create(
    CreateSnapshotParams(name="fuse-archil", image=image),
    on_logs=lambda chunk: print(chunk, end="", flush=True),
)
```

</TabItem>
<TabItem label="TypeScript" icon="seti:typescript">

```typescript
import { Daytona, Image } from '@daytona/sdk'

const daytona = new Daytona()

const image = Image.base('daytonaio/sandbox').runCommands(
  'sudo apt-get update ' +
    '&& sudo apt-get install -y --no-install-recommends libfuse2 ca-certificates',
  'curl -fsSL https://archil.com/install | sh',
)

await daytona.snapshot.create(
  { name: 'fuse-archil', image },
  { onLogs: console.log },
)
```

</TabItem>
<TabItem label="Ruby" icon="seti:ruby">

```ruby
require 'daytona'

daytona = Daytona::Daytona.new

image = Daytona::Image
  .base('daytonaio/sandbox')
  .run_commands(
    'sudo apt-get update ' \
    '&& sudo apt-get install -y --no-install-recommends libfuse2 ca-certificates',
    'curl -fsSL https://archil.com/install | sh'
  )

daytona.snapshot.create(
  Daytona::CreateSnapshotParams.new(name: 'fuse-archil', image: image),
  on_logs: proc { |chunk| print(chunk) }
)
```

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

```go
import (
	"context"
	"fmt"
	"log"

	"github.com/daytona/clients/sdk-go/pkg/daytona"
	"github.com/daytona/clients/sdk-go/pkg/types"
)

ctx := context.Background()
client, err := daytona.NewClient()
if err != nil {
	log.Fatal(err)
}

image := daytona.Base("daytonaio/sandbox").
	Run("sudo apt-get update && sudo apt-get install -y --no-install-recommends libfuse2 ca-certificates").
	Run("curl -fsSL https://archil.com/install | sh")

_, logChan, err := client.Snapshot.Create(ctx, &types.CreateSnapshotParams{
	Name:  "fuse-archil",
	Image: image,
})
if err != nil {
	log.Fatal(err)
}
for line := range logChan {
	fmt.Print(line)
}
```

</TabItem>
<TabItem label="Java" icon="seti:java">

```java
import io.daytona.sdk.Daytona;
import io.daytona.sdk.Image;

public class App {
    public static void main(String[] args) {
        try (Daytona daytona = new Daytona()) {
            Image image = Image.base("daytonaio/sandbox")
                .runCommands(
                    "sudo apt-get update "
                        + "&& sudo apt-get install -y --no-install-recommends libfuse2 ca-certificates",
                    "curl -fsSL https://archil.com/install | sh"
                );

            daytona.snapshot().create("fuse-archil", image, System.out::println);
        }
    }
}
```

</TabItem>
</Tabs>

#### Launch and mount [#launch-and-mount-6]

Pass `ARCHIL_MOUNT_TOKEN`, `ARCHIL_REGION`, and `ARCHIL_DISK` to the sandbox via `envVars`. Your code then mounts the disk at `/home/daytona/archil` and hands ownership to the `daytona` user so non-root processes can read and write through the mount. The `chown` step works on exclusive (non-shared) mounts only; on mounts created with `--shared` or `--read-only` it fails with `Read-only file system`. See [Shared and read-only mounts](#shared-and-read-only-mounts).

<Tabs syncKey="language">
<TabItem label="Python" icon="seti:python">

```python
import os
from daytona import CreateSandboxFromSnapshotParams, Daytona

daytona = Daytona()

mount_path = "/home/daytona/archil"

sandbox = daytona.create(
    CreateSandboxFromSnapshotParams(
        snapshot="fuse-archil",
        env_vars={
            "ARCHIL_MOUNT_TOKEN": os.environ["ARCHIL_MOUNT_TOKEN"],
            "ARCHIL_REGION": os.environ["ARCHIL_REGION"],
            "ARCHIL_DISK": os.environ["ARCHIL_DISK"],
        },
    )
)

sandbox.process.exec(f"sudo mkdir -p {mount_path}")
sandbox.process.exec(
    f"sudo --preserve-env=ARCHIL_MOUNT_TOKEN archil mount "
    f"$ARCHIL_DISK {mount_path} --region $ARCHIL_REGION"
)
# Exclusive mounts only; on --shared or --read-only mounts this fails
# with "Read-only file system" (see "Shared and read-only mounts" below)
sandbox.process.exec(f"sudo chown daytona:daytona {mount_path}")

# Read and write through the mount as if it were a local directory
response = sandbox.process.exec(f"ls {mount_path}")
print(response.result)
```

</TabItem>
<TabItem label="TypeScript" icon="seti:typescript">

```typescript
import { Daytona } from '@daytona/sdk'

const daytona = new Daytona()

const mountPath = '/home/daytona/archil'

const sandbox = await daytona.create({
  snapshot: 'fuse-archil',
  envVars: {
    ARCHIL_MOUNT_TOKEN: process.env.ARCHIL_MOUNT_TOKEN!,
    ARCHIL_REGION: process.env.ARCHIL_REGION!,
    ARCHIL_DISK: process.env.ARCHIL_DISK!,
  },
})

await sandbox.process.executeCommand(`sudo mkdir -p ${mountPath}`)
await sandbox.process.executeCommand(
  `sudo --preserve-env=ARCHIL_MOUNT_TOKEN archil mount ` +
    `$ARCHIL_DISK ${mountPath} --region $ARCHIL_REGION`,
)
// Exclusive mounts only; on --shared or --read-only mounts this fails
// with "Read-only file system" (see "Shared and read-only mounts" below)
await sandbox.process.executeCommand(`sudo chown daytona:daytona ${mountPath}`)

// Read and write through the mount as if it were a local directory
const response = await sandbox.process.executeCommand(`ls ${mountPath}`)
console.log(response.result)
```

</TabItem>
<TabItem label="Ruby" icon="seti:ruby">

```ruby
require 'daytona'

daytona = Daytona::Daytona.new

mount_path = '/home/daytona/archil'

sandbox = daytona.create(
  Daytona::CreateSandboxFromSnapshotParams.new(
    snapshot: 'fuse-archil',
    env_vars: {
      'ARCHIL_MOUNT_TOKEN' => ENV.fetch('ARCHIL_MOUNT_TOKEN'),
      'ARCHIL_REGION' => ENV.fetch('ARCHIL_REGION'),
      'ARCHIL_DISK' => ENV.fetch('ARCHIL_DISK')
    }
  )
)

sandbox.process.exec(command: "sudo mkdir -p #{mount_path}")
sandbox.process.exec(
  command: "sudo --preserve-env=ARCHIL_MOUNT_TOKEN archil mount " \
           "$ARCHIL_DISK #{mount_path} --region $ARCHIL_REGION"
)
# Exclusive mounts only; on --shared or --read-only mounts this fails
# with "Read-only file system" (see "Shared and read-only mounts" below)
sandbox.process.exec(command: "sudo chown daytona:daytona #{mount_path}")

# Read and write through the mount as if it were a local directory
response = sandbox.process.exec(command: "ls #{mount_path}")
puts response.result
```

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

```go
import (
	"context"
	"fmt"
	"log"
	"os"

	"github.com/daytona/clients/sdk-go/pkg/daytona"
	"github.com/daytona/clients/sdk-go/pkg/types"
)

ctx := context.Background()
client, err := daytona.NewClient()
if err != nil {
	log.Fatal(err)
}

mountPath := "/home/daytona/archil"

sandbox, err := client.Create(ctx, types.SnapshotParams{
	Snapshot: "fuse-archil",
	SandboxBaseParams: types.SandboxBaseParams{
		EnvVars: map[string]string{
			"ARCHIL_MOUNT_TOKEN": os.Getenv("ARCHIL_MOUNT_TOKEN"),
			"ARCHIL_REGION":      os.Getenv("ARCHIL_REGION"),
			"ARCHIL_DISK":        os.Getenv("ARCHIL_DISK"),
		},
	},
})
if err != nil {
	log.Fatal(err)
}

if _, err := sandbox.Process.ExecuteCommand(ctx, "sudo mkdir -p "+mountPath); err != nil {
	log.Fatal(err)
}
if _, err := sandbox.Process.ExecuteCommand(ctx,
	"sudo --preserve-env=ARCHIL_MOUNT_TOKEN archil mount "+
		"$ARCHIL_DISK "+mountPath+" --region $ARCHIL_REGION"); err != nil {
	log.Fatal(err)
}
// Exclusive mounts only; on --shared or --read-only mounts this fails
// with "Read-only file system" (see "Shared and read-only mounts" below)
if _, err := sandbox.Process.ExecuteCommand(ctx,
	"sudo chown daytona:daytona "+mountPath); err != nil {
	log.Fatal(err)
}

// Read and write through the mount as if it were a local directory
response, err := sandbox.Process.ExecuteCommand(ctx, "ls "+mountPath)
if err != nil {
	log.Fatal(err)
}
fmt.Println(response.Result)
```

</TabItem>
<TabItem label="Java" icon="seti:java">

```java
import io.daytona.sdk.Daytona;
import io.daytona.sdk.Sandbox;
import io.daytona.sdk.model.CreateSandboxFromSnapshotParams;
import io.daytona.sdk.model.ExecuteResponse;

import java.util.Map;

public class App {
    public static void main(String[] args) {
        try (Daytona daytona = new Daytona()) {
            String mountPath = "/home/daytona/archil";

            CreateSandboxFromSnapshotParams params = new CreateSandboxFromSnapshotParams();
            params.setSnapshot("fuse-archil");
            params.setEnvVars(Map.of(
                "ARCHIL_MOUNT_TOKEN", System.getenv("ARCHIL_MOUNT_TOKEN"),
                "ARCHIL_REGION", System.getenv("ARCHIL_REGION"),
                "ARCHIL_DISK", System.getenv("ARCHIL_DISK")
            ));
            Sandbox sandbox = daytona.create(params);

            sandbox.getProcess().executeCommand("sudo mkdir -p " + mountPath);
            sandbox.getProcess().executeCommand(
                "sudo --preserve-env=ARCHIL_MOUNT_TOKEN archil mount "
                + "$ARCHIL_DISK " + mountPath + " --region $ARCHIL_REGION");
            // Exclusive mounts only; on --shared or --read-only mounts this fails
            // with "Read-only file system" (see "Shared and read-only mounts" below)
            sandbox.getProcess().executeCommand("sudo chown daytona:daytona " + mountPath);

            // Read and write through the mount as if it were a local directory
            ExecuteResponse response = sandbox.getProcess().executeCommand("ls " + mountPath);
            System.out.println(response.getResult());
        }
    }
}
```

</TabItem>
</Tabs>

To let multiple sandboxes mount the **same** disk concurrently, add `--shared` to `archil mount`. Shared mounts change how permissions and writes behave; see [Shared and read-only mounts](#shared-and-read-only-mounts).

### Runtime install [#runtime-install-7]

Start from a default sandbox and install the `archil` CLI during startup before mounting the disk. This is useful when iterating quickly on mount behavior, with the tradeoff of slower cold starts for each sandbox.

<Tabs syncKey="language">
<TabItem label="Python" icon="seti:python">

```python
import os
from daytona import CreateSandboxBaseParams, Daytona

daytona = Daytona()

mount_path = "/home/daytona/archil"

sandbox = daytona.create(
    CreateSandboxBaseParams(
        env_vars={
            "ARCHIL_MOUNT_TOKEN": os.environ["ARCHIL_MOUNT_TOKEN"],
            "ARCHIL_REGION": os.environ["ARCHIL_REGION"],
            "ARCHIL_DISK": os.environ["ARCHIL_DISK"],
        },
    )
)

sandbox.process.exec(
    "sudo apt-get update "
    "&& sudo apt-get install -y --no-install-recommends libfuse2 ca-certificates"
)
sandbox.process.exec("curl -fsSL https://archil.com/install | sh")

sandbox.process.exec(f"sudo mkdir -p {mount_path}")
sandbox.process.exec(
    f"sudo --preserve-env=ARCHIL_MOUNT_TOKEN archil mount "
    f"$ARCHIL_DISK {mount_path} --region $ARCHIL_REGION"
)
# Exclusive mounts only; on --shared or --read-only mounts this fails
# with "Read-only file system" (see "Shared and read-only mounts" below)
sandbox.process.exec(f"sudo chown daytona:daytona {mount_path}")

response = sandbox.process.exec(f"ls {mount_path}")
print(response.result)
```

</TabItem>
<TabItem label="TypeScript" icon="seti:typescript">

```typescript
import { Daytona } from '@daytona/sdk'

const daytona = new Daytona()

const mountPath = '/home/daytona/archil'

const sandbox = await daytona.create({
  envVars: {
    ARCHIL_MOUNT_TOKEN: process.env.ARCHIL_MOUNT_TOKEN!,
    ARCHIL_REGION: process.env.ARCHIL_REGION!,
    ARCHIL_DISK: process.env.ARCHIL_DISK!,
  },
})

await sandbox.process.executeCommand(
  'sudo apt-get update ' +
    '&& sudo apt-get install -y --no-install-recommends libfuse2 ca-certificates',
)
await sandbox.process.executeCommand('curl -fsSL https://archil.com/install | sh')

await sandbox.process.executeCommand(`sudo mkdir -p ${mountPath}`)
await sandbox.process.executeCommand(
  `sudo --preserve-env=ARCHIL_MOUNT_TOKEN archil mount ` +
    `$ARCHIL_DISK ${mountPath} --region $ARCHIL_REGION`,
)
// Exclusive mounts only; on --shared or --read-only mounts this fails
// with "Read-only file system" (see "Shared and read-only mounts" below)
await sandbox.process.executeCommand(`sudo chown daytona:daytona ${mountPath}`)

const response = await sandbox.process.executeCommand(`ls ${mountPath}`)
console.log(response.result)
```

</TabItem>
<TabItem label="Ruby" icon="seti:ruby">

```ruby
require 'daytona'

daytona = Daytona::Daytona.new

mount_path = '/home/daytona/archil'

sandbox = daytona.create(
  Daytona::CreateSandboxBaseParams.new(
    env_vars: {
      'ARCHIL_MOUNT_TOKEN' => ENV.fetch('ARCHIL_MOUNT_TOKEN'),
      'ARCHIL_REGION' => ENV.fetch('ARCHIL_REGION'),
      'ARCHIL_DISK' => ENV.fetch('ARCHIL_DISK')
    }
  )
)

sandbox.process.exec(
  command: 'sudo apt-get update ' \
           '&& sudo apt-get install -y --no-install-recommends libfuse2 ca-certificates'
)
sandbox.process.exec(command: 'curl -fsSL https://archil.com/install | sh')

sandbox.process.exec(command: "sudo mkdir -p #{mount_path}")
sandbox.process.exec(
  command: "sudo --preserve-env=ARCHIL_MOUNT_TOKEN archil mount " \
           "$ARCHIL_DISK #{mount_path} --region $ARCHIL_REGION"
)
# Exclusive mounts only; on --shared or --read-only mounts this fails
# with "Read-only file system" (see "Shared and read-only mounts" below)
sandbox.process.exec(command: "sudo chown daytona:daytona #{mount_path}")

response = sandbox.process.exec(command: "ls #{mount_path}")
puts response.result
```

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

```go
import (
	"context"
	"fmt"
	"log"
	"os"

	"github.com/daytona/clients/sdk-go/pkg/daytona"
	"github.com/daytona/clients/sdk-go/pkg/types"
)

ctx := context.Background()
client, err := daytona.NewClient()
if err != nil {
	log.Fatal(err)
}

mountPath := "/home/daytona/archil"

sandbox, err := client.Create(ctx, types.SnapshotParams{
	SandboxBaseParams: types.SandboxBaseParams{
		EnvVars: map[string]string{
			"ARCHIL_MOUNT_TOKEN": os.Getenv("ARCHIL_MOUNT_TOKEN"),
			"ARCHIL_REGION":      os.Getenv("ARCHIL_REGION"),
			"ARCHIL_DISK":        os.Getenv("ARCHIL_DISK"),
		},
	},
})
if err != nil {
	log.Fatal(err)
}

if _, err := sandbox.Process.ExecuteCommand(ctx,
	"sudo apt-get update && sudo apt-get install -y --no-install-recommends libfuse2 ca-certificates"); err != nil {
	log.Fatal(err)
}
if _, err := sandbox.Process.ExecuteCommand(ctx,
	"curl -fsSL https://archil.com/install | sh"); err != nil {
	log.Fatal(err)
}

if _, err := sandbox.Process.ExecuteCommand(ctx, "sudo mkdir -p "+mountPath); err != nil {
	log.Fatal(err)
}
if _, err := sandbox.Process.ExecuteCommand(ctx,
	"sudo --preserve-env=ARCHIL_MOUNT_TOKEN archil mount "+
		"$ARCHIL_DISK "+mountPath+" --region $ARCHIL_REGION"); err != nil {
	log.Fatal(err)
}
// Exclusive mounts only; on --shared or --read-only mounts this fails
// with "Read-only file system" (see "Shared and read-only mounts" below)
if _, err := sandbox.Process.ExecuteCommand(ctx,
	"sudo chown daytona:daytona "+mountPath); err != nil {
	log.Fatal(err)
}

response, err := sandbox.Process.ExecuteCommand(ctx, "ls "+mountPath)
if err != nil {
	log.Fatal(err)
}
fmt.Println(response.Result)
```

</TabItem>
<TabItem label="Java" icon="seti:java">

```java
import io.daytona.sdk.Daytona;
import io.daytona.sdk.Sandbox;
import io.daytona.sdk.model.CreateSandboxFromSnapshotParams;
import io.daytona.sdk.model.ExecuteResponse;

import java.util.Map;

public class App {
    public static void main(String[] args) {
        try (Daytona daytona = new Daytona()) {
            String mountPath = "/home/daytona/archil";

            CreateSandboxFromSnapshotParams params = new CreateSandboxFromSnapshotParams();
            params.setEnvVars(Map.of(
                "ARCHIL_MOUNT_TOKEN", System.getenv("ARCHIL_MOUNT_TOKEN"),
                "ARCHIL_REGION", System.getenv("ARCHIL_REGION"),
                "ARCHIL_DISK", System.getenv("ARCHIL_DISK")
            ));
            Sandbox sandbox = daytona.create(params);

            sandbox.getProcess().executeCommand(
                "sudo apt-get update "
                + "&& sudo apt-get install -y --no-install-recommends libfuse2 ca-certificates");
            sandbox.getProcess().executeCommand(
                "curl -fsSL https://archil.com/install | sh");

            sandbox.getProcess().executeCommand("sudo mkdir -p " + mountPath);
            sandbox.getProcess().executeCommand(
                "sudo --preserve-env=ARCHIL_MOUNT_TOKEN archil mount "
                + "$ARCHIL_DISK " + mountPath + " --region $ARCHIL_REGION");
            // Exclusive mounts only; on --shared or --read-only mounts this fails
            // with "Read-only file system" (see "Shared and read-only mounts" below)
            sandbox.getProcess().executeCommand("sudo chown daytona:daytona " + mountPath);

            ExecuteResponse response = sandbox.getProcess().executeCommand("ls " + mountPath);
            System.out.println(response.getResult());
        }
    }
}
```

</TabItem>
</Tabs>

### Shared and read-only mounts [#shared-and-read-only-mounts]

The `--shared` and `--read-only` flags produce the same mount behavior: the entire mount is read-only, including `chmod` and `chown`, until `archil checkout <path>` grants a write delegation for that path. This has two consequences for the snippets above:

- **`sudo chown daytona:daytona <mountPath>`** fails with "Read-only file system" on any **`--shared`** or **`--read-only`** mount. On a writable shared mount:

1. Run **`sudo archil checkout <mountPath>`**
2. Run **`chown`**
3. Run **`sudo archil checkin <mountPath>`**

- Ownership and permissions come from the metadata stored on the Archil disk, not from the mount. If a root process wrote files with a restrictive umask, every mount shows them as **`root:root`** with mode **`600`**, and the **`daytona`** user cannot read them.

1. **Fix permissions at the source (recommended)**

    Write with `umask 022` or run `chmod -R a+rX` after writing, so files are readable from the start. For existing data, apply a one-time fix from any client with a writable shared mount:

```bash
sudo --preserve-env=ARCHIL_MOUNT_TOKEN archil mount "$ARCHIL_DISK" /mnt/fix --region "$ARCHIL_REGION" --shared
sudo archil checkout /mnt/fix/data
sudo chmod -R a+rX /mnt/fix/data
sudo archil checkin /mnt/fix/data
```

The permission bits persist on the disk, so every subsequent read-only mount shows the corrected modes.

2. **Re-export with `bindfs` (when the source data cannot be changed)** 

    Inside the sandbox, mount a read-only view of the Archil mount that remaps ownership to the **`daytona`** user:

```bash
sudo apt-get install -y bindfs
mkdir -p /home/daytona/archil-view
sudo bindfs --force-user=daytona --force-group=daytona --perms=a+rX \
  /home/daytona/archil /home/daytona/archil-view
```

The **`archil`** CLI requires root to mount and has no uid, gid, or umask override flags, so there is no way to remap ownership at mount time.

### Common pitfalls [#common-pitfalls]

Archil works on Tier 3 and Tier 4 sandboxes with default network settings (full egress) without additional configuration. The following failure modes look like Daytona blocking egress but have other causes:

- **Missing `libfuse2`.** The `daytonaio/sandbox` base image ships Debian Trixie, which does not include `libfuse2`, and the `archil` CLI links against it. Install `libfuse2t64` (the Trixie package name; `libfuse2` is a transitional package that pulls it in) before running the Archil installer, or bake both into a [pre-built snapshot](#pre-built-snapshot-6).
- **Egress allowlists break Archil.** A sandbox with a [`domainAllowList`](/docs/network-limits) cannot mount Archil disks: the Archil control plane is reached as a bare IP address on port `8100`, which a domain allowlist cannot express. The installer also downloads from `s3.amazonaws.com/archil-client/`, which must be reachable at install time. A [`networkAllowList`](/docs/network-limits) (CIDR) can pin Archil's mount and control IP addresses, but those addresses change, so this is fragile and not recommended. Use full egress for sandboxes that mount Archil disks.
- **`api.archil.com` is not a valid endpoint.** The hostname does not resolve (NXDOMAIN), so allowlisting or testing against it fails regardless of network settings. Archil endpoints are region-specific; for `aws-us-east-1` they are `mount.green.us-east-1.aws.prod.archil.com:8100` and `control.green.us-east-1.aws.prod.archil.com:443`. See [Archil troubleshooting ↗](https://docs.archil.com/reference/troubleshooting).
- **The disk is exclusively mounted elsewhere.** An exclusive mount holds a delegation lock on the disk. Mounting from another sandbox fails until the first client unmounts or runs `archil checkin`, or the new mount passes `--shared` (or `--force` to break a stale lock). This is an Archil delegation conflict, not an egress failure.

## Mount a MesaFS filesystem [#mount-a-mesafs-filesystem]

[MesaFS ↗](https://mesa.dev) is an agent-native versioned filesystem from Mesa, purpose-built for the same workloads Daytona sandboxes run — parallel agent swarms, shared working memory, structured artifacts, and long-lived state across runs. With MesaFS, instead of mounting a cloud bucket, you mount a Mesa **repository**: a Git-compatible versioned working directory with sub-50ms reads/writes, instant fork/branch operations, and unlimited concurrent writers.

The Mesa setup follows the same pattern as the bucket providers but uses the Mesa CLI rather than a FUSE-specific tool: install the CLI in your sandbox, authenticate with your API key, and run `mesa mount --daemonize` to mount your repos at `/home/daytona/mesa/mnt/<org>/<repo>`.

**Credentials** — set `MESA_API_KEY` and `MESA_ORG` (your Mesa organization slug) in your local environment. The snippets below pass them into the sandbox via `envVars`, and the Mesa CLI reads them from there.

### Pre-built snapshot [#pre-built-snapshot-8]

Build a snapshot with the Mesa CLI preinstalled, then launch Mesa-enabled sandboxes from that snapshot. You still authenticate and mount at runtime, but installation is no longer part of each sandbox startup sequence.

#### Build a snapshot [#build-a-snapshot-7]

Create a reusable snapshot that installs the Mesa CLI and enables the FUSE `user_allow_other` setting. Sandboxes launched from `fuse-mesa` can then authenticate and mount repos without repeating install work.

<Tabs syncKey="language">
<TabItem label="Python" icon="seti:python">

```python
from daytona import CreateSnapshotParams, Daytona, Image

daytona = Daytona()

image = (
  Image.base("daytonaio/sandbox")
  .run_commands(
    "curl -fsSL https://mesa.dev/install.sh | sh",
    "sudo sed -i 's/^#user_allow_other/user_allow_other/' /etc/fuse.conf",
  )
)

daytona.snapshot.create(
    CreateSnapshotParams(name="fuse-mesa", image=image),
    on_logs=lambda chunk: print(chunk, end="", flush=True),
)
```

</TabItem>
<TabItem label="TypeScript" icon="seti:typescript">

```typescript
import { Daytona, Image } from '@daytona/sdk'

const daytona = new Daytona()

const image = Image.base('daytonaio/sandbox').runCommands(
  'curl -fsSL https://mesa.dev/install.sh | sh',
  "sudo sed -i 's/^#user_allow_other/user_allow_other/' /etc/fuse.conf",
)

await daytona.snapshot.create(
  { name: 'fuse-mesa', image },
  { onLogs: console.log },
)
```

</TabItem>
<TabItem label="Ruby" icon="seti:ruby">

```ruby
require 'daytona'

daytona = Daytona::Daytona.new

image = Daytona::Image
  .base('daytonaio/sandbox')
  .run_commands(
    'curl -fsSL https://mesa.dev/install.sh | sh',
    "sudo sed -i 's/^#user_allow_other/user_allow_other/' /etc/fuse.conf"
  )

daytona.snapshot.create(
  Daytona::CreateSnapshotParams.new(name: 'fuse-mesa', image: image),
  on_logs: proc { |chunk| print(chunk) }
)
```

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

```go
import (
	"context"
	"fmt"
	"log"

	"github.com/daytona/clients/sdk-go/pkg/daytona"
	"github.com/daytona/clients/sdk-go/pkg/types"
)

ctx := context.Background()
client, err := daytona.NewClient()
if err != nil {
	log.Fatal(err)
}

image := daytona.Base("daytonaio/sandbox").
	Run("curl -fsSL https://mesa.dev/install.sh | sh").
	Run(`sudo sed -i 's/^#user_allow_other/user_allow_other/' /etc/fuse.conf`)

_, logChan, err := client.Snapshot.Create(ctx, &types.CreateSnapshotParams{
	Name:  "fuse-mesa",
	Image: image,
})
if err != nil {
	log.Fatal(err)
}
for line := range logChan {
	fmt.Print(line)
}
```

</TabItem>
<TabItem label="Java" icon="seti:java">

```java
import io.daytona.sdk.Daytona;
import io.daytona.sdk.Image;

public class App {
    public static void main(String[] args) {
        try (Daytona daytona = new Daytona()) {
            Image image = Image.base("daytonaio/sandbox")
                .runCommands(
                    "curl -fsSL https://mesa.dev/install.sh | sh",
                    "sudo sed -i 's/^#user_allow_other/user_allow_other/' /etc/fuse.conf"
                );

            daytona.snapshot().create("fuse-mesa", image, System.out::println);
        }
    }
}
```

</TabItem>
</Tabs>

#### Launch and mount [#launch-and-mount-7]

Pass `MESA_API_KEY` and your Mesa organization slug to the sandbox via `envVars`. Your code then writes a TOML config into the sandbox, authenticates the Mesa CLI, and mounts your repos at `/home/daytona/mesa/mnt/<org>/<repo>`.

<Tabs syncKey="language">
<TabItem label="Python" icon="seti:python">

```python
import os
from daytona import CreateSandboxFromSnapshotParams, Daytona

daytona = Daytona()

org = os.environ["MESA_ORG"]
repo = "my-workspace"
mount_path = f"/home/daytona/mesa/mnt/{org}/{repo}"
config_path = "/home/daytona/.config/mesa/config.toml"

sandbox = daytona.create(
    CreateSandboxFromSnapshotParams(
        snapshot="fuse-mesa",
        env_vars={
            "MESA_API_KEY": os.environ["MESA_API_KEY"],
            "MESA_ORG": org,
        },
    )
)

config = f'''mount-point = "/home/daytona/mesa/mnt"

[secrets]
backend = "plaintext-file"

[organizations.{org}]
'''

sandbox.process.exec(f"mkdir -p $(dirname {config_path})")
sandbox.fs.upload_file(config.encode(), config_path)

sandbox.process.exec("mesa auth set-key --org $MESA_ORG $MESA_API_KEY")
sandbox.process.exec("mesa mount --daemonize")

response = sandbox.process.exec(f"ls {mount_path}")
print(response.result)
```

</TabItem>
<TabItem label="TypeScript" icon="seti:typescript">

```typescript
import { Daytona } from '@daytona/sdk'

const daytona = new Daytona()

const org = process.env.MESA_ORG!
const repo = 'my-workspace'
const mountPath = `/home/daytona/mesa/mnt/${org}/${repo}`
const configPath = '/home/daytona/.config/mesa/config.toml'

const sandbox = await daytona.create({
  snapshot: 'fuse-mesa',
  envVars: {
    MESA_API_KEY: process.env.MESA_API_KEY!,
    MESA_ORG: org,
  },
})

const config = `mount-point = "/home/daytona/mesa/mnt"

[secrets]
backend = "plaintext-file"

[organizations.${org}]
`

await sandbox.process.executeCommand(`mkdir -p $(dirname ${configPath})`)
await sandbox.fs.uploadFile(Buffer.from(config), configPath)

await sandbox.process.executeCommand('mesa auth set-key --org $MESA_ORG $MESA_API_KEY')
await sandbox.process.executeCommand('mesa mount --daemonize')

const response = await sandbox.process.executeCommand(`ls ${mountPath}`)
console.log(response.result)
```

</TabItem>
<TabItem label="Ruby" icon="seti:ruby">

```ruby
require 'daytona'

daytona = Daytona::Daytona.new

org = ENV.fetch('MESA_ORG')
repo = 'my-workspace'
mount_path = "/home/daytona/mesa/mnt/#{org}/#{repo}"
config_path = '/home/daytona/.config/mesa/config.toml'

sandbox = daytona.create(
  Daytona::CreateSandboxFromSnapshotParams.new(
    snapshot: 'fuse-mesa',
    env_vars: {
      'MESA_API_KEY' => ENV.fetch('MESA_API_KEY'),
      'MESA_ORG' => org
    }
  )
)

config = <<~TOML
  mount-point = "/home/daytona/mesa/mnt"

  [secrets]
  backend = "plaintext-file"

  [organizations.#{org}]
TOML

sandbox.process.exec(command: "mkdir -p $(dirname #{config_path})")
sandbox.fs.upload_file(config, config_path)

sandbox.process.exec(command: 'mesa auth set-key --org $MESA_ORG $MESA_API_KEY')
sandbox.process.exec(command: 'mesa mount --daemonize')

response = sandbox.process.exec(command: "ls #{mount_path}")
puts response.result
```

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

```go
import (
	"context"
	"fmt"
	"log"
	"os"

	"github.com/daytona/clients/sdk-go/pkg/daytona"
	"github.com/daytona/clients/sdk-go/pkg/types"
)

ctx := context.Background()
client, err := daytona.NewClient()
if err != nil {
	log.Fatal(err)
}

org := os.Getenv("MESA_ORG")
repo := "my-workspace"
mountPath := fmt.Sprintf("/home/daytona/mesa/mnt/%s/%s", org, repo)
configPath := "/home/daytona/.config/mesa/config.toml"

sandbox, err := client.Create(ctx, types.SnapshotParams{
	Snapshot: "fuse-mesa",
	SandboxBaseParams: types.SandboxBaseParams{
		EnvVars: map[string]string{
			"MESA_API_KEY": os.Getenv("MESA_API_KEY"),
			"MESA_ORG":     org,
		},
	},
})
if err != nil {
	log.Fatal(err)
}

config := fmt.Sprintf(`mount-point = "/home/daytona/mesa/mnt"

[secrets]
backend = "plaintext-file"

[organizations.%s]
`, org)

if _, err := sandbox.Process.ExecuteCommand(ctx, "mkdir -p $(dirname "+configPath+")"); err != nil {
	log.Fatal(err)
}
if err := sandbox.FileSystem.UploadFile(ctx, []byte(config), configPath); err != nil {
	log.Fatal(err)
}

if _, err := sandbox.Process.ExecuteCommand(ctx, "mesa auth set-key --org $MESA_ORG $MESA_API_KEY"); err != nil {
	log.Fatal(err)
}
if _, err := sandbox.Process.ExecuteCommand(ctx, "mesa mount --daemonize"); err != nil {
	log.Fatal(err)
}

response, err := sandbox.Process.ExecuteCommand(ctx, "ls "+mountPath)
if err != nil {
	log.Fatal(err)
}
fmt.Println(response.Result)
```

</TabItem>
<TabItem label="Java" icon="seti:java">

```java
import io.daytona.sdk.Daytona;
import io.daytona.sdk.Sandbox;
import io.daytona.sdk.model.CreateSandboxFromSnapshotParams;
import io.daytona.sdk.model.ExecuteResponse;

import java.nio.charset.StandardCharsets;
import java.util.Map;

public class App {
    public static void main(String[] args) {
        try (Daytona daytona = new Daytona()) {
            String org = System.getenv("MESA_ORG");
            String repo = "my-workspace";
            String mountPath = "/home/daytona/mesa/mnt/" + org + "/" + repo;
            String configPath = "/home/daytona/.config/mesa/config.toml";

            CreateSandboxFromSnapshotParams params = new CreateSandboxFromSnapshotParams();
            params.setSnapshot("fuse-mesa");
            params.setEnvVars(Map.of(
                "MESA_API_KEY", System.getenv("MESA_API_KEY"),
                "MESA_ORG", org
            ));
            Sandbox sandbox = daytona.create(params);

            String config = "mount-point = \"/home/daytona/mesa/mnt\"\n\n"
                + "[secrets]\n"
                + "backend = \"plaintext-file\"\n\n"
                + "[organizations." + org + "]\n";

            sandbox.getProcess().executeCommand("mkdir -p $(dirname " + configPath + ")");
            sandbox.fs.uploadFile(config.getBytes(StandardCharsets.UTF_8), configPath);

            sandbox.getProcess().executeCommand(
                "mesa auth set-key --org $MESA_ORG $MESA_API_KEY");
            sandbox.getProcess().executeCommand("mesa mount --daemonize");

            ExecuteResponse response = sandbox.getProcess().executeCommand("ls " + mountPath);
            System.out.println(response.getResult());
        }
    }
}
```

</TabItem>
</Tabs>

### Runtime install [#runtime-install-8]

Start from a default sandbox and install the Mesa CLI during startup before configuring auth and running `mesa mount --daemonize`. This is useful when iterating quickly on mount behavior, with the tradeoff of slower cold starts for each sandbox.

<Tabs syncKey="language">
<TabItem label="Python" icon="seti:python">

```python
import os
from daytona import CreateSandboxBaseParams, Daytona

daytona = Daytona()

org = os.environ["MESA_ORG"]
repo = "my-workspace"
mount_path = f"/home/daytona/mesa/mnt/{org}/{repo}"
config_path = "/home/daytona/.config/mesa/config.toml"

sandbox = daytona.create(
    CreateSandboxBaseParams(
        env_vars={
            "MESA_API_KEY": os.environ["MESA_API_KEY"],
            "MESA_ORG": org,
        },
    )
)

sandbox.process.exec("curl -fsSL https://mesa.dev/install.sh | sh")
sandbox.process.exec(
    "sudo sed -i 's/^#user_allow_other/user_allow_other/' /etc/fuse.conf"
)

config = f'''mount-point = "/home/daytona/mesa/mnt"

[secrets]
backend = "plaintext-file"

[organizations.{org}]
'''

sandbox.process.exec(f"mkdir -p $(dirname {config_path})")
sandbox.fs.upload_file(config.encode(), config_path)

sandbox.process.exec("mesa auth set-key --org $MESA_ORG $MESA_API_KEY")
sandbox.process.exec("mesa mount --daemonize")

response = sandbox.process.exec(f"ls {mount_path}")
print(response.result)
```

</TabItem>
<TabItem label="TypeScript" icon="seti:typescript">

```typescript
import { Daytona } from '@daytona/sdk'

const daytona = new Daytona()

const org = process.env.MESA_ORG!
const repo = 'my-workspace'
const mountPath = `/home/daytona/mesa/mnt/${org}/${repo}`
const configPath = '/home/daytona/.config/mesa/config.toml'

const sandbox = await daytona.create({
  envVars: {
    MESA_API_KEY: process.env.MESA_API_KEY!,
    MESA_ORG: org,
  },
})

await sandbox.process.executeCommand('curl -fsSL https://mesa.dev/install.sh | sh')
await sandbox.process.executeCommand(
  "sudo sed -i 's/^#user_allow_other/user_allow_other/' /etc/fuse.conf",
)

const config = `mount-point = "/home/daytona/mesa/mnt"

[secrets]
backend = "plaintext-file"

[organizations.${org}]
`

await sandbox.process.executeCommand(`mkdir -p $(dirname ${configPath})`)
await sandbox.fs.uploadFile(Buffer.from(config), configPath)

await sandbox.process.executeCommand('mesa auth set-key --org $MESA_ORG $MESA_API_KEY')
await sandbox.process.executeCommand('mesa mount --daemonize')

const response = await sandbox.process.executeCommand(`ls ${mountPath}`)
console.log(response.result)
```

</TabItem>
<TabItem label="Ruby" icon="seti:ruby">

```ruby
require 'daytona'

daytona = Daytona::Daytona.new

org = ENV.fetch('MESA_ORG')
repo = 'my-workspace'
mount_path = "/home/daytona/mesa/mnt/#{org}/#{repo}"
config_path = '/home/daytona/.config/mesa/config.toml'

sandbox = daytona.create(
  Daytona::CreateSandboxBaseParams.new(
    env_vars: {
      'MESA_API_KEY' => ENV.fetch('MESA_API_KEY'),
      'MESA_ORG' => org
    }
  )
)

sandbox.process.exec(command: 'curl -fsSL https://mesa.dev/install.sh | sh')
sandbox.process.exec(
  command: "sudo sed -i 's/^#user_allow_other/user_allow_other/' /etc/fuse.conf"
)

config = <<~TOML
  mount-point = "/home/daytona/mesa/mnt"

  [secrets]
  backend = "plaintext-file"

  [organizations.#{org}]
TOML

sandbox.process.exec(command: "mkdir -p $(dirname #{config_path})")
sandbox.fs.upload_file(config, config_path)

sandbox.process.exec(command: 'mesa auth set-key --org $MESA_ORG $MESA_API_KEY')
sandbox.process.exec(command: 'mesa mount --daemonize')

response = sandbox.process.exec(command: "ls #{mount_path}")
puts response.result
```

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

```go
import (
	"context"
	"fmt"
	"log"
	"os"

	"github.com/daytona/clients/sdk-go/pkg/daytona"
	"github.com/daytona/clients/sdk-go/pkg/types"
)

ctx := context.Background()
client, err := daytona.NewClient()
if err != nil {
	log.Fatal(err)
}

org := os.Getenv("MESA_ORG")
repo := "my-workspace"
mountPath := fmt.Sprintf("/home/daytona/mesa/mnt/%s/%s", org, repo)
configPath := "/home/daytona/.config/mesa/config.toml"

sandbox, err := client.Create(ctx, types.SnapshotParams{
	SandboxBaseParams: types.SandboxBaseParams{
		EnvVars: map[string]string{
			"MESA_API_KEY": os.Getenv("MESA_API_KEY"),
			"MESA_ORG":     org,
		},
	},
})
if err != nil {
	log.Fatal(err)
}

if _, err := sandbox.Process.ExecuteCommand(ctx,
	"curl -fsSL https://mesa.dev/install.sh | sh"); err != nil {
	log.Fatal(err)
}
if _, err := sandbox.Process.ExecuteCommand(ctx,
	`sudo sed -i 's/^#user_allow_other/user_allow_other/' /etc/fuse.conf`); err != nil {
	log.Fatal(err)
}

config := fmt.Sprintf(`mount-point = "/home/daytona/mesa/mnt"

[secrets]
backend = "plaintext-file"

[organizations.%s]
`, org)

if _, err := sandbox.Process.ExecuteCommand(ctx, "mkdir -p $(dirname "+configPath+")"); err != nil {
	log.Fatal(err)
}
if err := sandbox.FileSystem.UploadFile(ctx, []byte(config), configPath); err != nil {
	log.Fatal(err)
}

if _, err := sandbox.Process.ExecuteCommand(ctx, "mesa auth set-key --org $MESA_ORG $MESA_API_KEY"); err != nil {
	log.Fatal(err)
}
if _, err := sandbox.Process.ExecuteCommand(ctx, "mesa mount --daemonize"); err != nil {
	log.Fatal(err)
}

response, err := sandbox.Process.ExecuteCommand(ctx, "ls "+mountPath)
if err != nil {
	log.Fatal(err)
}
fmt.Println(response.Result)
```

</TabItem>
<TabItem label="Java" icon="seti:java">

```java
import io.daytona.sdk.Daytona;
import io.daytona.sdk.Sandbox;
import io.daytona.sdk.model.CreateSandboxFromSnapshotParams;
import io.daytona.sdk.model.ExecuteResponse;

import java.nio.charset.StandardCharsets;
import java.util.Map;

public class App {
    public static void main(String[] args) {
        try (Daytona daytona = new Daytona()) {
            String org = System.getenv("MESA_ORG");
            String repo = "my-workspace";
            String mountPath = "/home/daytona/mesa/mnt/" + org + "/" + repo;
            String configPath = "/home/daytona/.config/mesa/config.toml";

            CreateSandboxFromSnapshotParams params = new CreateSandboxFromSnapshotParams();
            params.setEnvVars(Map.of(
                "MESA_API_KEY", System.getenv("MESA_API_KEY"),
                "MESA_ORG", org
            ));
            Sandbox sandbox = daytona.create(params);

            sandbox.getProcess().executeCommand(
                "curl -fsSL https://mesa.dev/install.sh | sh");
            sandbox.getProcess().executeCommand(
                "sudo sed -i 's/^#user_allow_other/user_allow_other/' /etc/fuse.conf");

            String config = "mount-point = \"/home/daytona/mesa/mnt\"\n\n"
                + "[secrets]\n"
                + "backend = \"plaintext-file\"\n\n"
                + "[organizations." + org + "]\n";

            sandbox.getProcess().executeCommand("mkdir -p $(dirname " + configPath + ")");
            sandbox.fs.uploadFile(config.getBytes(StandardCharsets.UTF_8), configPath);

            sandbox.getProcess().executeCommand(
                "mesa auth set-key --org $MESA_ORG $MESA_API_KEY");
            sandbox.getProcess().executeCommand("mesa mount --daemonize");

            ExecuteResponse response = sandbox.getProcess().executeCommand("ls " + mountPath);
            System.out.println(response.getResult());
        }
    }
}
```

</TabItem>
</Tabs>

### Production: scoped ephemeral keys [#production-scoped-ephemeral-keys]

For non-test workloads, Mesa recommends minting a **short-lived, scoped API key per sandbox session** rather than passing your long-lived `MESA_API_KEY` into the sandbox. Use the [Mesa SDK ↗](https://docs.mesa.dev) on your trusted host to derive an ephemeral key from your long-lived one — the long-lived key never leaves your host process. Mesa SDKs are available for TypeScript, Python, and Rust; for other languages, use the [Mesa REST API ↗](https://docs.mesa.dev/content/api-reference/overview) directly.

<Tabs syncKey="language">
<TabItem label="Python" icon="seti:python">

```python
import asyncio
import os
from daytona import CreateSandboxFromSnapshotParams, Daytona
from mesa_sdk import Mesa


async def mint_ephemeral_key() -> str:
    async with Mesa(api_key=os.environ["MESA_API_KEY"], org=os.environ["MESA_ORG"]) as mesa:
        key = await mesa.api_keys.create(
            name="sandbox-session",
            scopes=["read", "write"],
            expires_in_seconds=3600,
        )
        return key.key


ephemeral_key = asyncio.run(mint_ephemeral_key())

daytona = Daytona()
sandbox = daytona.create(
    CreateSandboxFromSnapshotParams(
        snapshot="fuse-mesa",
        env_vars={
            "MESA_API_KEY": ephemeral_key,
            "MESA_ORG": os.environ["MESA_ORG"],
        },
    )
)
```

</TabItem>
<TabItem label="TypeScript" icon="seti:typescript">

```typescript
import { Daytona } from '@daytona/sdk'
import { Mesa } from '@mesadev/sdk'

const mesa = new Mesa({ apiKey: process.env.MESA_API_KEY!, org: process.env.MESA_ORG! })

const ephemeralKey = await mesa.apiKeys.create({
  name: 'sandbox-session',
  scopes: ['read', 'write'],
  expires_in_seconds: 3600,
})

const daytona = new Daytona()
const sandbox = await daytona.create({
  snapshot: 'fuse-mesa',
  envVars: {
    MESA_API_KEY: ephemeralKey.key,
    MESA_ORG: process.env.MESA_ORG!,
  },
})
```

</TabItem>
</Tabs>

The rest of the launch flow (writing the TOML config, `mesa auth set-key`, `mesa mount --daemonize`) is unchanged — the sandbox doesn't know whether the key it received is long-lived or ephemeral.

For repo-scoped or path-scoped keys, see Mesa's [auth and permissions guide ↗](https://docs.mesa.dev/content/getting-started/auth-and-permissions). For the full integration recipe, see Mesa's [Daytona guide ↗](https://docs.mesa.dev/content/integration-guides/daytona).

## Mount a TiDB Cloud Filesystem [#mount-a-tidb-cloud-filesystem]

[TiDB Cloud Filesystem ↗](https://www.pingcap.com/tidb/tidb-cloud-filesystem/) is a serverless, POSIX-compatible filesystem from PingCAP, built for AI agent workloads. Mount the same filesystem in multiple sandboxes, or reuse it across sandbox runs: files persisted to TiDB Cloud Filesystem remain available after a sandbox is deleted.

TiDB Cloud Filesystem is in public preview.

The setup uses the TiDB Cloud CLI (`ti`) rather than a FUSE-specific tool: install `ti` in your sandbox and run `ti fs mount-file-system` to mount the filesystem at `/home/daytona/tidb-fs`. The `daytonaio/sandbox` base image already includes FUSE, so `ti` is the only tool to install.

The mount caches writes locally and uploads them in the background. Run `ti fs drain-file-system` to persist pending writes before another sandbox reads them or before you [delete the sandbox](#clean-up).

**Credentials** — create the filesystem once on your local machine or backend with your TiDB Cloud API key. Create the key in the [TiDB Cloud console ↗](https://tidbcloud.com/) with the **Organization Owner** role; the console shows a public key and a private key. Creating the filesystem returns a filesystem token (`TI_FS_TOKEN`), and `TI_REGION_CODE` is the region you create it in. The snippets below pass both values into the sandbox via `envVars`, and `ti` reads them from there. Keep the API keys on the machine that creates the filesystem.

```bash
curl -fsSL https://github.com/tidbcloud/ti-cli/releases/latest/download/install.sh | sh -s -- --yes
export PATH="$HOME/.ti/bin:$PATH"

export TIDB_CLOUD_PUBLIC_KEY="your-public-key"
export TIDB_CLOUD_PRIVATE_KEY="your-private-key"
export TI_REGION_CODE="aws-us-west-2"

ti configure --non-interactive --region-code "$TI_REGION_CODE"
export TI_FS_TOKEN="$(ti fs create-file-system \
  --display-name daytona-workspace \
  --region "$TI_REGION_CODE" \
  --wait \
  --query fs_token \
  --output text)"
```

The filesystem token grants owner access to the filesystem. For agent workloads, pass a [scoped token](#scoped-tokens-and-read-only-mounts) instead. See the [`ti` CLI repository ↗](https://github.com/tidbcloud/ti-cli) for supported filesystem regions.

### Pre-built snapshot [#pre-built-snapshot-9]

Build a snapshot with `ti` preinstalled, then launch sandboxes from that snapshot. You still mount at runtime, but installation is no longer part of each sandbox startup sequence.

#### Build a snapshot [#build-a-snapshot-8]

Create a reusable snapshot that installs `ti` to `/home/daytona/.ti/bin`. Sandboxes launched from `fuse-tidbfs` can mount the filesystem without repeating install work.

<Tabs syncKey="language">
<TabItem label="Python" icon="seti:python">

```python
from daytona import CreateSnapshotParams, Daytona, Image

daytona = Daytona()

image = (
  Image.base("daytonaio/sandbox")
  .run_commands(
    "curl -fsSL https://github.com/tidbcloud/ti-cli/releases/latest/download/install.sh | sh -s -- --yes",
    "/home/daytona/.ti/bin/ti --version",
  )
)

daytona.snapshot.create(
    CreateSnapshotParams(name="fuse-tidbfs", image=image),
    on_logs=lambda chunk: print(chunk, end="", flush=True),
)
```

</TabItem>
<TabItem label="TypeScript" icon="seti:typescript">

```typescript
import { Daytona, Image } from '@daytona/sdk'

const daytona = new Daytona()

const image = Image.base('daytonaio/sandbox').runCommands(
  'curl -fsSL https://github.com/tidbcloud/ti-cli/releases/latest/download/install.sh | sh -s -- --yes',
  '/home/daytona/.ti/bin/ti --version',
)

await daytona.snapshot.create(
  { name: 'fuse-tidbfs', image },
  { onLogs: console.log },
)
```

</TabItem>
<TabItem label="Ruby" icon="seti:ruby">

```ruby
require 'daytona'

daytona = Daytona::Daytona.new

image = Daytona::Image
  .base('daytonaio/sandbox')
  .run_commands(
    'curl -fsSL https://github.com/tidbcloud/ti-cli/releases/latest/download/install.sh | sh -s -- --yes',
    '/home/daytona/.ti/bin/ti --version'
  )

daytona.snapshot.create(
  Daytona::CreateSnapshotParams.new(name: 'fuse-tidbfs', image: image),
  on_logs: proc { |chunk| print(chunk) }
)
```

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

```go
import (
	"context"
	"fmt"
	"log"

	"github.com/daytona/clients/sdk-go/pkg/daytona"
	"github.com/daytona/clients/sdk-go/pkg/types"
)

ctx := context.Background()
client, err := daytona.NewClient()
if err != nil {
	log.Fatal(err)
}

image := daytona.Base("daytonaio/sandbox").
	Run("curl -fsSL https://github.com/tidbcloud/ti-cli/releases/latest/download/install.sh | sh -s -- --yes").
	Run("/home/daytona/.ti/bin/ti --version")

_, logChan, err := client.Snapshot.Create(ctx, &types.CreateSnapshotParams{
	Name:  "fuse-tidbfs",
	Image: image,
})
if err != nil {
	log.Fatal(err)
}
for line := range logChan {
	fmt.Print(line)
}
```

</TabItem>
<TabItem label="Java" icon="seti:java">

```java
import io.daytona.sdk.Daytona;
import io.daytona.sdk.Image;

public class App {
    public static void main(String[] args) {
        try (Daytona daytona = new Daytona()) {
            Image image = Image.base("daytonaio/sandbox")
                .runCommands(
                    "curl -fsSL https://github.com/tidbcloud/ti-cli/releases/latest/download/install.sh | sh -s -- --yes",
                    "/home/daytona/.ti/bin/ti --version"
                );

            daytona.snapshot().create("fuse-tidbfs", image, System.out::println);
        }
    }
}
```

</TabItem>
</Tabs>

#### Launch and mount [#launch-and-mount-8]

Pass `TI_FS_TOKEN` and `TI_REGION_CODE` to the sandbox via `envVars`. Your code then mounts the filesystem at `/home/daytona/tidb-fs`, writes a file, and drains pending writes so the file is persisted to TiDB Cloud Filesystem.

<Tabs syncKey="language">
<TabItem label="Python" icon="seti:python">

```python
import os
from daytona import CreateSandboxFromSnapshotParams, Daytona

daytona = Daytona()

ti = "/home/daytona/.ti/bin/ti"
mount_path = "/home/daytona/tidb-fs"

sandbox = daytona.create(
    CreateSandboxFromSnapshotParams(
        snapshot="fuse-tidbfs",
        env_vars={
            "TI_FS_TOKEN": os.environ["TI_FS_TOKEN"],
            "TI_REGION_CODE": os.environ["TI_REGION_CODE"],
        },
    )
)

sandbox.process.exec(f"mkdir -p {mount_path}")
sandbox.process.exec(f"{ti} fs mount-file-system --mount-path {mount_path} --driver fuse")

# Write through the mount, then persist pending writes
sandbox.process.exec(f"echo 'hello from Daytona' > {mount_path}/hello.txt")
sandbox.process.exec(f"{ti} fs drain-file-system --mount-path {mount_path} --timeout 60s")

response = sandbox.process.exec(f"cat {mount_path}/hello.txt")
print(response.result)
```

</TabItem>
<TabItem label="TypeScript" icon="seti:typescript">

```typescript
import { Daytona } from '@daytona/sdk'

const daytona = new Daytona()

const ti = '/home/daytona/.ti/bin/ti'
const mountPath = '/home/daytona/tidb-fs'

const sandbox = await daytona.create({
  snapshot: 'fuse-tidbfs',
  envVars: {
    TI_FS_TOKEN: process.env.TI_FS_TOKEN!,
    TI_REGION_CODE: process.env.TI_REGION_CODE!,
  },
})

await sandbox.process.executeCommand(`mkdir -p ${mountPath}`)
await sandbox.process.executeCommand(`${ti} fs mount-file-system --mount-path ${mountPath} --driver fuse`)

// Write through the mount, then persist pending writes
await sandbox.process.executeCommand(`echo 'hello from Daytona' > ${mountPath}/hello.txt`)
await sandbox.process.executeCommand(`${ti} fs drain-file-system --mount-path ${mountPath} --timeout 60s`)

const response = await sandbox.process.executeCommand(`cat ${mountPath}/hello.txt`)
console.log(response.result)
```

</TabItem>
<TabItem label="Ruby" icon="seti:ruby">

```ruby
require 'daytona'

daytona = Daytona::Daytona.new

ti = '/home/daytona/.ti/bin/ti'
mount_path = '/home/daytona/tidb-fs'

sandbox = daytona.create(
  Daytona::CreateSandboxFromSnapshotParams.new(
    snapshot: 'fuse-tidbfs',
    env_vars: {
      'TI_FS_TOKEN' => ENV.fetch('TI_FS_TOKEN'),
      'TI_REGION_CODE' => ENV.fetch('TI_REGION_CODE')
    }
  )
)

sandbox.process.exec(command: "mkdir -p #{mount_path}")
sandbox.process.exec(command: "#{ti} fs mount-file-system --mount-path #{mount_path} --driver fuse")

# Write through the mount, then persist pending writes
sandbox.process.exec(command: "echo 'hello from Daytona' > #{mount_path}/hello.txt")
sandbox.process.exec(command: "#{ti} fs drain-file-system --mount-path #{mount_path} --timeout 60s")

response = sandbox.process.exec(command: "cat #{mount_path}/hello.txt")
puts response.result
```

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

```go
import (
	"context"
	"fmt"
	"log"
	"os"

	"github.com/daytona/clients/sdk-go/pkg/daytona"
	"github.com/daytona/clients/sdk-go/pkg/types"
)

ctx := context.Background()
client, err := daytona.NewClient()
if err != nil {
	log.Fatal(err)
}

ti := "/home/daytona/.ti/bin/ti"
mountPath := "/home/daytona/tidb-fs"

sandbox, err := client.Create(ctx, types.SnapshotParams{
	Snapshot: "fuse-tidbfs",
	SandboxBaseParams: types.SandboxBaseParams{
		EnvVars: map[string]string{
			"TI_FS_TOKEN":    os.Getenv("TI_FS_TOKEN"),
			"TI_REGION_CODE": os.Getenv("TI_REGION_CODE"),
		},
	},
})
if err != nil {
	log.Fatal(err)
}

if _, err := sandbox.Process.ExecuteCommand(ctx, "mkdir -p "+mountPath); err != nil {
	log.Fatal(err)
}
if _, err := sandbox.Process.ExecuteCommand(ctx,
	ti+" fs mount-file-system --mount-path "+mountPath+" --driver fuse"); err != nil {
	log.Fatal(err)
}

// Write through the mount, then persist pending writes
if _, err := sandbox.Process.ExecuteCommand(ctx,
	"echo 'hello from Daytona' > "+mountPath+"/hello.txt"); err != nil {
	log.Fatal(err)
}
if _, err := sandbox.Process.ExecuteCommand(ctx,
	ti+" fs drain-file-system --mount-path "+mountPath+" --timeout 60s"); err != nil {
	log.Fatal(err)
}

response, err := sandbox.Process.ExecuteCommand(ctx, "cat "+mountPath+"/hello.txt")
if err != nil {
	log.Fatal(err)
}
fmt.Println(response.Result)
```

</TabItem>
<TabItem label="Java" icon="seti:java">

```java
import io.daytona.sdk.Daytona;
import io.daytona.sdk.Sandbox;
import io.daytona.sdk.model.CreateSandboxFromSnapshotParams;
import io.daytona.sdk.model.ExecuteResponse;

import java.util.Map;

public class App {
    public static void main(String[] args) {
        try (Daytona daytona = new Daytona()) {
            String ti = "/home/daytona/.ti/bin/ti";
            String mountPath = "/home/daytona/tidb-fs";

            CreateSandboxFromSnapshotParams params = new CreateSandboxFromSnapshotParams();
            params.setSnapshot("fuse-tidbfs");
            params.setEnvVars(Map.of(
                "TI_FS_TOKEN", System.getenv("TI_FS_TOKEN"),
                "TI_REGION_CODE", System.getenv("TI_REGION_CODE")
            ));
            Sandbox sandbox = daytona.create(params);

            sandbox.getProcess().executeCommand("mkdir -p " + mountPath);
            sandbox.getProcess().executeCommand(
                ti + " fs mount-file-system --mount-path " + mountPath + " --driver fuse");

            // Write through the mount, then persist pending writes
            sandbox.getProcess().executeCommand(
                "echo 'hello from Daytona' > " + mountPath + "/hello.txt");
            sandbox.getProcess().executeCommand(
                ti + " fs drain-file-system --mount-path " + mountPath + " --timeout 60s");

            ExecuteResponse response = sandbox.getProcess().executeCommand("cat " + mountPath + "/hello.txt");
            System.out.println(response.getResult());
        }
    }
}
```

</TabItem>
</Tabs>

### Runtime install [#runtime-install-9]

Start from a default sandbox and install `ti` during startup before mounting the filesystem. This is useful when iterating quickly on mount behavior, with the tradeoff of slower cold starts for each sandbox.

<Tabs syncKey="language">
<TabItem label="Python" icon="seti:python">

```python
import os
from daytona import CreateSandboxBaseParams, Daytona

daytona = Daytona()

ti = "/home/daytona/.ti/bin/ti"
mount_path = "/home/daytona/tidb-fs"

sandbox = daytona.create(
    CreateSandboxBaseParams(
        env_vars={
            "TI_FS_TOKEN": os.environ["TI_FS_TOKEN"],
            "TI_REGION_CODE": os.environ["TI_REGION_CODE"],
        },
    )
)

sandbox.process.exec(
    "curl -fsSL https://github.com/tidbcloud/ti-cli/releases/latest/download/install.sh | sh -s -- --yes"
)

sandbox.process.exec(f"mkdir -p {mount_path}")
sandbox.process.exec(f"{ti} fs mount-file-system --mount-path {mount_path} --driver fuse")

response = sandbox.process.exec(f"ls -la {mount_path}")
print(response.result)
```

</TabItem>
<TabItem label="TypeScript" icon="seti:typescript">

```typescript
import { Daytona } from '@daytona/sdk'

const daytona = new Daytona()

const ti = '/home/daytona/.ti/bin/ti'
const mountPath = '/home/daytona/tidb-fs'

const sandbox = await daytona.create({
  envVars: {
    TI_FS_TOKEN: process.env.TI_FS_TOKEN!,
    TI_REGION_CODE: process.env.TI_REGION_CODE!,
  },
})

await sandbox.process.executeCommand(
  'curl -fsSL https://github.com/tidbcloud/ti-cli/releases/latest/download/install.sh | sh -s -- --yes',
)

await sandbox.process.executeCommand(`mkdir -p ${mountPath}`)
await sandbox.process.executeCommand(`${ti} fs mount-file-system --mount-path ${mountPath} --driver fuse`)

const response = await sandbox.process.executeCommand(`ls -la ${mountPath}`)
console.log(response.result)
```

</TabItem>
<TabItem label="Ruby" icon="seti:ruby">

```ruby
require 'daytona'

daytona = Daytona::Daytona.new

ti = '/home/daytona/.ti/bin/ti'
mount_path = '/home/daytona/tidb-fs'

sandbox = daytona.create(
  Daytona::CreateSandboxBaseParams.new(
    env_vars: {
      'TI_FS_TOKEN' => ENV.fetch('TI_FS_TOKEN'),
      'TI_REGION_CODE' => ENV.fetch('TI_REGION_CODE')
    }
  )
)

sandbox.process.exec(
  command: 'curl -fsSL https://github.com/tidbcloud/ti-cli/releases/latest/download/install.sh | sh -s -- --yes'
)

sandbox.process.exec(command: "mkdir -p #{mount_path}")
sandbox.process.exec(command: "#{ti} fs mount-file-system --mount-path #{mount_path} --driver fuse")

response = sandbox.process.exec(command: "ls -la #{mount_path}")
puts response.result
```

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

```go
import (
	"context"
	"fmt"
	"log"
	"os"

	"github.com/daytona/clients/sdk-go/pkg/daytona"
	"github.com/daytona/clients/sdk-go/pkg/types"
)

ctx := context.Background()
client, err := daytona.NewClient()
if err != nil {
	log.Fatal(err)
}

ti := "/home/daytona/.ti/bin/ti"
mountPath := "/home/daytona/tidb-fs"

sandbox, err := client.Create(ctx, types.SnapshotParams{
	SandboxBaseParams: types.SandboxBaseParams{
		EnvVars: map[string]string{
			"TI_FS_TOKEN":    os.Getenv("TI_FS_TOKEN"),
			"TI_REGION_CODE": os.Getenv("TI_REGION_CODE"),
		},
	},
})
if err != nil {
	log.Fatal(err)
}

if _, err := sandbox.Process.ExecuteCommand(ctx,
	"curl -fsSL https://github.com/tidbcloud/ti-cli/releases/latest/download/install.sh | sh -s -- --yes"); err != nil {
	log.Fatal(err)
}

if _, err := sandbox.Process.ExecuteCommand(ctx, "mkdir -p "+mountPath); err != nil {
	log.Fatal(err)
}
if _, err := sandbox.Process.ExecuteCommand(ctx,
	ti+" fs mount-file-system --mount-path "+mountPath+" --driver fuse"); err != nil {
	log.Fatal(err)
}

response, err := sandbox.Process.ExecuteCommand(ctx, "ls -la "+mountPath)
if err != nil {
	log.Fatal(err)
}
fmt.Println(response.Result)
```

</TabItem>
<TabItem label="Java" icon="seti:java">

```java
import io.daytona.sdk.Daytona;
import io.daytona.sdk.Sandbox;
import io.daytona.sdk.model.CreateSandboxFromSnapshotParams;
import io.daytona.sdk.model.ExecuteResponse;

import java.util.Map;

public class App {
    public static void main(String[] args) {
        try (Daytona daytona = new Daytona()) {
            String ti = "/home/daytona/.ti/bin/ti";
            String mountPath = "/home/daytona/tidb-fs";

            CreateSandboxFromSnapshotParams params = new CreateSandboxFromSnapshotParams();
            params.setEnvVars(Map.of(
                "TI_FS_TOKEN", System.getenv("TI_FS_TOKEN"),
                "TI_REGION_CODE", System.getenv("TI_REGION_CODE")
            ));
            Sandbox sandbox = daytona.create(params);

            sandbox.getProcess().executeCommand(
                "curl -fsSL https://github.com/tidbcloud/ti-cli/releases/latest/download/install.sh | sh -s -- --yes");

            sandbox.getProcess().executeCommand("mkdir -p " + mountPath);
            sandbox.getProcess().executeCommand(
                ti + " fs mount-file-system --mount-path " + mountPath + " --driver fuse");

            ExecuteResponse response = sandbox.getProcess().executeCommand("ls -la " + mountPath);
            System.out.println(response.getResult());
        }
    }
}
```

</TabItem>
</Tabs>

### Share a filesystem across sandboxes [#share-a-filesystem-across-sandboxes]

Launch another sandbox with the same `TI_FS_TOKEN` and `TI_REGION_CODE`, then run the same mount commands. Files that the first sandbox drained are available in the new mount, even after the first sandbox is deleted.

Sharing a filesystem does not make overlapping writes transactional. Give each agent its own output paths, and stop writers and drain pending writes before handing files to another sandbox.

### Scoped tokens and read-only mounts [#scoped-tokens-and-read-only-mounts]

For agent sessions, generate a short-lived token on your trusted host with `ti fs generate-file-system-scoped-token`, and pass it to the sandbox as `TI_FS_TOKEN` instead of the owner token. Each `--allow` option grants operations (`read`, `list`, `search`, `write`, `delete`) on a path prefix, and `--ttl` sets the token lifetime. Choose a lifetime that covers the workload and the final drain.

```bash
export TI_FS_TOKEN="$(ti fs generate-file-system-scoped-token \
  --allow /shared:read,list \
  --ttl 1h \
  --query fs_token \
  --output text)"
```

A token scoped to a subtree can only mount that subtree. Pass the same path as `--remote-path`, and add `--read-only` when the token does not grant `write`:

```bash
/home/daytona/.ti/bin/ti fs mount-file-system \
  --mount-path /home/daytona/tidb-fs \
  --remote-path /shared \
  --driver fuse \
  --read-only
```

Without `--read-only`, the mount accepts writes into the local cache before the service rejects them, and the drain fails.

### Clean up [#clean-up]

Stop all processes that write to the mount, drain pending writes, then delete the sandbox. Deleting the sandbox removes the mount and stops its background processes. The snippets below continue from the launch example and only delete the sandbox if the drain succeeds.

<Tabs syncKey="language">
<TabItem label="Python" icon="seti:python">

```python
response = sandbox.process.exec(f"{ti} fs drain-file-system --mount-path {mount_path} --timeout 60s")
if response.exit_code != 0:
    raise RuntimeError(response.result)

daytona.delete(sandbox)
```

</TabItem>
<TabItem label="TypeScript" icon="seti:typescript">

```typescript
const drain = await sandbox.process.executeCommand(`${ti} fs drain-file-system --mount-path ${mountPath} --timeout 60s`)
if (drain.exitCode !== 0) {
  throw new Error(drain.result)
}

await daytona.delete(sandbox)
```

</TabItem>
<TabItem label="Ruby" icon="seti:ruby">

```ruby
drain = sandbox.process.exec(command: "#{ti} fs drain-file-system --mount-path #{mount_path} --timeout 60s")
raise drain.result unless drain.exit_code.zero?

daytona.delete(sandbox)
```

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

```go
drain, err := sandbox.Process.ExecuteCommand(ctx,
	ti+" fs drain-file-system --mount-path "+mountPath+" --timeout 60s")
if err != nil {
	log.Fatal(err)
}
if drain.ExitCode != 0 {
	log.Fatal(drain.Result)
}

if err := sandbox.Delete(ctx); err != nil {
	log.Fatal(err)
}
```

</TabItem>
<TabItem label="Java" icon="seti:java">

```java
ExecuteResponse drain = sandbox.getProcess().executeCommand(
    ti + " fs drain-file-system --mount-path " + mountPath + " --timeout 60s");
if (drain.getExitCode() != 0) {
    throw new IllegalStateException(drain.getResult());
}

sandbox.delete();
```

</TabItem>
</Tabs>

If the drain fails or times out, keep the sandbox running, resolve the error, and retry. Deleting the sandbox discards writes that have not been drained, including when the sandbox is deleted automatically.

### Common pitfalls [#common-pitfalls-1]

- **Mount fails after a successful install.** A successful install does not verify access to the filesystem. Run `/home/daytona/.ti/bin/ti fs list-files --path /` in the sandbox with the same token and region (for a scoped token, use an allowed path) and resolve connection or authorization errors before mounting.
- **Region mismatch.** `TI_REGION_CODE` must match the region the filesystem was created in.
- **Connection errors.** For `aws-us-west-2`, the filesystem endpoint is `https://aws-us-west-2.drive9.ai`. If a request fails before receiving an HTTP response, check the sandbox's [network limits](/docs/network-limits).
- **Permission errors.** Mount as the same user that runs your agent, and check that the token allows the requested path and operations. A read-only mount still requires a token with `read` access.

## Unmount [#unmount]

When a sandbox is deleted via `daytona.delete(sandbox)`, the container teardown automatically removes any active FUSE mounts and shuts down their daemons. For normal cleanup, this is all you need — no manual unmount required.

To free a mount path **during** a sandbox's lifetime (for example, to remount with different credentials or before persisting a workspace archive), relocate the mount onto a throwaway path:

```bash
sudo mkdir -p /tmp/.fuse-defunct-$$
sudo mount --move <your-mount-path> /tmp/.fuse-defunct-$$
```

After this, your original mount path is free for remounting. The FUSE daemon stays alive serving the mount at the new path; both the relocated mount and the daemon are cleaned up automatically when the sandbox is deleted.

This works for any FUSE-based mount — verified against `mount-s3`, `gcsfuse`, and `blobfuse2`.
