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