---
url: https://gha-cache-server.falcondev.io/storage-drivers/s3.md
description: >-
  Store self-hosted GitHub Actions caches in AWS S3, RustFS, MinIO, Cloudflare
  R2 or any S3 compatible storage, with optional direct downloads from the
  bucket.
---

# S3 / RustFS / MinIO / R2

Driver: `s3`

This storage driver stores the cache in a S3 compatible storage, e.g. AWS S3 or RustFS.

## Configuration

### `docker-compose` RustFS example

```yaml [docker-compose.yml]
version: '3.9'

services:
  cache-server:
    image: ghcr.io/falcondev-oss/github-actions-cache-server:latest
    ports:
      - '3000:3000'
    environment:
      API_BASE_URL: http://localhost:3000

      STORAGE_DRIVER: s3
      STORAGE_S3_BUCKET: gh-actions-cache

      AWS_ACCESS_KEY_ID: access_key
      AWS_SECRET_ACCESS_KEY: secret_key
      AWS_ENDPOINT_URL: http://rustfs:9000
    volumes:
      - cache-data:/app/.data

  rustfs:
    image: rustfs/rustfs:latest
    ports:
      - '9000:9000'
      - '9001:9001'
    environment:
      RUSTFS_ACCESS_KEY: access_key
      RUSTFS_SECRET_KEY: secret_key
    volumes:
      - rustfs-data:/data

volumes:
  cache-data:
  rustfs-data:
```

### `docker-compose` AWS S3 example

This example assumes that credentials are being provided by the environment, e.g. via an [instance profile](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_use_switch-role-ec2_instance-profiles.html) or [EKS IRSA](https://docs.aws.amazon.com/eks/latest/userguide/iam-roles-for-service-accounts.html).

```yaml [docker-compose.yml]
version: '3.9'

services:
  cache-server:
    image: ghcr.io/falcondev-oss/github-actions-cache-server:latest
    ports:
      - '3000:3000'
    environment:
      API_BASE_URL: http://localhost:3000

      STORAGE_DRIVER: s3
      STORAGE_S3_BUCKET: gh-actions-cache

    volumes:
      - cache-data:/app/.data

volumes:
  cache-data:
```

### Environment Variables

The only required S3-related environment variables are `STORAGE_DRIVER: s3` and `STORAGE_S3_BUCKET`. The rest of the environment variables are optional and depend on your S3-compatible storage provider.

The server picks up AWS credentials from the environment: `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` (with `AWS_SESSION_TOKEN`), a web identity token (EKS IRSA), ECS task credentials or EC2 instance metadata. Outside of AWS, the access key variables authenticate with S3-compatible storage, as seen in the RustFS example above. Shared credential files and `AWS_PROFILE` are not supported.

Common environment variables are listed below. For a full list, see [`AmazonS3Builder::from_env`](https://docs.rs/object_store/latest/object_store/aws/struct.AmazonS3Builder.html#method.from_env).

#### `STORAGE_S3_BUCKET`

Example: `gh-actions-cache`

The name of the S3 bucket used for storage. This environment variable is always required.

#### `AWS_REGION`

Example: `us-east-1`

Defaults to `us-east-1`. This has no effect if you are using a non-AWS S3-compatible storage provider, such as RustFS.

#### `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY`

Example:
`AWS_ACCESS_KEY_ID: access_key`
`AWS_SECRET_ACCESS_KEY: secret_key`

This is the access key/secret key used to authenticate with S3-compatible storage. If required to authenticate with your provider, these should be provided by the provider.

#### `AWS_ENDPOINT_URL`

Example: `http://rustfs:9000`

This is the endpoint URL for the S3-compatible storage. This is only required if you are using a non-AWS S3-compatible storage provider, such as RustFS.

#### `STORAGE_S3_SOCKET_TIMEOUT_MS`

* Default: `10000`

How long a request to S3 may go without making progress before it fails. `0` disables it.

## Minimal IAM permissions

The cache server only ever touches objects under the `gh-actions-cache/` prefix in the bucket. These are all the actions it performs:

| Action                    | Used for                                             |
| ------------------------- | ---------------------------------------------------- |
| `s3:ListBucket`           | Startup bucket check, listing/counting cache objects |
| `s3:GetObject`            | Downloading cache entries, existence checks          |
| `s3:PutObject`            | Uploading cache entries, merging parts               |
| `s3:AbortMultipartUpload` | Cleaning up failed multipart uploads                 |
| `s3:DeleteObject`         | Cache cleanup and abandoned uploads                  |

A minimal policy, scoped to the bucket and prefix (replace `YOUR_BUCKET`):

```json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "github-actions-cache-server-bucket",
      "Effect": "Allow",
      "Action": ["s3:ListBucket"],
      "Resource": "arn:aws:s3:::YOUR_BUCKET"
    },
    {
      "Sid": "github-actions-cache-server-objects",
      "Effect": "Allow",
      "Action": ["s3:GetObject", "s3:PutObject", "s3:AbortMultipartUpload", "s3:DeleteObject"],
      "Resource": "arn:aws:s3:::YOUR_BUCKET/gh-actions-cache/*"
    }
  ]
}
```

Parts are uploaded through presigned URLs, which the server signs with its own credentials. If they can't sign, uploads fall back to multipart.

## Incomplete multipart uploads

A server killed mid-upload or mid-merge leaves an incomplete multipart upload that S3 keeps and bills. Add a lifecycle rule that aborts them:

```json
{
  "Rules": [
    {
      "ID": "abort-incomplete-uploads",
      "Status": "Enabled",
      "Filter": { "Prefix": "gh-actions-cache/" },
      "AbortIncompleteMultipartUpload": { "DaysAfterInitiation": 1 }
    }
  ]
}
```
