# Object storage

> Creating S3-compatible storage buckets and attaching them to apps

# Object storage

> **Preflight - check auth first.** Run `tarout whoami --json`. If it does not
> return `success: true`, authenticate with the table in
> [start.md](https://tarout.sa/docs/for-ai/start.md)
> before running anything on this page - every command below fails without a
> credential, and the failure reads like a broken deploy rather than a missing
> login.

Tarout provides object storage behind an S3-compatible gateway. If the project's
code indicates file uploads, create the bucket as part of the deploy - announce
it, don't ask.

## Create a bucket

```sh
tarout storage create my-bucket
```

Do not pass `--plan`: the server derives the bucket type from the project's
subscription. The free plan includes one 50 MB bucket. On a paid plan, buckets
share the project's committed storage capacity (the `storage.gb` add-on, billed
per GB per month); if none is committed, the create fails and you need
[billing.md](https://tarout.sa/docs/for-ai/billing.md).

## Wire an app to it

Attach the bucket to the app. This creates an access key scoped to the bucket
and app, writes the connection details into the app's environment variables, and
restarts the app if it is already deployed:

```sh
tarout storage attach <bucket> <app-id>
```

(The user can do the same from the bucket's page in the dashboard.) The bucket
and the app must be in the same project. `tarout deploy` already
attaches a bucket it creates for the project, so only run this for a bucket you
created separately. Over MCP there is no dedicated attach tool: use the `call`
tool with `storage.attachToApplication` and `{ "bucketId": "...",
"applicationId": "..." }`.

Do **not** create a dashboard access key and paste it into the app's env vars.
Such keys only work from IP addresses on the bucket's external-access
allow-list, so an app on Tarout using one gets `403 AccessDenied`.

Variables the attach writes (the app's code should read these):

| Variable | Use |
|---|---|
| `STORAGE_ENDPOINT` | S3 endpoint |
| `STORAGE_BUCKET` | Bucket name for S3 requests (not the display name) |
| `STORAGE_REGION` | `auto` |
| `STORAGE_ACCESS_KEY_ID` / `STORAGE_SECRET_ACCESS_KEY` | Credentials |

It also sets `S3_ENDPOINT`, `S3_BUCKET`, `AWS_ACCESS_KEY_ID`,
`AWS_SECRET_ACCESS_KEY`, `AWS_ENDPOINT_URL`, `AWS_REGION`, and
`AWS_S3_FORCE_PATH_STYLE=true`. If the code builds its own S3 client, make sure
it sets path-style addressing (`forcePathStyle: true` in the AWS SDK for
JavaScript).

## Gateway limits

- Single-object `PUT`, `GET`, `HEAD`, and `DELETE`, plus listing a bucket
  (at most the first 1,000 keys, optional prefix).
- No multipart upload and no Range requests. Code that relies on either (for
  example `@aws-sdk/lib-storage` `Upload` for big files) must send each object
  in one `PUT` instead. Large files are better uploaded with
  `tarout storage put <bucket> <key> <file>`, which uses a signed upload URL.

## Access from outside Tarout

Only when the user asks for it: create an access key for the bucket, then enable
external access with the caller's IP allow-list:

```sh
tarout storage external-access <bucket> --enable --cidrs <ip-or-cidr>
```

Without an allow-listed IP, requests with that key are refused.

## Inspect

```sh
tarout storage list
tarout storage info <bucket>
```

## When does a project need storage?

Look for these before deciding:

- Dependencies: `@aws-sdk/client-s3`, `aws-sdk`, `@google-cloud/storage`,
  `firebase-admin`, `uploadthing`, `multer`, `formidable`, `busboy`, `minio`
- Code strings: `S3Client`, `PutObjectCommand`, `S3_BUCKET`, `STORAGE_BUCKET`,
  `FIREBASE_STORAGE_BUCKET`, `multipart/form-data`, `upload.single`,
  `upload.array`

If none of these appear, do not create a bucket.

If the org has no bucket slot or no storage capacity, the command returns
`NEEDS_UPGRADE` or a "Bucket limit reached" / "No object-storage capacity"
error - see [billing.md](https://tarout.sa/docs/for-ai/billing.md).

Storage credentials are secrets. Ask the user before revealing them with
`tarout env <app> reveal <KEY>`.

---

## Every Tarout agent guide

- [Start Here (Agents)](https://tarout.sa/docs/for-ai/start.md)
- [Overview](https://tarout.sa/docs/for-ai.md)
- [Deploying an app](https://tarout.sa/docs/for-ai/deploy.md)
- [Databases](https://tarout.sa/docs/for-ai/database.md)
- [Custom domains](https://tarout.sa/docs/for-ai/domains.md)
- [Plans and upgrades](https://tarout.sa/docs/for-ai/billing.md)
- [Troubleshooting](https://tarout.sa/docs/for-ai/troubleshoot.md)
- [Agent Onboarding](https://tarout.sa/docs/for-ai/onboarding.md)
- [CLI Reference](https://tarout.sa/docs/for-ai/cli-reference.md)
- [CLI JSON Schema](https://tarout.sa/docs/for-ai/cli-json-schema.md)

Whole corpus in one file: https://tarout.sa/llms-full.txt · index: https://tarout.sa/llms.txt
Any docs page is raw markdown at the same URL + `.md`. Short link to the entry point: https://tarout.sa/deploy.md
