[Feature Request] Read files from a volume directly via API without starting a sandbox
Problem / Motivation
Currently, files in persistent volumes can only be read through a running sandbox. The /volumes REST endpoints (GET/POST /volumes, GET/DELETE /volumes/{volumeID}) only manage volume lifecycle; the SDK's file operations (Files.Read/Stat/List) are all methods on Sandbox, and requests must be sent to envd inside the microVM.
Therefore, even when the platform only wants to retrieve one file from a volume, it must first create or resume a sandbox, attach the volume, read the file through envd, and finally release the sandbox. Common scenarios include:
- Agent platform: the sandbox has already been reclaimed, but the user still needs to download files that were stored in the session
/workspacevolume; - Knowledge base or file management UI: displaying and previewing files stored in a user-level volume;
- Backend tasks: batch-reading volume contents for indexing, export, auditing, etc.
Our current workaround is to spin up a dedicated temporary sandbox, attach the volume, read the file, and then destroy it. This brings the following problems:
- Latency: every download incurs a sandbox cold start, even if the file is very small;
- Resources: scheduling a microVM for a single HTTP download, and taking up concurrency quota reserved for real workloads;
- Complexity: callers must handle sandbox acquisition, mount compatibility checks, idle timeouts, and failure cleanup, when all they actually need is to read a file.
The data already lives in the volume backend (e.g., volumes/<volumeID>/ in the S3 plugin); reading it should not depend on a virtual machine.
Proposed Solution
Add read-only file access at the volume layer, provided by CubeAPI/CubeMaster through the volume plugin, without starting a sandbox at all.
REST API (CubeAPI)
| Method and path | Description |
|---|---|
GET /volumes/{volumeID}/files?path=<relative path> |
Stream file contents (with Content-Type, Content-Length, and Range support if feasible) |
HEAD /volumes/{volumeID}/files?path=<relative path> or GET .../files/stat |
Return file metadata: size, mode, mtime, whether it is a directory |
GET /volumes/{volumeID}/dir?path=<relative path> (optional) |
List directory entries, with the same structure as envd's FileEntry |
pathis relative to the volume root; the server is responsible for normalization and rejects absolute paths,..traversal, and access escaping the volume;- Authentication is the same as other
/volumesendpoints (API Key); - It works whether the volume is currently mounted or not (
refCountequal to 0 or greater than 0); - Error codes: volume or file not found returns 404 (
ErrVolumeNotFound, or a newErrVolumeFileNotFound); invalid path returns 400; returns 501 when the volume's driver does not support direct reads.
Volume Plugin Interface
Add a set of optional Controller-side hooks, such as ReadFile / StatFile (and ListDir), implemented by drivers that can access the backend without mounting:
s3/cos: performGetObject/HeadObject/ListObjectsV2against thevolumes/<volumeID>/prefix; if objects carryx-amz-meta-mode/mtime, read them as well, ensuring results match what is seen in s3fs;- Drivers that do not implement these hooks return 501; existing third-party plugins do not need changes;
- Alternative approach: the driver returns a short-lived presigned URL, and CubeAPI responds with a redirect, so large files do not have to pass through the control plane.
SDK
// Go
rc, entry, err := client.ReadVolumeFile(ctx, "my-data", "reports/output.pdf")
entry, err := client.StatVolumeFile(ctx, "my-data", "reports/output.pdf")
entries, err := client.ListVolumeDir(ctx, "my-data", "reports")# Python
data = Volume.connect("my-data").files.read("reports/output.pdf")Consistency Notes (to be documented)
While a volume is mounted, content that has not yet been fully written inside the sandbox may not be readable; for example, s3fs uploads only on close(). The documentation needs to state that the API returns the data currently committed in the backend.
Alternatives Considered
- Spin up a temporary sandbox for every read (current approach): works with any driver, but pays the full cold-start latency every time and consumes VM resources for a single file read. This is exactly the overhead this issue aims to eliminate.
- Keep a sandbox resident for each volume: latency can be hidden, but a virtual machine idles long-term just for occasional downloads, and mount compatibility and timeout reclamation still need to be handled.
- Have applications read backend storage directly (e.g., performing S3
GetObjectagainstvolumes/<volumeID>/): fast, but applications would depend on the internal layout of a specific plugin, which is not a public contract; they would also need to hold backend credentials outside CubeSandbox, bypassing CubeAPI's authentication and path validation, and would break if the driver or layout changes. - Use
VolumeInfo.Tokenas a data-plane credential: the token is plugin-defined and optional (the S3 plugin returns an empty value), and currently no data-plane interface accepts it, so the API above would still be needed.
By contrast, this proposal keeps the storage layout behind the plugin abstraction, reuses existing API authentication, allows each driver to opt in as needed, and gives all SDKs a unified, supported way to read volume files.
Source: TencentCloud/CubeSandbox