> For the complete documentation index, see [llms.txt](https://mercure-technologies.gitbook.io/xprem/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://mercure-technologies.gitbook.io/xprem/storage/s3-storage.md).

# S3 Storage

Use Amazon S3 or an S3-compatible provider to store update assets.

### Configure Amazon S3

Set the bucket and AWS Region:

```dotenv
STORAGE_MODE=s3
AWS_REGION=your-region
S3_BUCKET_NAME=your-bucket-name
```

### Configure S3-compatible storage

For Cloudflare R2, MinIO, DigitalOcean Spaces, or [Railway storage buckets](https://docs.railway.com/storage-buckets), set the provider endpoint:

```dotenv
STORAGE_MODE=s3
AWS_REGION=auto
AWS_BASE_ENDPOINT=https://account-id.r2.cloudflarestorage.com
S3_BUCKET_NAME=your-bucket-name
```

{% hint style="info" %}
If the provider requires path-style addressing, also set:

<pre class="language-shellscript"><code class="lang-shellscript"><strong>AWS_S3_FORCE_PATH_STYLE=true
</strong></code></pre>

{% endhint %}

### Authentication

If xprem runs inside AWS, use an IAM role rather than long-lived access keys. The AWS SDK resolves credentials automatically from the runtime environment, so you leave the key variables unset:

* **EC2:** attach an instance profile
* **EKS:** use IAM Roles for Service Accounts (IRSA) or EKS Pod Identity

Attach a role granting the bucket permissions listed above and xprem picks it up on startup.

Anywhere else: self-hosted, another cloud provider, or an S3-compatible service you need to provide access keys:

```dotenv
AWS_ACCESS_KEY_ID=your-access-key-id
AWS_SECRET_ACCESS_KEY=your-secret-access-key
```

{% hint style="warning" %}
Use the equivalent identity mechanism for S3-compatible storage. This IAM role configuration applies only to AWS.
{% endhint %}

### Recommended AWS IAM role

Use a dedicated EKS service account named `expo-open-ota-sa`. Attach one of these permission policies to its IAM role.

{% tabs %}
{% tab title="Without BUCKET\_KEY\_PREFIX" %}

```json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "ListBucket",
      "Effect": "Allow",
      "Action": "s3:ListBucket",
      "Resource": "arn:aws:s3:::YOUR_BUCKET"
    },
    {
      "Sid": "ObjectRW",
      "Effect": "Allow",
      "Action": [
        "s3:GetObject",
        "s3:PutObject",
        "s3:DeleteObject"
      ],
      "Resource": "arn:aws:s3:::YOUR_BUCKET/*"
    }
  ]
}
```

Replace `YOUR_BUCKET` with your bucket name.
{% endtab %}

{% tab title="With BUCKET\_KEY\_PREFIX" %}

```json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "ListBucket",
      "Effect": "Allow",
      "Action": "s3:ListBucket",
      "Resource": "arn:aws:s3:::YOUR_BUCKET",
      "Condition": {
        "StringLike": {
          "s3:prefix": "myprefix/*"
        }
      }
    },
    {
      "Sid": "ObjectRW",
      "Effect": "Allow",
      "Action": [
        "s3:GetObject",
        "s3:PutObject",
        "s3:DeleteObject"
      ],
      "Resource": "arn:aws:s3:::YOUR_BUCKET/myprefix/*"
    }
  ]
}
```

Replace `YOUR_BUCKET` and `myprefix` with your bucket and `BUCKET_KEY_PREFIX` value.
{% endtab %}
{% endtabs %}

The role can manage update objects in the configured prefix or entire bucket.

### Asset delivery

When a client requests an update, xprem returns a URL for each asset.\
\
How that URL is produced depends on your configuration.

[**CloudFront**](/xprem/cdn/cloudfront.md)**:** xprem returns CloudFront-signed URLs. Clients fetch from the edge, and the signature keeps your bucket private.

[**Generic CDN**](/xprem/cdn/generic-cdn.md)**:** set `CDN_BASE_URL` to serve assets through any other CDN (like cloudflare). xprem rewrites asset URLs to your CDN hostname, which caches them and reads from your bucket on a miss.

**Direct from storage:** the default when no CDN is configured. xprem returns pre-signed storage URLs valid for 15 minutes. Clients download straight from your bucket, which stays private.

**Through the server:** set `DISABLE_S3_DIRECT_CDN=true` and xprem fetches each object from storage and streams it back itself. Use this when your storage provider doesn't support pre-signed URLs. Every asset byte then transits through your server, so size it accordingly.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://mercure-technologies.gitbook.io/xprem/storage/s3-storage.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
