S3 Files, enabling S3 buckets as file systems

AWS has introduced S3 Files, which allows you to access S3 buckets as a filesystem across compute services such as EC2, Lambda, ECS and EKS.

Previously, working with S3 followed a download-process-upload pattern:

  • Download objects from S3 to local storage

  • Process them

  • Upload the results back to S3

With S3 Files, this pattern is no longer necessary. Applications can interact with S3 objects as if they were a local filesystem, making S3 behave like a "shared drive" across your compute environments.

Use Cases

  • AI Agents

    AI agents often need to read and write data across multiple steps. With S3 Files, they can work directly on S3 as a filesystem, making it easier to share state and coordinate without extra syncing.

  • ML pipelines

    ML workflows such as preprocessing, feature engineering, and model training work primarily with files. Every run typically involves pulling data from S3, processing it locally, and writing the results back. With S3 files, data can be accessed and modified in place, removing the unnecessary data movement.

post image

Templates

This section provides CloudFormation templates for S3 FilesSystem and its integration with Lambda, ECS task, and EC2.

The following values used in the examples are arbitrary and can be adjusted as needed.

  • UID, GID (non-root Linux user)

  • Access point root path

  • Mount path

S3 FilesSystem

AWSTemplateFormatVersion: "2010-09-09"
Description: S3 Files

Parameters:
  S3BucketName:
    Type: String
  VpcId:
    Type: AWS::EC2::VPC::Id
  SubnetId:
    Type: AWS::EC2::Subnet::Id
  Ec2AmiId:
    Type: AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>
    Default: "/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2"

Resources:
  S3Bucket:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: !Ref S3BucketName
      VersioningConfiguration: 
        Status: Enabled

  S3FilesSystem:
    Type: AWS::S3Files::FileSystem
    Properties:
      Bucket: !GetAtt S3Bucket.Arn
      RoleArn: !GetAtt S3FilesSystemRole.Arn

  # For production use, consider creating mount targets in multiple AZs for high availability.
  S3FilesMountTarget:
    Type: AWS::S3Files::MountTarget
    Properties:
      FileSystemId: !Ref S3FilesSystem
      SubnetId: !Ref SubnetId
      SecurityGroups:
        - !Ref S3FilesSecurityGroup

  S3FilesSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: !Sub "${AWS::StackName}-s3-files-sg"
      VpcId: !Ref VpcId

  S3FilesSystemRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal:
              Service: elasticfilesystem.amazonaws.com
            Action: sts:AssumeRole
      Policies:
        - PolicyName: !Sub "${AWS::StackName}-s3-files"
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Effect: Allow
                Action:
                  - s3:ListBucket
                  - s3:ListBucketVersions
                Resource:
                  - !Sub "arn:aws:s3:::${S3Bucket}"
                Condition:
                  StringEquals:
                    aws:ResourceAccount: !Ref AWS::AccountId
              - Effect: Allow
                Action:
                  - s3:AbortMultipartUpload
                  - s3:DeleteObject*
                  - s3:GetObject*
                  - s3:List*
                  - s3:PutObject*
                Resource:
                  - !Sub "arn:aws:s3:::${S3Bucket}/*"
                Condition:
                  StringEquals:
                    aws:ResourceAccount: !Ref AWS::AccountId
              - Effect: Allow
                Action:
                  - kms:GenerateDataKey
                  - kms:Encrypt
                  - kms:Decrypt
                  - kms:ReEncryptFrom
                  - kms:ReEncryptTo
                Resource:
                  - !Sub "arn:aws:kms:${AWS::Region}:${AWS::AccountId}:*"
                Condition:
                  StringLike:
                    kms:ViaService: !Sub "s3.${AWS::Region}.amazonaws.com"
                    "kms:EncryptionContext:aws:s3:arn":
                      - !Sub "arn:aws:s3:::${S3Bucket}"
                      - !Sub "arn:aws:s3:::${S3Bucket}/*"
              - Effect: Allow
                Action:
                  - events:DeleteRule
                  - events:DisableRule
                  - events:EnableRule
                  - events:PutRule
                  - events:PutTargets
                  - events:RemoveTargets
                Resource:
                  - !Sub "arn:aws:events:${AWS::Region}:${AWS::AccountId}:rule/DO-NOT-DELETE-S3-Files*"
                Condition:
                  StringEquals:
                    events:ManagedBy: elasticfilesystem.amazonaws.com
              - Effect: Allow
                Action:
                  - events:DescribeRule
                  - events:ListRuleNamesByTarget
                  - events:ListRules
                  - events:ListTargetsByRule
                Resource:
                  - !Sub "arn:aws:events:${AWS::Region}:${AWS::AccountId}:rule/*"
  • Enable S3 bucket versioning.

  • The mount target for S3 Files must to be within the same VPC as your compute services.

Lambda

  # ===== Lambda =====
  S3FilesAccessPointLambda:
    Type: AWS::S3Files::AccessPoint
    Properties:
      FileSystemId: !Ref S3FilesSystem
      PosixUser: 
        Gid: 1000
        Uid: 1000
      RootDirectory:
        Path: "/lambda"
        # Allow User ID 1000 to read/write/execute in the root directory
        CreationPermissions:
          OwnerGid: 1000
          OwnerUid: 1000
          Permissions: "755"

  S3FilesSecurityGroupIngressLambda:
    Type: AWS::EC2::SecurityGroupIngress
    Properties:
      GroupId: !Ref S3FilesSecurityGroup
      IpProtocol: tcp
      FromPort: 2049
      ToPort: 2049
      SourceSecurityGroupId: !Ref LambdaSecurityGroup

  LambdaSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: !Sub "${AWS::StackName}-lambda-sg"
      VpcId: !Ref VpcId
      # S3 File System uses NFS (port 2049) over the VPC
      SecurityGroupEgress:
        - IpProtocol: tcp
          FromPort: 2049
          ToPort: 2049
          DestinationSecurityGroupId: !Ref S3FilesSecurityGroup

  # NOTE: Ensure the S3 VPC Endpoint or NAT Gateway is configured to allow access between VPC and S3
  Lambda:
    Type: AWS::Lambda::Function
    DependsOn: S3FilesMountTarget
    Properties:
      FunctionName: s3-files-demo
      Role: !GetAtt LambdaExecutionRole.Arn
      Handler: index.lambda_handler
      Runtime: python3.14
      MemorySize: 512
      Timeout: 30
      Code:
        ZipFile: |
          import os
          import json
          import time

          MOUNT_PATH = "/mnt/lambda"


          def lambda_handler(event, context):
              timestamp = int(time.time())
              filename = f"demo-{timestamp}.txt"
              filepath = os.path.join(MOUNT_PATH, filename)
              content = f"Hello from Lambda! Written at {timestamp}"

              with open(filepath, "w") as f:
                  f.write(content)

              files = os.listdir(MOUNT_PATH)

              return {
                  "statusCode": 200,
                  "body": json.dumps({"files_in_mount": files[:20]}),
              }
      VpcConfig:
        SecurityGroupIds:
          - !Ref LambdaSecurityGroup
        SubnetIds:
          - !Ref SubnetId
      # Mount the S3 Files System at /mnt/lambda
      FileSystemConfigs:
        - Arn: !GetAtt S3FilesAccessPointLambda.AccessPointArn
          LocalMountPath: /mnt/lambda

  LambdaExecutionRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal:
              Service: lambda.amazonaws.com
            Action: sts:AssumeRole
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole
        - arn:aws:iam::aws:policy/AmazonS3FilesClientReadWriteAccess
      Policies:
        - PolicyName: !Sub "${AWS::StackName}-LambdaS3FilesAccess"
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Effect: Allow
                Action:
                  - s3:GetObject
                  - s3:GetObjectVersion
                Resource: !Sub "arn:aws:s3:::${S3Bucket}/*"
  • CreationPermissions for S3Files AccessPoint should be set to enable the user (UID 1000) to create S3 prefix (/lambda), as well as perform read and write operations on S3 objects under that prefix.

  • FileSystemConfigs for Lambda must be configured and the LocalMountPath defines where it is accessible within the Lambda function.

  • Inbound traffic from Lambda for S3 Files Mount Target's security group.

  • Outbound traffic to S3 Files Mount Target for Lambda's security group.

ECS Task

  # ===== ECS Task =====
  S3FilesAccessPointEcs:
    Type: AWS::S3Files::AccessPoint
    Properties:
      FileSystemId: !Ref S3FilesSystem
      PosixUser: 
        Gid: 1001
        Uid: 1001
      RootDirectory:
        Path: "/ecs"
        # Allow User ID 1001 to read/write/execute in the root directory
        CreationPermissions:
          OwnerGid: 1001
          OwnerUid: 1001
          Permissions: "755"

  S3FilesSecurityGroupIngressECS:
    Type: AWS::EC2::SecurityGroupIngress
    Properties:
      GroupId: !Ref S3FilesSecurityGroup
      IpProtocol: tcp
      FromPort: 2049
      ToPort: 2049
      SourceSecurityGroupId: !Ref EcsTaskSecurityGroup

  EcsTaskSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: !Sub "${AWS::StackName}-ecs-task-sg"
      VpcId: !Ref VpcId
      SecurityGroupEgress:
        - IpProtocol: tcp
          FromPort: 443
          ToPort: 443
          CidrIp: 0.0.0.0/0
          Description: ECR image pull and CloudWatch Logs via NAT Gateway
        - IpProtocol: tcp
          FromPort: 2049
          ToPort: 2049
          DestinationSecurityGroupId: !Ref S3FilesSecurityGroup
          Description: S3 Files mount target

  EcrRepository:
    Type: AWS::ECR::Repository
    Properties:
      RepositoryName: !Sub "${AWS::StackName}"

  # NOTE: Ensure the S3 VPC Endpoint or NAT Gateway is configured to allow access between VPC and S3
  # NOTE: Ensure the ECR VPC endpoint or NAT Gateway is configured to allow access for image pulls from ECR
  EcsCluster:
    Type: AWS::ECS::Cluster
    Properties:
      ClusterName: !Sub "${AWS::StackName}-cluster"

  EcsLogGroup:
    Type: AWS::Logs::LogGroup
    Properties:
      LogGroupName: !Sub "/ecs/${AWS::StackName}"
      RetentionInDays: 7

  EcsTaskExecutionRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal:
              Service: ecs-tasks.amazonaws.com
            Action: sts:AssumeRole
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy

  EcsTaskRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal:
              Service: ecs-tasks.amazonaws.com
            Action: sts:AssumeRole
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/AmazonS3FilesClientReadWriteAccess
      Policies:
        - PolicyName: !Sub "${AWS::StackName}-ECSTaskS3Access"
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Effect: Allow
                Action:
                  - s3:GetObject
                  - s3:GetObjectVersion
                Resource: !Sub "arn:aws:s3:::${S3Bucket}/*"

  EcsTaskDefinition:
    Type: AWS::ECS::TaskDefinition
    DependsOn: S3FilesMountTarget
    Properties:
      Family: !Sub "${AWS::StackName}-s3-files"
      RequiresCompatibilities:
        - FARGATE
      NetworkMode: awsvpc
      Cpu: "256"
      Memory: "512"
      ExecutionRoleArn: !GetAtt EcsTaskExecutionRole.Arn
      TaskRoleArn: !GetAtt EcsTaskRole.Arn
      Volumes:
        - Name: s3-files
          S3FilesVolumeConfiguration:
            FileSystemArn: !GetAtt S3FilesSystem.FileSystemArn
            AccessPointArn: !GetAtt S3FilesAccessPointEcs.AccessPointArn
      ContainerDefinitions:
        - Name: !Sub "${AWS::StackName}-s3-files"
          Image: !Sub "${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/${EcrRepository}:latest"
          Essential: true
          User: "1001:1001"
          MountPoints:
            - SourceVolume: s3-files
              ContainerPath: /mnt/ecs
              ReadOnly: false
          LogConfiguration:
            LogDriver: awslogs
            Options:
              awslogs-group: !Ref EcsLogGroup
              awslogs-region: !Ref AWS::Region
              awslogs-stream-prefix: ecs

  EcsService:
    Type: AWS::ECS::Service
    DependsOn: S3FilesMountTarget
    Properties:
      Cluster: !Ref EcsCluster
      TaskDefinition: !Ref EcsTaskDefinition
      LaunchType: FARGATE
      DesiredCount: 1
      NetworkConfiguration:
        AwsvpcConfiguration:
          Subnets:
            - !Ref SubnetId
          SecurityGroups:
            - !Ref EcsTaskSecurityGroup
# ===== Example in ECS task  =====
import json
import os
import time

MOUNT_PATH = "/mnt/ecs"

def main():
    timestamp = int(time.time())
    filename = f"demo-{timestamp}.txt"
    filepath = os.path.join(MOUNT_PATH, filename)
    content = f"Hello from ECS! Written at {timestamp}"

    with open(filepath, "w") as f:
        f.write(content)

    files = os.listdir(MOUNT_PATH)
    print(json.dumps({"files_in_mount": files[:20]}))


if __name__ == "__main__":
    main()
  • CreationPermissions for S3Files AccessPoint should be set to enable the user (UID 1001) to create S3 prefix (/ecs), as well as perform read and write operations on S3 objects under that prefix.

  • MountPoints in ContainerDefinitions must be configured and the ContainerPath specifies where S3 Files is mounted inside the container.

  • Inbound traffic from ECS Task for S3 Files Mount Target's security group.

  • Outbound traffic to S3 Files Mount Target for ECS Task's security group.

EC2

  # ===== EC2 =====
  S3FilesAccessPointEc2:
    Type: AWS::S3Files::AccessPoint
    Properties:
      FileSystemId: !Ref S3FilesSystem
      PosixUser:
        Gid: 1002
        Uid: 1002
      RootDirectory:
        Path: "/ec2"
        CreationPermissions:
          OwnerGid: 1002
          OwnerUid: 1002
          Permissions: "755"

  S3FilesSecurityGroupIngressEc2:
    Type: AWS::EC2::SecurityGroupIngress
    Properties:
      GroupId: !Ref S3FilesSecurityGroup
      IpProtocol: tcp
      FromPort: 2049
      ToPort: 2049
      SourceSecurityGroupId: !Ref Ec2SecurityGroup

  Ec2SecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: !Sub "${AWS::StackName}-ec2-sg"
      VpcId: !Ref VpcId
      SecurityGroupEgress:
        - IpProtocol: tcp
          FromPort: 2049
          ToPort: 2049
          DestinationSecurityGroupId: !Ref S3FilesSecurityGroup
        - IpProtocol: tcp
          FromPort: 443
          ToPort: 443
          CidrIp: 0.0.0.0/0
          Description: SSM and S3 via NAT Gateway

  Ec2InstanceRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal:
              Service: ec2.amazonaws.com
            Action: sts:AssumeRole
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore
        - arn:aws:iam::aws:policy/AmazonS3FilesClientReadWriteAccess
      Policies:
        - PolicyName: !Sub "${AWS::StackName}-Ec2S3Access"
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Effect: Allow
                Action:
                  - s3:GetObject
                  - s3:GetObjectVersion
                Resource: !Sub "arn:aws:s3:::${S3Bucket}/*"

  Ec2InstanceProfile:
    Type: AWS::IAM::InstanceProfile
    Properties:
      Roles:
        - !Ref Ec2InstanceRole

  # NOTE: Ensure the S3 VPC Endpoint or NAT Gateway is configured to allow access between VPC and S3
  Ec2Instance:
    Type: AWS::EC2::Instance
    DependsOn: S3FilesMountTarget
    Properties:
      ImageId: !Ref Ec2AmiId
      InstanceType: t3.micro
      SubnetId: !Ref SubnetId
      IamInstanceProfile: !Ref Ec2InstanceProfile
      SecurityGroupIds:
        - !Ref Ec2SecurityGroup
      UserData:
        Fn::Base64: !Sub |
          #!/bin/bash
          set -e

          # Mount S3 Files access point
          sudo yum install -y amazon-efs-utils
          sudo mkdir -p /mnt/ec2
          sudo mount -t s3files -o accesspoint=${S3FilesAccessPointEc2.AccessPointId} ${SC3FilesSystem.FileSystemId} /mnt/ec2

          # Test writing a file (to be deleted)
          TIMESTAMP=$(date +%s)
          FILE="/mnt/ec2/demo-$TIMESTAMP.txt"
          echo "Hello from EC2! Written at $TIMESTAMP" > "$FILE"
  • CreationPermissions for S3Files AccessPoint should be set to enable the user (UID 1002) to create S3 prefix (/ec2), as well as perform read and write operations on S3 objects under that prefix.

  • Command to mount S3 Files using an access point.

  • Inbound traffic from EC2 for S3 Files Mount Target's security group.

  • Outbound traffic to S3 Files Mount Target for EC2's security group.