# S3 Files

Source: /aws/services/s3files/

## Introduction

Amazon S3 Files lets you access the objects in a general purpose S3 bucket as a shared file system.
Compute services such as Lambda and ECS mount the file system and read and write files with standard file operations, while S3 Files keeps the file system and the bucket in sync in both directions.
See the [S3 Files documentation](https://docs.aws.amazon.com/AmazonS3/latest/userguide/s3-files.html) for an overview of the service.

LocalStack lets you use the S3 Files APIs in your local environment to create file systems on top of your buckets, manage access points, mount targets, file system policies, and synchronization configurations, and mount file systems into Lambda functions, ECS tasks, and Batch jobs.
The supported APIs are available on the [API coverage section](#api-coverage), which provides information on the extent of S3 Files' integration with LocalStack.

## Getting started

This guide is designed for users new to S3 Files and assumes basic knowledge of the AWS CLI and our [`lstk aws`](/aws/developer-tools/running-localstack/lstk/cloud-and-iac-commands/#aws) command.

Start your LocalStack container using your preferred method.
We will demonstrate how to create a versioned bucket, an IAM role for S3 Files, a file system, a mount target, and an access point.

### Create a versioned bucket

S3 Files requires a general purpose bucket with versioning enabled, in the same region and account as the file system.
Create a bucket:

```bash
lstk aws s3api create-bucket --bucket my-s3files-bucket
```

```bash title="Output"
{
    "Location": "/my-s3files-bucket",
    "BucketArn": "arn:aws:s3:::my-s3files-bucket"
}
```

Enable versioning on the bucket. The command returns no output:

```bash
lstk aws s3api put-bucket-versioning \
    --bucket my-s3files-bucket \
    --versioning-configuration Status=Enabled
```

### Create an IAM role for S3 Files

S3 Files assumes an IAM role to read and write the objects in your bucket.
Create a trust policy that allows S3 Files to assume the role:

```json title="trust-policy.json" showLineNumbers
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": { "Service": "elasticfilesystem.amazonaws.com" },
      "Action": "sts:AssumeRole"
    }
  ]
}
```

Create the role and attach a policy that grants access to the bucket:

```bash
lstk aws iam create-role \
    --role-name s3files-role \
    --assume-role-policy-document file://trust-policy.json
lstk aws iam put-role-policy \
    --role-name s3files-role \
    --policy-name s3files-bucket-access \
    --policy-document '{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Action":["s3:ListBucket","s3:ListBucketVersions","s3:GetObject*","s3:PutObject*","s3:DeleteObject*","s3:AbortMultipartUpload"],"Resource":["arn:aws:s3:::my-s3files-bucket","arn:aws:s3:::my-s3files-bucket/*"]}]}'
```

See [IAM roles for S3 Files](https://docs.aws.amazon.com/AmazonS3/latest/userguide/s3-files-getting-started.html) for the full set of permissions AWS recommends for this role.

### Create a file system

Create a file system on top of the bucket using the [`CreateFileSystem`](https://docs.aws.amazon.com/cli/latest/reference/s3files/create-file-system.html) API:

```bash
lstk aws s3files create-file-system \
    --bucket arn:aws:s3:::my-s3files-bucket \
    --role-arn arn:aws:iam::000000000000:role/s3files-role
```

```bash title="Output"
{
    "creationTime": 1790380487.720941,
    "fileSystemArn": "arn:aws:s3files:us-east-1:000000000000:file-system/fs-c42a80aad0687dc16",
    "fileSystemId": "fs-c42a80aad0687dc16",
    "bucket": "arn:aws:s3:::my-s3files-bucket",
    "prefix": "",
    "clientToken": "02f9bd54-3ad2-4f07-9907-258628bf7244",
    "status": "creating",
    "roleArn": "arn:aws:iam::000000000000:role/s3files-role",
    "ownerId": "000000000000",
    "tags": []
}
```

The file system starts in the `creating` status and moves to `available` once it is ready.
Check its status with the [`GetFileSystem`](https://docs.aws.amazon.com/cli/latest/reference/s3files/get-file-system.html) API:

```bash
lstk aws s3files get-file-system --file-system-id fs-c42a80aad0687dc16 --query status
```

```bash title="Output"
"available"
```

If S3 Files cannot assume the role, the file system moves to the `error` status and its `statusMessage` explains why, as on AWS.

### Create a mount target

A file system can only be mounted through a mount target in the subnet (or availability zone) where your compute runs.
Look up a subnet of the default VPC and create a mount target in it using the [`CreateMountTarget`](https://docs.aws.amazon.com/cli/latest/reference/s3files/create-mount-target.html) API:

```bash
SUBNET_ID=$(lstk aws ec2 describe-subnets \
    --filters Name=default-for-az,Values=true \
    --query 'Subnets[0].SubnetId' --output text)

lstk aws s3files create-mount-target \
    --file-system-id fs-c42a80aad0687dc16 \
    --subnet-id $SUBNET_ID
```

```bash title="Output"
{
    "availabilityZoneId": "use1-az6",
    "ownerId": "000000000000",
    "mountTargetId": "fsmt-9a5de3f3693f532ba",
    "fileSystemId": "fs-c42a80aad0687dc16",
    "subnetId": "subnet-83e9c59fddf26bca4",
    "ipv4Address": "172.31.9.121",
    "networkInterfaceId": "eni-53cc3e4f1af1d3f18",
    "vpcId": "vpc-34e4d3eaed665d31d",
    "securityGroups": [
        "sg-ab01c07b07e57bfae"
    ],
    "status": "creating"
}
```

### Create an access point

Access points give an application its own entry point into the file system, with a fixed POSIX identity and root directory.
Create an access point using the [`CreateAccessPoint`](https://docs.aws.amazon.com/cli/latest/reference/s3files/create-access-point.html) API:

```bash
lstk aws s3files create-access-point \
    --file-system-id fs-c42a80aad0687dc16 \
    --posix-user uid=1000,gid=1000 \
    --root-directory 'path=/data,creationPermissions={ownerUid=1000,ownerGid=1000,permissions=0755}' \
    --tags key=Name,value=my-access-point
```

```bash title="Output"
{
    "accessPointArn": "arn:aws:s3files:us-east-1:000000000000:file-system/fs-c42a80aad0687dc16/access-point/fsap-967845c746d9962e9",
    "accessPointId": "fsap-967845c746d9962e9",
    "clientToken": "be4fab3c-4279-4fc7-b971-5e44e0b3280f",
    "fileSystemId": "fs-c42a80aad0687dc16",
    "status": "available",
    "ownerId": "000000000000",
    "posixUser": {
        "uid": 1000,
        "gid": 1000
    },
    "rootDirectory": {
        "path": "/data",
        "creationPermissions": {
            "ownerUid": 1000,
            "ownerGid": 1000,
            "permissions": "0755"
        }
    },
    "tags": [
        {
            "key": "Name",
            "value": "my-access-point"
        }
    ]
}
```

## Mounting a file system

In LocalStack, you access an S3 Files file system from compute services: [Lambda](#lambda), [ECS](#ecs), and [Batch](#batch).
LocalStack mounts the file system into the function, task, or job container for you, so you do not need to run any mount command.
Mounts work with both the Docker executor and the [Kubernetes executor](/aws/customization/kubernetes/kubernetes-executor/).

To mount a file system, the following requirements apply:

- The file system must have a mount target in the availability zone the compute runs in. Without a mount target, the file system cannot be reached, as on AWS.
- With the Docker executor, LocalStack must have access to the Docker socket, as with any Lambda function or ECS task (`/var/run/docker.sock:/var/run/docker.sock`).
- With the Kubernetes executor, see the additional [Kubernetes requirements](#kubernetes).

### Lambda

Lambda functions mount an S3 Files access point through the same [`FileSystemConfigs`](https://docs.aws.amazon.com/lambda/latest/dg/configuration-filesystem.html) field used for EFS.
A function takes a single entry, which is an access point ARN and a `LocalMountPath` under `/mnt`.
The function must be connected to a VPC, and every subnet it runs in needs a mount target in its availability zone.

```bash
lstk aws lambda create-function \
    --function-name my-function \
    --runtime python3.13 \
    --handler handler.handler \
    --role arn:aws:iam::000000000000:role/lambda-role \
    --zip-file fileb://function.zip \
    --vpc-config SubnetIds=$SUBNET_ID,SecurityGroupIds=sg-ab01c07b07e57bfae \
    --file-system-configs Arn=arn:aws:s3files:us-east-1:000000000000:file-system/fs-c42a80aad0687dc16/access-point/fsap-967845c746d9962e9,LocalMountPath=/mnt/data
```

Your function can then read and write files under `/mnt/data`.

LocalStack validates the configuration when you create or update the function, in the same order as AWS.
It checks that the access point exists in the same region and account, that the file system has a mount target, and that there is a mount target in each availability zone of the function's subnets.

### ECS

ECS tasks mount a file system through an `s3filesVolumeConfiguration` volume in the task definition, referenced from the container's `mountPoints`.
See [`S3FilesVolumeConfiguration`](https://docs.aws.amazon.com/AmazonECS/latest/APIReference/API_S3FilesVolumeConfiguration.html) for the available fields.
The task definition must include a `taskRoleArn`.

```json title="task-definition.json" showLineNumbers
{
  "family": "s3files-task",
  "networkMode": "awsvpc",
  "requiresCompatibilities": ["FARGATE"],
  "cpu": "256",
  "memory": "512",
  "taskRoleArn": "arn:aws:iam::000000000000:role/ecs-task-role",
  "containerDefinitions": [
    {
      "name": "app",
      "image": "alpine",
      "command": ["sh", "-c", "echo hello > /mnt/documents/hello.txt"],
      "mountPoints": [
        { "sourceVolume": "documents", "containerPath": "/mnt/documents" }
      ]
    }
  ],
  "volumes": [
    {
      "name": "documents",
      "s3filesVolumeConfiguration": {
        "fileSystemArn": "arn:aws:s3files:us-east-1:000000000000:file-system/fs-c42a80aad0687dc16"
      }
    }
  ]
}
```

Register the task definition and run the task in a subnet that has a mount target:

```bash
lstk aws ecs register-task-definition --cli-input-json file://task-definition.json
lstk aws ecs run-task \
    --cluster my-cluster \
    --task-definition s3files-task \
    --launch-type FARGATE \
    --network-configuration "awsvpcConfiguration={subnets=[$SUBNET_ID],assignPublicIp=ENABLED}"
```

You can narrow what a task sees with an `accessPointArn`, a `rootDirectory`, or both.
When you set both, the task mounts the `rootDirectory` path inside the access point's root directory.
For example, an access point rooted at `/team-a` combined with `"rootDirectory": "/reports"` mounts `/team-a/reports`.

If the file system has no mount target, the task stops with `TaskFailedToStart` and the same `ResourceInitializationError` reason AWS reports (`Failed to resolve "<az>.<file-system-id>.s3files.<region>.on.aws" ...`).

:::note
With the Docker executor, mounting a `rootDirectory` other than `/` requires Docker Engine 26.0 or newer, and is not supported with Podman.
On an older runtime, the task stops with a `ResourceInitializationError: LocalStack cannot mount the rootDirectory of an S3 Files volume` reason.
Mounting the whole file system or an access point without a `rootDirectory` works with older Docker versions.
:::

### Batch

Batch jobs mount a file system through an `s3filesVolumeConfiguration` volume in the job definition's `containerProperties`, referenced from `mountPoints`.
See [`S3FilesVolumeConfiguration`](https://docs.aws.amazon.com/batch/latest/APIReference/API_S3FilesVolumeConfiguration.html) for the available fields.
The job definition must include a `jobRoleArn`.

```json title="job-definition.json" showLineNumbers
{
  "jobDefinitionName": "s3files-job",
  "type": "container",
  "platformCapabilities": ["FARGATE"],
  "containerProperties": {
    "image": "alpine",
    "command": ["sh", "-c", "ls /mnt/data"],
    "resourceRequirements": [
      { "type": "VCPU", "value": "0.25" },
      { "type": "MEMORY", "value": "512" }
    ],
    "executionRoleArn": "arn:aws:iam::000000000000:role/batch-execution-role",
    "jobRoleArn": "arn:aws:iam::000000000000:role/batch-job-role",
    "networkConfiguration": { "assignPublicIp": "ENABLED" },
    "volumes": [
      {
        "name": "data",
        "s3filesVolumeConfiguration": {
          "fileSystemArn": "arn:aws:s3files:us-east-1:000000000000:file-system/fs-c42a80aad0687dc16"
        }
      }
    ],
    "mountPoints": [
      { "sourceVolume": "data", "containerPath": "/mnt/data" }
    ]
  }
}
```

```bash
lstk aws batch register-job-definition --cli-input-json file://job-definition.json
```

```bash title="Output"
{
    "jobDefinitionName": "s3files-job",
    "jobDefinitionArn": "arn:aws:batch:us-east-1:000000000000:job-definition/s3files-job:1",
    "revision": 1
}
```

Batch validates S3 Files volumes with its own rules, which differ from ECS, as on AWS.
The `fileSystemArn` must be a full ARN (a bare `fs-` ID is rejected), and Batch stores the volume as given without filling in a default `rootDirectory`.

### Kubernetes

With the [Kubernetes executor](/aws/customization/kubernetes/kubernetes-executor/), LocalStack mounts S3 Files file systems into the pods of Lambda functions, ECS tasks, and Batch jobs using a PersistentVolume and a PersistentVolumeClaim.
The cluster nodes must have NFS client support in their kernel, because the kubelet mounts the file system on the node that runs the pod.
LocalStack's ServiceAccount also needs permissions that the [Helm chart](/aws/customization/kubernetes/deploy-helm-chart/) does not grant by default: `persistentvolumes` at the cluster level, and `persistentvolumeclaims` and `events` in LocalStack's namespace.
You can grant them with the chart's `extraDeploy` value:

```yaml title="values.yaml" showLineNumbers
extraDeploy:
  - apiVersion: rbac.authorization.k8s.io/v1
    kind: Role
    metadata:
      name: localstack-s3files
      namespace: "{{ .Release.Namespace }}"
    rules:
      - apiGroups: [""]
        resources: ["persistentvolumeclaims"]
        verbs: ["get", "list", "watch", "create", "delete"]
      - apiGroups: [""]
        resources: ["events"]
        verbs: ["get", "list", "watch"]
  - apiVersion: rbac.authorization.k8s.io/v1
    kind: RoleBinding
    metadata:
      name: localstack-s3files
      namespace: "{{ .Release.Namespace }}"
    roleRef:
      apiGroup: rbac.authorization.k8s.io
      kind: Role
      name: localstack-s3files
    subjects:
      - kind: ServiceAccount
        name: '{{ include "localstack.serviceAccountName" . }}'
        namespace: "{{ .Release.Namespace }}"
  - apiVersion: rbac.authorization.k8s.io/v1
    kind: ClusterRole
    metadata:
      name: localstack-s3files
    rules:
      - apiGroups: [""]
        resources: ["persistentvolumes"]
        verbs: ["get", "list", "watch", "create", "delete"]
  - apiVersion: rbac.authorization.k8s.io/v1
    kind: ClusterRoleBinding
    metadata:
      name: localstack-s3files
    roleRef:
      apiGroup: rbac.authorization.k8s.io
      kind: ClusterRole
      name: localstack-s3files
    subjects:
      - kind: ServiceAccount
        name: '{{ include "localstack.serviceAccountName" . }}'
        namespace: "{{ .Release.Namespace }}"
```

## Synchronization between the bucket and the file system

S3 Files keeps the file system and the bucket in sync in both directions, and the bucket is the source of truth.

- **Bucket to file system**: Objects you write to the bucket appear in the file system within seconds.
  Small objects are imported into the file system according to the synchronization configuration, and larger objects are read directly from the bucket.
- **File system to bucket**: A file is exported to the bucket as a new object version after it has gone 60 seconds without writes.
  A file that is created and deleted within this window never reaches the bucket.
  Directories appear in the bucket right away as directory markers (`dir/`).
  See [Export window](#export-window) for how the delay behaves.

Each export creates a new version of the object.
S3 Files stores the file's POSIX attributes (owner, group, permissions, and timestamps) as object metadata, so a `chmod` or a timestamp change alone exports a new version with updated metadata.
The first export of a file sets the object's `Content-Type` from the file extension, and files with an unknown extension get `application/octet-stream`.

When a file changes in the bucket and in the file system at the same time, the bucket wins.
S3 Files moves your local copy to the `.s3files-lost+found-<file-system-id>` directory in the root of the file system, which is not exported to the bucket.

S3 Files supports regular files, directories, symbolic links, and FIFOs.
As on AWS, hard links, device files, sockets, and writing extended attributes are not supported.

### Export window

On AWS, S3 Files exports a file only after it has gone 60 seconds without writes, and this window cannot be configured.
Every write restarts the window, so the file is not exported while writes keep arriving.
For example, a process that appends to a log file every 5 seconds keeps resetting the window, and the file never reaches the bucket while the process runs.

LocalStack uses the same 60-second window by default.
To speed up manual testing, you can shorten it with the [`S3FILES_EXPORT_DELAY_SECONDS`](/aws/customization/configuration-options/#s3-files) configuration variable, so that your writes reach the bucket sooner.

:::caution
A shorter export window changes when files reach the bucket, and can hide behavior your application will see on AWS.
With `S3FILES_EXPORT_DELAY_SECONDS=2`, the log file in the example above is exported after every write, while on AWS it is never exported until the writes stop.
To validate how your application behaves on AWS, test it with the default 60-second window.
:::

### Synchronization configuration

Each new file system gets a default synchronization configuration, which you can read with the [`GetSynchronizationConfiguration`](https://docs.aws.amazon.com/cli/latest/reference/s3files/get-synchronization-configuration.html) API:

```bash
lstk aws s3files get-synchronization-configuration --file-system-id fs-c42a80aad0687dc16
```

```bash title="Output"
{
    "latestVersionNumber": 0,
    "importDataRules": [
        {
            "prefix": "",
            "trigger": "ON_DIRECTORY_FIRST_ACCESS",
            "sizeLessThan": 131072
        }
    ],
    "expirationDataRules": [
        {
            "daysAfterLastAccess": 30
        }
    ]
}
```

With the default import rule, objects smaller than 128 KiB are imported when their directory is first listed.
With the `ON_FILE_ACCESS` trigger, they are imported when the file is first read.
Data that is not accessed for the number of days in the expiration rule is evicted from the file system and read from the bucket again on the next access.

You can change these rules with the [`PutSynchronizationConfiguration`](https://docs.aws.amazon.com/cli/latest/reference/s3files/put-synchronization-configuration.html) API, and the changes apply to a running file system right away.

## Current Limitations

- List operations do not paginate, and return all results in a single page.
- The account in resource ARNs is not checked. AWS returns an `AccessDeniedException` for ARNs from another account.
- `PutFileSystemPolicy` accepts policies that would lock you out of future `PutFileSystemPolicy` calls. AWS rejects them.
- The `kmsKeyId` of a file system is stored but not used to encrypt data.
- The `networkInterfaceId` of a mount target does not refer to an existing network interface.
- IAM authorization for mounts is not enforced, including the `s3files:ClientMount` and `s3files:ClientRootAccess` permissions.
- The `secondaryGids` of an access point's POSIX user are not enforced.
- CloudFormation does not add the `aws:cloudformation:*` system tags to S3 Files resources.
- S3 allows deleting a bucket that still has file systems attached.

## API Coverage


### s3files API coverage

Source service: `s3files`. 21 of 21 tracked operations are implemented.

Service documentation: /aws/services/s3files/
License availability: available starting with the Ultimate plan. See /aws/licensing/ for current plan details.

| Operation | Status |
| --- | --- |
| CreateAccessPoint | Implemented |
| CreateFileSystem | Implemented |
| CreateMountTarget | Implemented |
| DeleteAccessPoint | Implemented |
| DeleteFileSystem | Implemented |
| DeleteFileSystemPolicy | Implemented |
| DeleteMountTarget | Implemented |
| GetAccessPoint | Implemented |
| GetFileSystem | Implemented |
| GetFileSystemPolicy | Implemented |
| GetMountTarget | Implemented |
| GetSynchronizationConfiguration | Implemented |
| ListAccessPoints | Implemented |
| ListFileSystems | Implemented |
| ListMountTargets | Implemented |
| ListTagsForResource | Implemented |
| PutFileSystemPolicy | Implemented |
| PutSynchronizationConfiguration | Implemented |
| TagResource | Implemented |
| UntagResource | Implemented |
| UpdateMountTarget | Implemented |
