Skip to main content

Virtual Filesystem

Isolates hosted on Deno Deploy Subhosting have a virtual read-only file system. The contents of this file system is controlled entirely by the subhoster, through origin RPC endpoints.

The APIs to interact with this file system are nearly identical to the ones in the Deno CLI. One notable difference is that synchronous file system operations are not available in Deno Deploy Subhosting.

Because the file system is read-only, all write operations fail with a Deno.errors.PermissionDenied error. This behavior can be emulated in Deno CLI by specifying the --allow-read permission flag, but denying all write permissions.

While all of the file system functions available in the Deno CLI are available in Deno Deploy Subhosting, only these ones are really useful:

  • Deno.FsFile: The Deno abstraction for reading and writing files.
    • The readSync() and writeSync() methods always throw on use.
    • The write() method always throws a Deno.errors.PermissionDenied error.
  • Deno.readDir: Read a directory listing.
  • Deno.readFile: Read a file into memory.
  • Deno.readTextFile: Read a text file into memory as text.
  • Deno.open: Open a file handle for streaming reads.
  • Deno.cwd: Get the current working directory.
  • Deno.stat: Get the file info.
  • Deno.lstat: Get the file info without following symlinks.
  • Deno.realPath: Get the canonicalized path of a file.
  • Deno.realPath - Get the target path for the given symlink

Manifest

The virtual file system is backed by a manifest that describes the file tree of the virtual file system. This is the “fs manifest”. The manifest contains two pieces of information: the file tree, and the metadata for each file. The file contents are not part of the manifest and are retrieved separately.

The manifest has the following structure:

interface FSManifest {
  /** The file system entries contained in this manifest, stored as a flat map.
   *
   * The keys of this map are the paths of the files in the manifest. The values
   * are the metadata for each file. For each entry located within a parent
   * directory, that parent directory must exist as an entry too. This also
   * applies to the root directory.
   */
  entries: Record<string, Entry>;
}

type Entry = EntryFile | EntryDirectory | EntrySymlink;

interface EntryFile {
  kind: "file";
  /** The size of the file in bytes. */
  size: number;
  /** The file hash (or other unique identifier) for the file. This is used as
   * an identifier to retrieve file contents when the file is opened. */
  hash: string;
}

interface EntryDirectory {
  kind: "directory";
}

interface EntrySymlink {
  kind: "symlink";
  /** The target of the symlink. */
  target: string;
}

To illustrate, an example manifest:

{
  "entries": {
    "": {
      "kind": "directory"
    },
    "/hello.txt": {
      "kind": "file",
      "size": 5,
      "hash": "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"
    },
    "/goodbye.txt": {
      "kind": "file",
      "size": 7,
      "hash": "82e35a63ceba37e9646434c5dd412ea577147f1e4a41ccde1614253187e3dbf9"
    },
    "/foo": {
      "kind": "directory"
    },
    "/foo/bar.txt": {
      "kind": "file",
      "size": 3,
      "hash": "fcde2b2edba56bf408601fb721fe9b5c338d10ee429ea04fae5511b68fbf8fb9"
    },
    "/bar": {
      "kind": "symlink",
      "target": "./foo"
    }
  }
}

Integration

To make use of the virtual file system feature, two origin RPC endpoints must be implemented by you. One endpoint is used to retrieve the fs manifest, while the other is used to fetch file contents for a given blob.

The GET /fs/manifest origin RPC endpoint is called with the deployment ID as a parameter. The manifest is cached for each deployment, so not ever fs operation performed by a user will trigger this RPC call.

The GET /fs/blob origin RPC endpoint is called with the hash of the file to be loaded. The origin should stream the response body to minimize memory usage and caching. To support Deno.seek() and similar functions, the origin must support Range headers for the /fs/blob endpoint.

Blobs are cached, so multiple reads of the same file in an isolate will not necessarily trigger multiple /fs/blob RPC calls.

Limits

  • A manifest must not contain more than 65,536 entries.
  • Blob size must not exceed 4 GiB.
  • Blob hashes must not exceed 512 bytes.
  • Blob hashes must be globally unique per subhoster.
  • File paths may not exceed 4096 bytes.
  • Symlink targets may not exceed 4096 bytes.