# S3 Destination (AWS S3 / MinIO / R2)
## Config block
```yaml
destination:
type: s3
bucket: my-data-bucket # S3 bucket name (must already exist)
prefix: exports/daily/ # optional key prefix (folder-like path)
region: us-east-1 # AWS region (required for AWS S3)
```
## Credentials
Rivet uses [OpenDAL](https://opendal.apache.org/) for S3 access. Credentials are resolved in this order:
### Option 1: AWS default credential chain (recommended)
If you're running on EC2, ECS, Lambda, or have `~/.aws/credentials` configured, just set `region`:
```yaml
destination:
type: s3
bucket: my-data-bucket
region: us-east-1
```
### Option 2: Environment variables
Set `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` before running:
```bash
export AWS_ACCESS_KEY_ID=AKIA...
export AWS_SECRET_ACCESS_KEY=wJa...
rivet run --config export.yaml
```
Or reference them in the config:
```yaml
destination:
type: s3
bucket: my-data-bucket
region: us-east-1
access_key_env: AWS_ACCESS_KEY_ID
secret_key_env: AWS_SECRET_ACCESS_KEY
```
### Option 3: AWS profile
```yaml
destination:
type: s3
bucket: my-data-bucket
region: us-east-1
aws_profile: my-profile # uses [my-profile] from ~/.aws/credentials
```
## S3-compatible endpoints (MinIO, R2, Wasabi)
For non-AWS S3-compatible services, add `endpoint`:
```yaml
# MinIO
destination:
type: s3
bucket: rivet-exports
endpoint: "http://localhost:9000"
region: us-east-1 # required but can be any value
access_key_env: MINIO_ACCESS_KEY
secret_key_env: MINIO_SECRET_KEY
# Cloudflare R2
destination:
type: s3
bucket: my-r2-bucket
endpoint: "https://ACCOUNT_ID.r2.cloudflarestorage.com"
region: auto
access_key_env: R2_ACCESS_KEY
secret_key_env: R2_SECRET_KEY
```
## Anonymous access
For public or pre-configured buckets (e.g. MinIO in dev):
```yaml
destination:
type: s3
bucket: public-bucket
endpoint: "http://localhost:9000"
region: us-east-1
allow_anonymous: true
```
## Output keys
Files are uploaded as:
```
s3://{bucket}/{prefix}{export_name}_{YYYYMMDD}_{HHMMSS}.{format}
```
Example: `s3://my-data-bucket/exports/daily/orders_20260406_120000.parquet`
## Streaming upload
Rivet streams data directly to S3 without buffering the entire file in memory. Peak RSS stays proportional to `batch_size`, not to the total export size.
## Verify
```bash
rivet doctor --config export.yaml
```
Output:
```
[OK] Destination 's3://my-data-bucket/exports/daily/' — bucket accessible, write test passed
```
## Troubleshooting
**`NoSuchBucket`** -- The bucket must already exist. Create it first: `aws s3 mb s3://my-data-bucket`.
**`AccessDenied`** -- Check IAM policy. Rivet needs `s3:PutObject` and `s3:GetBucketLocation`.
**`SignatureDoesNotMatch` with MinIO** -- Ensure `region` is set (even for MinIO, e.g. `us-east-1`).