---
title: "S3 object storage in France: the while.sh docs"
url: https://while.sh/en/docs/building-blocks/s3/
updated: 2026-11-09
---

# Object storage (S3)

S3 buckets in France for your app's files: images, exports, documents. They're encrypted and versioned, and your app receives a key that works only for them.

> You, to your agent: Add storage for the visit photos.

## What your agent writes

Your agent writes it in while.yaml, at the root of your app:

```yaml titre="while.yaml"
buckets:
  - name: uploads
```

## What your app receives

Your app receives these environment variables at every deployment:

| Variable | What it holds |
|---|---|
| `S3_ENDPOINT` | The S3 endpoint your app reaches. |
| `S3_REGION` | The S3 region. |
| `AWS_REGION` | The same, for the AWS SDKs. |
| `AWS_ENDPOINT_URL_S3` | The S3 endpoint, for the AWS SDKs. |
| `S3_BUCKET_UPLOADS` | The name of the uploads bucket. |
| `S3_BUCKET` | The name of the first bucket. |
| `AWS_ACCESS_KEY_ID` | The access key, limited to the environment's buckets. |
| `AWS_SECRET_ACCESS_KEY` | Its secret. |
| `S3_ACCESS_KEY_ID` | The same key, under another name. |
| `S3_SECRET_ACCESS_KEY` | Its secret. |
| `S3_ENDPOINT_PUBLIC` | The public S3 endpoint, for a presigned URL that a browser opens. |

## When to use it

For files (images, exports, documents), rather than in the database.

## How it works

Each declared S3 bucket is created in France, encrypted and versioned. The app receives the S3 endpoint, the name of its buckets and a key limited to its environment's buckets.

## What to know

An app with closed `egress` (the default) reaches its buckets through while's S3 relay (`S3_ENDPOINT`, in the cluster). Through the relay, the key received is only valid for the environment's buckets, and only from its pods. To read a bucket from a workstation, without a key, your agent uses the `read_bucket` tool.

A presigned URL for a browser is made on `S3_ENDPOINT_PUBLIC`, in path style (forcePathStyle), and the app's addresses are allowed on it (CORS). For another origin, declare `buckets: [{ name, cors: [https://…] }]`. The public address takes presigned URLs to read or send an object (GET, HEAD, PUT, 5 GB at most), and presigned POST forms (createPresignedPost): the relay checks the policy (signature, expiration, bucket, key or its prefix, Content-Type, x-amz-meta-*) and counts content-length-range during the upload. A URL that signs content-length enforces the size: the relay checks the signature with it and passes Content-Length on as is; any other length is refused.

The bucket is created at deployment: it is a billed resource, approved like a database, and `public: true` (anonymous read) requires approval too. A replaced or deleted version of an object is kept 30 days. A preview has its own buckets, empty: nothing is copied. Buckets go with their environment, emptied, versions included. Object locking in compliance mode is not offered.

## Pricing

These prices are the same on every plan, excluding VAT:

| Resource | Price | Details |
|---|---|---|
| Object storage | €0.25 per 10 GiB·month | requests included |

## Your agent's MCP tools

Your agent handles it with the `get_app_status` and `read_bucket` tools.

## Guides

- [Store your app's files in an S3 bucket, from your AI agent](/en/guides/store-files-s3/)
