# How to mount an S3 bucket in a sandbox Store the bucket's key as a Runtime secret, then call `sbx.mounts.add({ provider: "s3", bucket, path, secret })`. **On Runtime the bucket's key stays outside the sandbox.** You store the key once as a secret. The mount client inside the sandbox signs its requests with a placeholder, and Runtime's egress proxy signs each one again with the real key as it leaves for the bucket's endpoint, so code in the sandbox, or a prompt injection steering it, has nothing worth stealing. Amazon S3, Cloudflare R2, Google Cloud Storage and any S3-compatible store work, and a public bucket needs no key at all ([mount your own bucket](/docs/storage#mount-your-own-bucket)). ## Store the key and mount the bucket ```bash no-run printf %s "$AWS_ACCESS_KEY_ID:$AWS_SECRET_ACCESS_KEY" | runtime secrets set DATA_BUCKET \ --host s3.eu-west-1.amazonaws.com --header Authorization --format 'AWS4-HMAC-SHA256 {value}' runtime sandbox mount "${id}" s3://my-data/datasets /data --secret DATA_BUCKET --region eu-west-1 runtime sandbox mounts "${id}" runtime sandbox unmount "${id}" /data ``` ```ts check import { Runtime } from "withruntime"; const runtime = new Runtime(); await runtime.secrets.set("DATA_BUCKET", { value: `${process.env.AWS_ACCESS_KEY_ID}:${process.env.AWS_SECRET_ACCESS_KEY}`, hosts: ["s3.eu-west-1.amazonaws.com"], header: "Authorization", format: "AWS4-HMAC-SHA256 {value}", }); await using sbx = await runtime.sandboxes.create(); await sbx.mounts.add({ provider: "s3", bucket: "my-data", prefix: "datasets", region: "eu-west-1", path: "/data", secret: "DATA_BUCKET", }); console.log((await sbx.exec("ls -la /data")).stdout); await sbx.exec("echo 'a,b' > /data/result.csv", { check: true }); // reaches the bucket on close ``` ```python check import os from withruntime import Runtime runtime = Runtime() runtime.secrets.set( "DATA_BUCKET", value=f"{os.environ['AWS_ACCESS_KEY_ID']}:{os.environ['AWS_SECRET_ACCESS_KEY']}", hosts=["s3.eu-west-1.amazonaws.com"], header="Authorization", format="AWS4-HMAC-SHA256 {value}", ) with runtime.sandboxes.create() as sbx: sbx.mounts.add(provider="s3", bucket="my-data", prefix="datasets", region="eu-west-1", path="/data", secret="DATA_BUCKET") print(sbx.exec("ls -la /data").stdout) ``` The secret must match exactly: the value `ACCESS_KEY_ID:SECRET_ACCESS_KEY` (add `:SESSION_TOKEN` for temporary keys), the header `Authorization`, the format `AWS4-HMAC-SHA256 {value}`, and the bucket's endpoint as its host. ## R2, Google Cloud Storage and other stores | Provider | Mount with | The secret's host | | -------------------- | --------------------------------------------------- | --------------------------------------- | | Amazon S3 | `provider: "s3"`, `region` (us-east-1 when omitted) | `s3..amazonaws.com` | | Cloudflare R2 | `provider: "r2"`, `accountId` | `.r2.cloudflarestorage.com` | | Google Cloud Storage | `provider: "gcs"`, with an HMAC key | `storage.googleapis.com` | | Any S3-compatible | `provider: "s3"`, `endpoint` | Your store's own host | | A public bucket | Any of the above, no `secret` | None | ```ts check import { Sandbox } from "withruntime"; await using sbx = await Sandbox.create(); await sbx.mounts.add({ provider: "r2", accountId: "", bucket: "models", path: "/models", secret: "R2_MODELS", readOnly: true, }); console.log(await sbx.mounts.list()); ``` `prefix` mounts only the keys under it, and `readOnly` refuses writes, which suits a shared dataset that jobs must not change. ## How the mount behaves | Question | Answer | | ------------------------------- | ----------------------------------------------------------------------------- | | What runs it | rclone, installed in the sandbox the first time you mount, a few seconds more | | When a write reaches the bucket | When the file is closed | | Two sandboxes, one bucket | Both can mount it; a file both write ends as the last one closed | | Pause and wake | The mount lasts through both | | Stop and restart | The mount ends at a stop; mount it again after a restart | | Network | HTTPS to the endpoint, through the sandbox's own rules, like any other host | | Where the key lives | Sealed on Runtime's servers; no API, tool or command returns it | ## A mount, a volume or the AWS SDK? - **A mount** fits data that already lives in a bucket: datasets, model files, results other systems read. - **A [volume](/how-to/back-up-a-volume)** is a disk that outlives sandboxes, on the sandbox's own server, for data a program writes and rereads many times, such as a package cache or a database file. - **The AWS SDK with an identity token** fits code that calls S3's API itself, for listing, presigned URLs or other AWS services, with no key at all ([use identity tokens with AWS](/how-to/use-identity-tokens-with-aws)). ## Mistakes and how Runtime handles them - **The secret's host does not match the endpoint.** The proxy adds the real key only on HTTPS requests to the secret's hosts. For S3 that is the regional host, `s3..amazonaws.com`. - **Pasting the key into an environment variable.** Then it lives in the sandbox. With the secret, a sandbox that prints its whole environment shows a worthless placeholder. - **Writing a file and reading it from elsewhere at once.** It reaches the bucket when it is closed; close it, or finish the program, first. - **An allow list that leaves the bucket out.** The bucket is reached through the sandbox's network rules. Allow its endpoint host when you narrow them ([turn off sandbox internet](/how-to/turn-off-sandbox-internet)). - **An MCP agent.** The same mounts are the `runtime_sandbox_mount`, `runtime_sandbox_mounts` and `runtime_sandbox_unmount` tools. ## Start ```bash no-run npx withruntime sandbox run --trial --keep -- ls /workspace ``` New accounts get 50 free sandbox hours, no card. Then mount a bucket into the sandbox it kept with `runtime sandbox mount`. Facts on this page were checked on 25 September 2026.