Files
Read, write, list, inspect, move, remove and watch files in a running microVM, including binary content and whole directories.
File operations move data in and out of a running microVM without a command: upload a project before you run it, read a report back after a test run, or check whether a file exists. They are binary safe, so images, archives and compiled output travel unchanged.
Paths and ownership
A path is absolute, such as /tmp/report.json, or relative to the home directory of the image's user, so app/index.js is /root/app/index.js for an image that runs as root. An image whose user has no home directory resolves relative paths against /.
File operations act as the image's user. Files you write are owned by that user, and a path that user cannot read or write answers 403. Use a command with user: 'root' when you need to change something the image's user cannot.
Write and read files
import { readFile } from 'node:fs/promises';
import { MicroVM } from '@gigadrive/sdk';
await using vm = await MicroVM.create({ image: 'python3.13' });
await vm.files.writeMany([
{
path: '/tmp/job/count.py',
content: 'n = open("input.csv").read().count("\\n")\nopen("lines.txt", "w").write(str(n))\n',
},
{ path: '/tmp/job/input.csv', content: await readFile('input.csv') },
]);
await vm.run('python', ['count.py'], { cwd: '/tmp/job' });
const lines = await vm.files.readText('/tmp/job/lines.txt');
const csv = await vm.files.read('/tmp/job/input.csv');write and writeMany take a string, bytes, a Blob or a stream. readText returns a string, read returns the raw bytes, and readStream returns a stream for files too large to hold in memory.
A write replaces the whole file and creates missing parent directories. Readers never see a half-written file: the new content appears all at once when the write completes. Pass mkdirs=false to fail instead when the parent directory is missing, and mode to set permissions such as 0755 for a script. New files get 0644.
There is no fixed upload size limit; a write is bounded by the free space on the microVM's disk and fails with 413 when the disk fills. To read part of a large file, pass offset and length in bytes.
List, inspect and check
const entries = await vm.files.list('/tmp/job', { recursive: true });
for (const entry of entries) {
console.log(entry.type, entry.size, entry.path);
}
const info = await vm.files.stat('/tmp/job/count.py');
const exists = await vm.files.exists('/tmp/job/lines.txt');Over REST, GET /microvms/{id}/files?path=/tmp/job lists a directory as { entries, truncated }, and GET /microvms/{id}/files/stat?path=/tmp/job/count.py describes one path. A listing shows one level unless you pass recursive=true, and depth, from 1 to 64, limits how far a recursive listing goes. Entries are sorted by path, and symbolic links are listed but not followed. A listing stops at 10,000 entries and then sets truncated: true, so list a narrower directory.
Each entry is a FileEntry:
| Field | Value |
|---|---|
path | Absolute path |
name | Last part of the path |
type | file, directory, symlink or other |
size | Size in bytes |
mode | Permissions as an octal string, such as 0644 |
modifiedAt | Last modification time |
target | Where a symbolic link points, else null |
Create, move and remove
await vm.files.mkdir('/tmp/job/archive');
await vm.files.move('/tmp/job/lines.txt', '/tmp/job/archive/lines.txt');
await vm.files.remove('/tmp/job', { recursive: true });| Operation | REST | Notes |
|---|---|---|
| mkdir | POST /microvms/{id}/files/mkdir with { path } | Creates missing parents unless parents is false. An existing directory is fine |
| move | POST /microvms/{id}/files/move with { from, to } | Renames a file or directory. Answers 409 if to exists, unless overwrite: true |
| remove | DELETE /microvms/{id}/files?path=/tmp/job&recursive=true | Answers 204. A directory that is not empty needs recursive |
Watch a directory
Watch a directory to react when something inside the microVM changes files, such as a build writing its output or a server saving an upload. GET /microvms/{id}/files/watch is a Server-Sent Events stream with one event per change:
curl -N \
-H "Authorization: Bearer $GIGADRIVE_TOKEN" \
-H "Accept: text/event-stream" \
"https://api.gigadrive.network/microvms/0199a7c4-5b1e-7c3d-9f20-333333333333/files/watch?path=/tmp/job&recursive=true"The response starts once the watch is in place, so changes made after that are reported. A path that does not exist answers 404 and a file answers 400, before any event is sent. Running python count.py in /tmp/job from the earlier example streams:
event: ready
data: {"path":"/tmp/job","recursive":true}
event: change
data: {"type":"create","path":"/tmp/job/lines.txt","isDirectory":false}
event: change
data: {"type":"write","path":"/tmp/job/lines.txt","isDirectory":false}| Event | Data | Meaning |
|---|---|---|
ready | { path, recursive } | The watch is in place. Always the first event |
change | { type, path, isDirectory } | Something changed at path, an absolute path. type is listed below |
end | { reason: "directory_gone" } | The watched directory was deleted or moved. The stream ends after this event |
error | { retryable: true } | The stream failed partway, for example because the microVM stopped |
type | Meaning |
|---|---|
create | A file or directory was created, or moved into the watched directory |
write | A file's content changed. A file that stays open and keeps changing reports at most one write every 50 milliseconds |
remove | A file or directory was deleted |
rename | A file or directory was moved away from path. Its new path gets a create if it is watched |
chmod | Permissions, ownership or timestamps changed |
overflow | Some changes were not reported. List the directory to catch up |
By default only the entries of the directory itself are watched. With recursive=true, the watch also covers every subdirectory, including the ones created while it runs, up to 8,192 directories. Past that it sends one overflow and leaves the rest unwatched, so watch a narrower directory instead. Symbolic links are not followed. Whatever a new subdirectory already holds when its watch starts is reported as create, and an entry created at that moment can be reported twice.
The watch uses the permissions of the image's user, or of the user you name with user, and skips subdirectories that user cannot read. It runs until you close the connection. Changes made while no watch is open are not replayed: after an error, reconnect and list the directory to catch up. Once the microVM has stopped, a reconnect answers 409 with microvm_not_running, and everything on its disk is gone.
The number of watches and log streams open at once is limited per credential and per organization. Past the limit, the call answers 429 with a retry-after header.
Errors
Every error response is { error, code }. The SDK throws MicroVmFileNotFoundError for a path that does not exist.
| Status | code | When |
|---|---|---|
400 | invalid_request, guest_invalid_argument | A malformed path or option |
400 | guest_is_a_directory, guest_not_a_directory | Reading a directory as a file, watching a file, or a path through a regular file |
403 | guest_permission_denied | The image's user may not read or write the path |
404 | not_found, guest_not_found | The path, or the microVM, does not exist |
409 | guest_already_exists, guest_not_empty | The target already exists, or removing a directory that is not empty |
409 | microvm_not_running | The microVM is not running |
409 | microvm_watch_unsupported | The microVM cannot watch directories. Create a new microVM to watch directories |
413 | guest_too_large | The disk is full, or the microVM has too many watches open |
Everything on the disk is deleted when the microVM stops. Copy out what you want to keep, for example into File Storage, before you stop it.
