Freestyle Docs

Freestyle / Docs

Files

Read and write files inside a VM — from a one-line config file to a 16 GiB model, over the same API.

Every VM exposes its guest filesystem at vm.fs. Reads and writes carry raw bytes, so there is nothing to encode and no size limit short of 16 GiB per file — the same call that writes a config file uploads a model checkpoint.

Read And Write

await vm.fs.writeFile("/etc/app.conf", "debug = true\n");

const config = await vm.fs.readTextFile("/etc/app.conf");
const bytes = await vm.fs.readFile("/usr/share/logo.png");

writeFile takes a string, a Uint8Array, or a Blob. Writes are atomic: the file is verified before it replaces the target, so a reader inside the VM sees either the old file or the new one — never a half-written one. Integrity is checked with a sha256 the client computes and the guest re-verifies, so a corrupted transfer fails instead of landing.

Large Files

Nothing special is required for a big file. Pass a Blob and the SDK reads it a chunk at a time, so the file is never held in memory whole:

import { openAsBlob } from "node:fs";

await vm.fs.writeFile("/data/model.bin", await openAsBlob("model.bin"), {
  onProgress: ({ completedBytes, totalBytes }) => {
    process.stdout.write(`\r${Math.floor((completedBytes / totalBytes) * 100)}%`);
  },
});

Above 8 MiB the SDK switches transports on its own: instead of one request, the file goes as a sequence of chunks, each retried independently. A dropped connection costs one chunk rather than the whole upload, which is what makes a multi-gigabyte transfer over an ordinary internet link finish. The file still appears at its path atomically, at the end.

Streaming Reads

readFile buffers; readFileStream does not. Use it to pipe a large file somewhere without holding it in memory:

import { createWriteStream } from "node:fs";
import { Readable } from "node:stream";
import { pipeline } from "node:stream/promises";

const bytes = await vm.fs.readFileStream("/var/log/build.log");
await pipeline(Readable.fromWeb(bytes), createWriteStream("build.log"));

Reads are also ranged, which is how you tail a log or resume an interrupted download:

const { size } = await vm.fs.stat("/var/log/build.log");
const tail = await vm.fs.readTextFile("/var/log/build.log", {
  offset: Math.max(0, size - 4096),
});

Directories

await vm.fs.mkdir("/data/checkpoints");
const entries = await vm.fs.readDir("/data");
const info = await vm.fs.stat("/data/model.bin");
const there = await vm.fs.exists("/data/model.bin");
await vm.fs.remove("/data/checkpoints");

mkdir creates parents. remove deletes a file or a directory tree.

From The CLI

freestyle vm fs write <vmId> /data/model.bin ./model.bin
freestyle vm fs read <vmId> /var/log/build.log --out build.log
freestyle vm scp ./model.bin <vmId>:/data/model.bin
freestyle vm fs ls <vmId> /data
esc