> ## Documentation Index
> Fetch the complete documentation index at: https://docs.sailresearch.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Filesystem

> Read, write, and stream files in a running Sailbox

Each Sailbox has a writable state disk. Runtime filesystem APIs operate on that
writable disk; checkpoints preserve it across pause, sleep, resume, cloned
children, and recovery.

The examples below assume a running Sailbox `sb`.

## Write files

Upload bytes or strings into the Sailbox filesystem. Paths must be absolute.
Missing parent directories are created by default.

<CodeGroup>
  ```python Python theme={null}
  sb.fs.write("/workspace/input.txt", "hello\n")
  ```

  ```typescript TypeScript theme={null}
  await sb.fs.write("/workspace/input.txt", "hello\n");
  ```

  ```rust Rust theme={null}
  use sail::WriteOptions;

  sb.fs().write(
      "/workspace/input.txt",
      b"hello\n",
      WriteOptions {
          create_parents: true,
          mode: None,
      },
  )
  .await?;
  ```
</CodeGroup>

Pass a mode to set POSIX permission bits. When omitted, writes default to
`0o644`.

<CodeGroup>
  ```python Python theme={null}
  sb.fs.write("/workspace/private.txt", "secret\n", mode=0o600)
  ```

  ```typescript TypeScript theme={null}
  await sb.fs.write("/workspace/private.txt", "secret\n", { mode: 0o600 });
  ```

  ```rust Rust theme={null}
  use sail::WriteOptions;

  sb.fs().write(
      "/workspace/private.txt",
      b"secret\n",
      WriteOptions {
          create_parents: true,
          mode: Some(0o600),
      },
  )
  .await?;
  ```
</CodeGroup>

Disable parent creation if you want writes to fail when parent directories are
missing:

<CodeGroup>
  ```python Python theme={null}
  sb.fs.write("/workspace/data/input.txt", "hello\n", create_parents=False)
  ```

  ```typescript TypeScript theme={null}
  await sb.fs.write("/workspace/data/input.txt", "hello\n", {
    createParents: false,
  });
  ```

  ```rust Rust theme={null}
  use sail::WriteOptions;

  sb.fs().write(
      "/workspace/data/input.txt",
      b"hello\n",
      WriteOptions {
          create_parents: false,
          mode: None,
      },
  )
  .await?;
  ```
</CodeGroup>

## Read files

Fetch a regular file back as bytes:

<CodeGroup>
  ```python Python theme={null}
  data = sb.fs.read("/workspace/input.txt")
  print(data.decode())
  ```

  ```typescript TypeScript theme={null}
  const data = await sb.fs.read("/workspace/input.txt");
  console.log(data.toString());
  ```

  ```rust Rust theme={null}
  let data = sb.fs().read("/workspace/input.txt").await?;
  println!("{}", String::from_utf8_lossy(&data));
  ```
</CodeGroup>

Whole-file reads buffer the full file in memory. For larger files, stream
chunks instead:

<CodeGroup>
  ```python Python theme={null}
  with open("output.bin", "wb") as out:
      for chunk in sb.fs.read_stream("/workspace/output.bin"):
          out.write(chunk)
  ```

  ```typescript TypeScript theme={null}
  import { createWriteStream } from "node:fs";
  import { pipeline } from "node:stream/promises";

  await pipeline(
    (await sb.fs.readStream("/workspace/output.bin")).toReadable(),
    createWriteStream("output.bin"),
  );
  ```

  ```rust Rust theme={null}
  use std::io::Write;

  let mut out = std::fs::File::create("output.bin")?;
  let reader = sb.fs().read_stream("/workspace/output.bin").await?;
  while let Some(chunk) = reader.next().await {
      out.write_all(&chunk?)?;
  }
  ```
</CodeGroup>

## Work with directories

The `fs` namespace also covers directory work. `mkdir` creates a directory and
any missing parents, `ls` lists a directory's immediate entries as structured
records (name, type, size, modified time, mode), `exists` checks a path, and
`remove` deletes a file or directory tree:

<CodeGroup>
  ```python Python theme={null}
  sb.fs.mkdir("/workspace/results")
  sb.fs.write("/workspace/results/input.txt", "hello\n")
  for entry in sb.fs.ls("/workspace/results"):
      print(entry.name, entry.type, entry.size)
  if sb.fs.exists("/workspace/results/input.txt"):
      sb.fs.remove("/workspace/results/input.txt")
  ```

  ```typescript TypeScript theme={null}
  await sb.fs.mkdir("/workspace/results");
  await sb.fs.write("/workspace/results/input.txt", "hello\n");
  for (const entry of await sb.fs.ls("/workspace/results")) {
    console.log(entry.name, entry.type, entry.size);
  }
  if (await sb.fs.exists("/workspace/results/input.txt")) {
    await sb.fs.remove("/workspace/results/input.txt");
  }
  ```

  ```rust Rust theme={null}
  sb.fs().mkdir("/workspace/results").await?;
  sb.fs()
      .write("/workspace/results/input.txt", b"hello\n", Default::default())
      .await?;
  for entry in sb.fs().ls("/workspace/results").await? {
      println!("{} {:?} {}", entry.name, entry.entry_type, entry.size);
  }
  if sb.fs().exists("/workspace/results/input.txt").await? {
      sb.fs().remove("/workspace/results/input.txt").await?;
  }
  ```
</CodeGroup>

Archives and shell-native workflows still go through `exec()`. For many small
files, create an archive locally, upload it, and unpack it inside
the Sailbox:

<CodeGroup>
  ```python Python theme={null}
  sb.fs.write("/workspace/project.tar.gz", open("project.tar.gz", "rb"))
  sb.exec("mkdir -p /workspace/project && tar -xzf /workspace/project.tar.gz -C /workspace/project").wait()
  ```

  ```typescript TypeScript theme={null}
  import { readFileSync } from "node:fs";

  await sb.fs.write("/workspace/project.tar.gz", readFileSync("project.tar.gz"));
  const untar = await sb.exec(
    "mkdir -p /workspace/project && tar -xzf /workspace/project.tar.gz -C /workspace/project",
  );
  await untar.wait();
  ```

  ```rust Rust theme={null}
  use sail::{ExecOptions, WriteOptions};

  let bytes = std::fs::read("project.tar.gz")?;
  sb.fs().write(
      "/workspace/project.tar.gz",
      &bytes,
      WriteOptions {
          create_parents: true,
          mode: None,
      },
  )
  .await?;
  let untar = sb.exec(vec![
          "sh".to_string(),
          "-lc".to_string(),
          "mkdir -p /workspace/project && tar -xzf /workspace/project.tar.gz -C /workspace/project".to_string(),
      ],
      ExecOptions::default(),
  )
  .await?;
  untar.wait().await?;
  ```
</CodeGroup>

## Persist state with checkpoints

Runtime writes live on the Sailbox state disk. Checkpoint after important
writes if you want recovery and future resumes to start from that point:

<CodeGroup>
  ```python Python theme={null}
  sb.fs.write("/workspace/config.json", '{"ready": true}\n')
  sb.checkpoint()
  ```

  ```typescript TypeScript theme={null}
  await sb.fs.write("/workspace/config.json", '{"ready": true}\n');
  await sb.checkpoint();
  ```

  ```rust Rust theme={null}
  use sail::CheckpointOptions;

  use sail::WriteOptions;

  sb.fs().write(
      "/workspace/config.json",
      b"{\"ready\": true}\n",
      WriteOptions {
          create_parents: true,
          mode: None,
      },
  )
  .await?;
  sb.checkpoint(CheckpointOptions::default()).await?;
  ```
</CodeGroup>

See [Lifecycle](/sailboxes-lifecycle) for checkpoint, start-from-checkpoint,
pause, sleep, and resume behavior.

## Runtime files vs image files

Use runtime filesystem APIs for inputs, outputs, logs, generated artifacts, and
data that changes per Sailbox. Use [Images](/sailboxes-images) for packages,
source files, and static assets that should be present before the VM boots.
