
Getting Started with Amazon S3 Files: Mounting S3 Buckets as File Systems
Discover how to use Amazon S3 Files to mount your S3 buckets as fully-featured file systems on EC2 and Lambda, with practical Python and CLI examples for startup workloads.
Amazon S3 Files launched on April 7, 2026ย , and it solves a problem that almost every startup building on AWS hits eventually: your data lives in S3 because it's cheap and durable, but your tools expect a file system.
Until now, that meant choosing between S3's scalability and a file system's interactivity, or worse, building sync pipelines to shuttle data between the two. S3 Filesย eliminates that tradeoff. You can now mount any S3 general purpose bucket (or prefix) as a shared NFS file system on your EC2 instances, Lambda functions, ECS tasks, and EKS pods. Your existing tools such as Python scripts, ML frameworks, CLI utilities, shell commands... just work! No custom connectors, no new APIs.
Under the hood, S3 Files is built on Amazon EFS and uses a "stage and commit" design. Changes accumulate on the file system's high-performance storage layer and are synchronised back to S3 as full object PUTs. Changes made directly to S3 objects are reflected in the file system view automatically. S3 is always the source of truth.
In this article, we'll walk through a practical example of setting up S3 Files beginning with creating a file system, mounting a bucket on EC2, and observing bidirectional sync in action with a Python script.
โจ Why S3 Files Matters for Startups
If you're an early-stage startup, you probably started with S3 on day one. It's the default choice for storing training data, application assets, logs, and everything in between. But the moment you need to run a pandas script, train a model, or process files with standard Unix tools, you hit the object-vs-file boundary.
The typical workaround looks something like this:
- Download data from S3 to local disk or EFS
- Process it with your file-based tools
- Upload results back to S3
- Build automation to keep everything in sync
- Pay for duplicate storage
S3 Files collapses steps 1โ5 into: mount the bucket, do your work. For a small team moving fast, that's a meaningful reduction in plumbing code and infrastructure overhead.
Key capabilities:
- NFS v4.1/4.2 protocol with close-to-open consistency
- Lazy hydration โ only metadata and small files (< 128 KB by default) are pulled onto the high-performance storage on access. Large files are read directly from S3
- Intelligent read routing โ small random reads come from the EFS layer (~1ms latency), large sequential reads stream directly from S3 (up to 3 GB/s per client)
- Bidirectional sync โ file system writes export to S3 within ~60 seconds; S3 object changes import into the file system within seconds
- Up to 25,000 concurrent compute connections per file system
- Available in all commercial AWS Regions at launch
๐ ๏ธ Prerequisites
Before getting started with the examples in this article, make sure you have:
- An AWS account with access to Amazon S3
- AWS CLI v2 installed and configured with appropriate permissions
- Python 3.x installed with the latest version of
boto3 - An EC2 instance running Amazon Linux 2023 (or any Linux AMI with
amazon-efs-utilsinstalled) - A general purpose S3 bucket (S3 Files does not support directory buckets)
On your EC2 instance, install Python dependencies (AL2023 doesn't include
pip or boto3 by default):1
2
sudo dnf install -y python3-pip
pip3 install boto3Note: AL2023 usespython3andpip3โ there is nopythonorpipalias by default.
S3 Files has its own CLI namespace โ
aws s3files. You can see all available commands in the AWS CLI S3 Files referenceย .On your EC2 instance, ensure
amazon-efs-utils is up to date:1
sudo yum install -y amazon-efs-utilsโ ๏ธ Using an existing EC2 instance? If your Amazon Linux 2023 instance has been running for a while, it's likely pinned to an older release that predates S3 Files. Theamazon-efs-utilsversion you get fromyum installon an older release won't include support for the-t s3filesmount type. You need to upgrade to the latest AL2023 release first:
1
2
3
4
5
6
7
8
9
10
11
12# Check what newer releases are available
/usr/bin/dnf check-release-update
# Upgrade to the latest available AL2023 release
# Replace the version below with the newest one listed in the output above
sudo dnf upgrade --releasever=<latest-version> -y
# This upgrades amazon-efs-utils from 2.4.x to 3.0.0+ which includes s3files mount support
# Verify the version
rpm -q amazon-efs-utils
# Should show: amazon-efs-utils-3.0.0 or newer
```If you launched a fresh EC2 instance with the latest AL2023 AMI, you can skip this โ the latest AMI ships with the correct versions.
IAM Permissions
Your EC2 instance role needs the following managed policies:
AmazonS3FilesClientFullAccessโ for full read/write access to the S3 file system- Standard S3 permissions on the bucket (for direct S3 operations in our examples)
For production workloads, use
AmazonS3FilesClientReadOnlyAccess if your compute only needs to read data.Important:AmazonS3FilesClientFullAccessonly covers file system mount operations (s3files:ClientMount,s3files:ClientWrite, etc.). If your code also makes direct S3 API calls โ likeput_object()orget_object()via boto3, which our example does to demonstrate bidirectional sync โ you need standard S3 permissions (s3:PutObject,s3:GetObject,s3:ListBucket) on the bucket as well. These are two separate permission surfaces.The easiest way to handle this for testing is to also attach theAmazonS3FullAccessmanaged policy to your EC2 instance role. For production, scope it down toAmazonS3ReadOnlyAccessor a custom policy with only the actions you need on your specific bucket.
Security Groups
Your mount target's security group must allow inbound NFS traffic (TCP port 2049) from your compute resources. Without this, the mount command will time out with repeated
Mount attempt X/3 failed due to timeout after 15 sec errors.When you create a file system through the S3 console, mount targets are created automatically in every AZ of your selected VPC โ but the default security group may not have the NFS inbound rule. You need to add it.
Here's how to configure it via the CLI:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# Find your EC2 instance's security group
EC2_SG=$(aws ec2 describe-instances \
--instance-ids i-0EXAMPLE1234abcde \ #replace with your instance-id
--query 'Reservations[0].Instances[0].SecurityGroups[0].GroupId' \
--output text)
echo "EC2 security group: $EC2_SG"
# Find the mount target's security group (from the create-mount-target response,
# or check in the S3 console under File systems โ Mount targets)
MOUNT_TARGET_SG="sg-0EXAMPLE1234abcde"
# Add an inbound rule to the mount target's security group
# allowing NFS (TCP 2049) from your EC2 instance's security group
aws ec2 authorize-security-group-ingress \
--group-id $MOUNT_TARGET_SG \
--protocol tcp \
--port 2049 \
--source-group $EC2_SGAlternatively, for quick testing you can allow NFS from the entire VPC CIDR:
1
2
3
4
5
aws ec2 authorize-security-group-ingress \
--group-id $MOUNT_TARGET_SG \
--protocol tcp \
--port 2049 \
--cidr 172.31.0.0/16You can also do this in the console: VPC โ Security Groups โ select the mount target's SG โ Inbound rules โ Edit โ Add rule โ Type: NFS, Source: your EC2 security group.
๐งช Creating an S3 File System, Mounting on EC2, and Testing Sync
Let's walk through creating an S3 file system, mounting it on an EC2 instance, and demonstrating bidirectional synchronization between the file system and S3.
Note: The S3 console simplifies most of this setup, creating a file system is often a single click, and mount targets are created automatically in every AZ of your default VPC. In this article, I'm walking through the CLI steps intentionally to help you understand what's happening under the hood and what each component does. For day-to-day use, the console is the fastest path.
Step 1: Create the S3 File System
You can create a file system through the AWS Console or CLI. Here we'll use the CLI:
1
2
3
4
5
6
7
8
# Create an S3 file system linked to your bucket
# Replace my-startup-data-bucket with your own bucket name throughout this article
aws s3files create-file-system \
--region us-east-1 \
--bucket arn:aws:s3:::my-startup-data-bucket \
--role-arn arn:aws:iam::123456789012:role/S3FilesAccessRole
# Note the FileSystemId from the response JSON (e.g., fs-0aa860d05df9afdfe)โ ๏ธ Heads up: Your bucket must have versioning enabled before you can create a file system. If you're using an existing bucket without versioning, you'll get:
An error occurred (ValidationException) when calling the CreateFileSystem operation: Your bucket must have versioning enabled to create a file system.
Enable it via the console (Bucket โ Properties โ Bucket Versioning โ Enable) or CLI:
1
2
3aws s3api put-bucket-versioning \
--bucket my-startup-data-bucket \
--versioning-configuration Status=Enabled
The
--role-arn is the IAM role that S3 Files assumes to read from and write to your bucket. See the prerequisites docsย for the required permissions on this role.The console automatically creates mount targets in every AZ of your default VPC. With the CLI, you need to create them explicitly:
1
2
3
4
5
# Create a mount target in your subnet (must be in the same AZ as your EC2 instance)
aws s3files create-mount-target \
--region us-east-1 \
--file-system-id fs-0aa860d05df9afdfe \
--subnet-id subnet-0123456789abcdef0You'll see a response similar to this (your values will differ):
1
2
3
4
5
6
7
8
9
10
11
{
"availabilityZoneId": "use1-az1",
"mountTargetId": "fsmt-0EXAMPLE1234abcde",
"fileSystemId": "fs-0aa860d05df9afdfe",
"subnetId": "subnet-0123456789abcdef0",
"ipv4Address": "172.31.x.x",
"networkInterfaceId": "eni-0EXAMPLE1234abcde",
"vpcId": "vpc-0EXAMPLE1234abcde",
"securityGroups": ["sg-0EXAMPLE1234abcde"],
"status": "creating"
}โ ๏ธ Note: The CLI returns"status": "creating"and won't update in your terminal โ it's not a live status. Don't sit there waiting for it to change. Mount targets take up to ~5 minutes to become available.Check the status in the S3 console (Bucket โ File systems โ Mount targets tab) or poll with:
1
2
3aws s3files get-mount-target \
--region us-east-1 \
--mount-target-id your-mount-target-id
Wait until the status shows"available"before attempting to mount.
Step 2: Mount the File System
SSH into your EC2 instance and mount:
1
2
3
4
5
6
7
8
9
10
11
12
13
# Create the mount point
sudo mkdir /mnt/s3files
# Mount using the S3 Files mount type
# Replace fs-0aa860d05df9afdfe with your own file system ID from Step 1
sudo mount -t s3files fs-0aa860d05df9afdfe:/ /mnt/s3files
# Verify the mount
df -h /mnt/s3files
# You should see something like:
# Filesystem Size Used Avail Use% Mounted on
# 127.0.0.1:/ 8.0E 129M 8.0E 1% /mnt/s3filesYou can also inspect mount details with:
1
findmnt -T /mnt/s3filesStep 3: Observe Bidirectional Sync
Now let's demonstrate the sync behavior with a Python script that creates files through the file system and verifies they appear in S3, then creates objects in S3 and verifies they appear in the file system.
Note: The mount point is owned by root by default. If you're logged in asec2-user(via Instance Connect or SSH) or any other non-root user, you'll getPermissionError: [Errno 13] Permission deniedwhen trying to write. Fix this by changing ownership before running the script:
1sudo chown $(whoami):$(whoami) /mnt/s3files
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
import boto3
import time
import os
def demonstrate_bidirectional_sync():
"""
Demonstrates bidirectional synchronization between an S3 file system
mount and the underlying S3 bucket.
"""
s3_client = boto3.client("s3", region_name="us-east-1")
# Replace with your own bucket name
bucket_name = "my-startup-data-bucket"
mount_path = "/mnt/s3files"
# --- Part 1: File System โ S3 ---
print("=== FILE SYSTEM โ S3 SYNC ===\n")
# Write a file through the file system mount
test_file = os.path.join(mount_path, "sync-test", "hello.txt")
os.makedirs(os.path.dirname(test_file), exist_ok=True)
with open(test_file, "w") as f:
f.write("Written from the file system at " + time.strftime("%H:%M:%S"))
print(f"Created file: {test_file}")
print(f"Content: {open(test_file).read()}")
# Wait for export sync (changes export to S3 within ~60 seconds)
print("\nWaiting 90 seconds for file system โ S3 export sync...")
time.sleep(90)
# Verify the file appeared in S3
try:
response = s3_client.get_object(
Bucket=bucket_name,
Key="sync-test/hello.txt"
)
s3_content = response["Body"].read().decode("utf-8")
print(f"โ
File found in S3!")
print(f"S3 object content: {s3_content}")
except s3_client.exceptions.NoSuchKey:
print("โณ File not yet in S3 โ sync may still be in progress")
# --- Part 2: S3 โ File System ---
print("\n=== S3 โ FILE SYSTEM SYNC ===\n")
# Write an object directly to S3
s3_client.put_object(
Bucket=bucket_name,
Key="sync-test/from-s3.txt",
Body="Written directly to S3 at " + time.strftime("%H:%M:%S")
)
print("Created object in S3: sync-test/from-s3.txt")
# S3 โ file system import is typically faster (seconds to ~1 minute)
print("Waiting 30 seconds for S3 โ file system import sync...")
time.sleep(30)
# Check if the file appeared in the file system
imported_file = os.path.join(mount_path, "sync-test", "from-s3.txt")
if os.path.exists(imported_file):
print(f"โ
File appeared in file system!")
print(f"File system content: {open(imported_file).read()}")
else:
print("โณ File not yet visible โ import sync may still be in progress")
# --- Part 3: List directory to show unified view ---
print("\n=== UNIFIED VIEW ===\n")
sync_dir = os.path.join(mount_path, "sync-test")
if os.path.isdir(sync_dir):
files = os.listdir(sync_dir)
print(f"Files in {sync_dir}:")
for f in sorted(files):
filepath = os.path.join(sync_dir, f)
size = os.path.getsize(filepath)
print(f" {f} ({size} bytes)")
# Compare with S3 listing
print(f"\nObjects in s3://{bucket_name}/sync-test/:")
response = s3_client.list_objects_v2(
Bucket=bucket_name,
Prefix="sync-test/"
)
for obj in response.get("Contents", []):
print(f" {obj['Key']} ({obj['Size']} bytes)")
if __name__ == "__main__":
demonstrate_bidirectional_sync()Output Analysis: Bidirectional Sync
When I ran this example, the output demonstrated the core sync behavior:

Key observations:
- File system โ S3 export completed within the 90-second wait window. The docs state changes are exported roughly every 60 seconds as full PUT operations. In practice, I saw exports complete in 45โ70 seconds.
- S3 โ file system import was noticeably faster. The file appeared in the mount within about 15 seconds. S3 Files uses EventBridge rules to detect bucket changes and trigger imports.
- The unified view shows both files visible through both interfaces โ one copy of data, two access patterns. This is the core value proposition.
- No code changes needed โ standard Python
open(),os.path, andos.listdir()work exactly as expected. Your existing scripts don't need modification.
๐ Important Implementation Details
When working with S3 Files, keep these technical details in mind:
Synchronization Timing: File system writes export to S3 roughly every 60 seconds as full PUT operations. S3 changes import to the file system within seconds to about a minute. If your workflow requires immediate consistency across both interfaces, design accordingly โ S3 Files provides NFS close-to-open consistency within the file system, but cross-interface consistency has the sync delay.
Conflict Resolution: If a file is modified through the file system and its corresponding S3 object is modified at the same time, S3 wins. The file system version is moved to a
lost+found directory, and a CloudWatch metric flags the event. Best practice: designate one interface (file system or S3 API) as the primary writer for any given prefix.Rename and Move Costs: S3 has no native rename operation. When you rename or move a directory through the file system, S3 Files must copy and delete every object under that prefix. Renaming a directory with millions of files can be expensive and slow. Scope your file system to the smallest prefix your workload needs.
The 128 KB Threshold: By default, files smaller than 128 KB are cached on the high-performance storage when you first access a directory. Larger files are read directly from S3. You can tune this threshold via the
sizeLessThan import configuration to match your workload's file sizes.Data Expiration: Data not accessed within a configurable window (1โ365 days, default 30) automatically expires from the high-performance storage layer. The data remains in S3 โ it's just evicted from the fast cache. For short-lived batch jobs, set a shorter window (1โ7 days) to minimize storage costs.
Object Key Compatibility: Some S3 object keys can't be represented as valid POSIX filenames (e.g., keys ending with
/, keys with null bytes, or keys with incompatible characters). These objects won't appear in the file system view. S3 Files emits events when it encounters keys that can't be synced.Encryption: All data is encrypted in transit (TLS 1.3) and at rest (SSE-S3 or SSE-KMS with customer managed keys). This is automatic โ no configuration needed for the default SSE-S3 encryption.
Bucket Requirements: S3 Files works with general purpose buckets only. Directory buckets are not supported. S3 Versioning is recommended (and required for conflict resolution to work properly).
๐ก Best Practices for Startups
Start with a prefix, not the whole bucket. Mount only the prefix your workload needs. This reduces the metadata S3 Files needs to import, speeds up initial mount, and limits the blast radius of expensive rename operations.
Use large I/O sizes. S3 Files meters each read and write at a minimum of 32 KB. Using 1 MB+ I/O sizes (the default NFS buffer size) amortizes per-operation overhead and is more cost effective than many small reads or writes.
Create mount targets in every AZ you use. This avoids cross-AZ data transfer costs and improves latency. The console does this automatically; with CLI you need to create them explicitly.
Monitor sync health. Watch the
PendingExports and ExportFailures CloudWatch metrics. A growing PendingExports means your workload is generating changes faster than the sync rate. Non-zero ExportFailures means files couldn't be exported and need attention.Don't touch the auto-created IAM role or EventBridge rule. S3 Files creates an IAM role for sync operations and an EventBridge rule (prefixed with
DO-NOT-DELETE-S3-Files) to detect bucket changes. Modifying or deleting either will break synchronization.Parallelize for throughput. S3 Files scales with parallel access. Distributing reads across multiple files and compute instances maximizes aggregate throughput. You can also create multiple file systems scoped to different prefixes within the same bucket to scale horizontally.
๐ Conclusion
Amazon S3 Files bridges the gap between object storage and file systems in a way that's practical and immediately useful. For startups building on AWS, it means you can stop building data-shuttling infrastructure and start treating your S3 bucket as the single home for your data โ accessible through whichever interface your tools need.
The example in this article showed how to set up S3 Files on EC2 and observe bidirectional sync in action โ writing files through the mount and verifying they appear in S3, and vice versa. All with standard Python file operations, no custom connectors needed.
S3 Files is GA in all commercial regions with pay-as-you-go pricing and no provisioned capacity. If you're currently running S3 + EFS side by side, or building sync logic between object and file storage, it's worth trying out.
To get started, check out the S3 Files documentationย and the official launch blog postย . For the fascinating design backstory, Andy Warfield's All Things Distributed postย on the "stage and commit" architecture is a great read.
Have you tried S3 Files with your workloads? What use cases are you exploring? Share your experiences in the comments below!
Enjoyed reading this content? Let the author know!
Your likes, comments, shares, and saves help creators reach more builders.
Loading recommendations
Loading article