Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions lambda-s3-files-cdk/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
node_modules
cdk.out
*.js
!src/**/*.js
*.d.ts
package-lock.json
134 changes: 134 additions & 0 deletions lambda-s3-files-cdk/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
# Lambda with Amazon S3 Files Mount

This pattern deploys a Lambda function with an Amazon S3 Files file system mounted as a local directory, enabling standard file operations on S3 data without downloading objects.

Learn more about this pattern at Serverless Land Patterns: https://serverlessland.com/patterns/lambda-s3-files-cdk

Important: this application uses various AWS services and there are costs associated with these services after the Free Tier usage - please see the [AWS Pricing page](https://aws.amazon.com/pricing/) for details. You are responsible for any AWS costs incurred. No warranty is implied in this example.

## Requirements

- [Create an AWS account](https://portal.aws.amazon.com/gp/aws/developer/registration/index.html) if you do not already have one and log in. The IAM user that you use must have sufficient permissions to make necessary AWS service calls and manage AWS resources.
- [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/install-cliv2.html) installed and configured
- [Git Installed](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git)
- [Node and NPM](https://nodejs.org/en/download/) installed
- [AWS CDK](https://docs.aws.amazon.com/cdk/latest/guide/cli.html) installed

## Deployment Instructions

1. Create a new directory, navigate to that directory in a terminal and clone the GitHub repository:
```bash
git clone https://github.com/aws-samples/serverless-patterns
```
2. Change directory to the pattern directory:
```bash
cd serverless-patterns/lambda-s3-files-cdk
```
3. Install CDK dependencies:
```bash
npm install
```
4. Deploy the stack:
```bash
cdk deploy
```

## How it works

[Amazon S3 Files](https://docs.aws.amazon.com/AmazonS3/latest/userguide/s3-files.html) provides NFS access to S3 buckets with full POSIX semantics. This pattern mounts an S3 bucket on a Lambda function at `/mnt/s3data`.

### What gets deployed

| Resource | Purpose |
|---|---|
| S3 Bucket | Data store backing the file system |
| VPC (2 AZs) | Network for Lambda and mount targets |
| S3 Files FileSystem | NFS file system linked to the S3 bucket |
| S3 Files MountTargets | Network endpoints in each private subnet |
| S3 Files AccessPoint | Application entry point (UID/GID 1000, root `/lambda`) |
| Security Group | Allows NFS traffic (port 2049) |
| Lambda Function | Reads, writes, and lists files via the mount |

### Architecture

```
┌──────────┐ ┌─────────────────────────────────────────┐
│ S3 Bucket│◄───►│ S3 Files FileSystem │
└──────────┘ │ (auto-sync between S3 and filesystem) │
└──────────────┬────────────────────────────┘
│ NFS (port 2049)
┌──────────────┴────────────────────────────┐
│ VPC │
│ ┌────────────────┐ ┌────────────────┐ │
│ │ Mount Target │ │ Mount Target │ │
│ │ (AZ-1) │ │ (AZ-2) │ │
│ └────────────────┘ └────────────────┘ │
│ ▲ │
│ │ │
│ ┌────────┴───────┐ │
│ │ Lambda Function│ │
│ │ /mnt/s3data │ │
│ └────────────────┘ │
└────────────────────────────────────────────┘
```

### Key S3 Files concepts

- **FileSystem** — A shared file system linked to your S3 bucket. Changes sync bidirectionally.
- **MountTarget** — Network endpoint in a specific AZ. Lambda must be in the same VPC/subnet.
- **AccessPoint** — Application-specific entry point with POSIX user identity and root directory.
- **High-performance storage** — Actively used data cached locally for sub-millisecond latency.

## Testing

1. After deployment, note the `FunctionName` and `BucketName` outputs.

2. **Write a file** through the Lambda mount:
```bash
aws lambda invoke \
--function-name <FunctionName> \
--payload '{"action": "write", "filename": "hello.txt", "content": "Hello from Lambda via S3 Files!"}' \
--cli-binary-format raw-in-base64-out \
output.json

cat output.json
```

3. **Verify the file appeared in S3** (sync takes ~1 minute):
```bash
aws s3 ls s3://<BucketName>/lambda/
```

4. **Read the file** back through Lambda:
```bash
aws lambda invoke \
--function-name <FunctionName> \
--payload '{"action": "read", "filename": "hello.txt"}' \
--cli-binary-format raw-in-base64-out \
output.json

cat output.json
```

5. **List directory** contents:
```bash
aws lambda invoke \
--function-name <FunctionName> \
--payload '{"action": "list"}' \
--cli-binary-format raw-in-base64-out \
output.json

cat output.json
```

## Cleanup

```bash
cdk destroy
```

---

Copyright 2026 Amazon.com, Inc. or its affiliates. All Rights Reserved.

SPDX-License-Identifier: MIT-0
7 changes: 7 additions & 0 deletions lambda-s3-files-cdk/bin/app.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#!/usr/bin/env node
import "source-map-support/register";
import * as cdk from "aws-cdk-lib";
import { LambdaS3FilesStack } from "../lib/lambda-s3-files-stack";

const app = new cdk.App();
new LambdaS3FilesStack(app, "LambdaS3FilesStack");
11 changes: 11 additions & 0 deletions lambda-s3-files-cdk/cdk.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"app": "npx ts-node --prefer-ts-exts bin/app.ts",
"watch": {
"include": ["**"],
"exclude": ["README.md", "cdk*.json", "**/*.d.ts", "**/*.js", "node_modules", "src"]
},
"context": {
"@aws-cdk/aws-lambda:recognizeLayerVersion": true,
"@aws-cdk/core:checkSecretUsage": true
}
}
61 changes: 61 additions & 0 deletions lambda-s3-files-cdk/example-pattern.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
{
"title": "AWS Lambda with Amazon S3 Files Mount",
"description": "Mount an Amazon S3 bucket as a local file system on AWS Lambda using Amazon S3 Files, enabling standard file operations (read, write, list) without downloading objects.",
"language": "TypeScript",
"level": "300",
"framework": "AWS CDK",
"introBox": {
"headline": "How it works",
"text": [
"This pattern deploys a Lambda function with an Amazon S3 Files file system mounted at /mnt/s3data. The function performs standard file operations (read, write, list) on S3 data using the local filesystem — no S3 API calls needed.",
"S3 Files provides NFS access to S3 buckets with sub-millisecond latency on small files and full POSIX semantics. The pattern creates a VPC, S3 Files file system, mount targets, access point, and a Lambda function wired together.",
"Multiple Lambda functions can connect to the same S3 Files file system simultaneously, sharing data through a common workspace without custom synchronization logic."
]
},
"gitHub": {
"template": {
"repoURL": "https://github.com/aws-samples/serverless-patterns/tree/main/lambda-s3-files-cdk",
"templateURL": "serverless-patterns/lambda-s3-files-cdk",
"projectFolder": "lambda-s3-files-cdk",
"templateFile": "lib/lambda-s3-files-stack.ts"
}
},
"resources": {
"bullets": [
{
"text": "Amazon S3 Files Documentation",
"link": "https://docs.aws.amazon.com/AmazonS3/latest/userguide/s3-files.html"
},
{
"text": "Configuring Amazon S3 Files access for Lambda",
"link": "https://docs.aws.amazon.com/lambda/latest/dg/configuration-filesystem-s3files.html"
},
{
"text": "Mounting S3 file systems on Lambda functions",
"link": "https://docs.aws.amazon.com/AmazonS3/latest/userguide/s3-files-mounting-lambda.html"
}
]
},
"deploy": {
"text": [
"cdk deploy"
]
},
"testing": {
"text": [
"See the GitHub repo for detailed testing instructions."
]
},
"cleanup": {
"text": [
"Delete the stack: <code>cdk destroy</code>."
]
},
"authors": [
{
"name": "Nithin Chandran R",
"bio": "Technical Account Manager at AWS",
"linkedin": "nithin-chandran-r"
}
]
}
126 changes: 126 additions & 0 deletions lambda-s3-files-cdk/lib/lambda-s3-files-stack.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import * as cdk from "aws-cdk-lib";
import * as lambda from "aws-cdk-lib/aws-lambda";
import * as s3 from "aws-cdk-lib/aws-s3";
import * as ec2 from "aws-cdk-lib/aws-ec2";
import * as iam from "aws-cdk-lib/aws-iam";
import { Construct } from "constructs";

export class LambdaS3FilesStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);

const mountPath = "/mnt/s3data";

// S3 bucket for the file system (versioning required by S3 Files)
const bucket = new s3.Bucket(this, "DataBucket", {
removalPolicy: cdk.RemovalPolicy.DESTROY,
autoDeleteObjects: true,
versioned: true,
});

// VPC for Lambda and S3 Files mount targets
const vpc = new ec2.Vpc(this, "Vpc", {
maxAzs: 2,
natGateways: 1,
});

// Security group allowing NFS traffic (port 2049)
const s3FilesSg = new ec2.SecurityGroup(this, "S3FilesSg", {
vpc,
description: "Allow NFS traffic for S3 Files mount targets",
});
s3FilesSg.addIngressRule(s3FilesSg, ec2.Port.tcp(2049), "NFS from Lambda");

// IAM role for S3 Files to access the bucket (uses EFS service principal)
const s3FilesRole = new iam.Role(this, "S3FilesRole", {
assumedBy: new iam.ServicePrincipal("elasticfilesystem.amazonaws.com"),
});
bucket.grantReadWrite(s3FilesRole);

// S3 Files FileSystem (L1 construct — no L2 yet)
const fileSystem = new cdk.CfnResource(this, "S3FileSystem", {
type: "AWS::S3Files::FileSystem",
properties: {
Bucket: bucket.bucketArn,
RoleArn: s3FilesRole.roleArn,
AcceptBucketWarning: true,
},
});

// Mount targets in each private subnet
const privateSubnets = vpc.privateSubnets;
const mountTargets = privateSubnets.map((subnet, i) => {
const mt = new cdk.CfnResource(this, `MountTarget${i}`, {
type: "AWS::S3Files::MountTarget",
properties: {
FileSystemId: fileSystem.getAtt("FileSystemId"),
SubnetId: subnet.subnetId,
SecurityGroups: [s3FilesSg.securityGroupId],
},
});
mt.addDependency(fileSystem);
return mt;
});

// Access point for Lambda (UID/GID 1000, root /lambda)
const accessPoint = new cdk.CfnResource(this, "S3FilesAccessPoint", {
type: "AWS::S3Files::AccessPoint",
properties: {
FileSystemId: fileSystem.getAtt("FileSystemId"),
PosixUser: { Uid: "1000", Gid: "1000" },
RootDirectory: {
Path: "/lambda",
CreationPermissions: {
OwnerUid: "1000",
OwnerGid: "1000",
Permissions: "755",
},
},
},
});
accessPoint.addDependency(fileSystem);

// Lambda function with S3 Files mount
const fn = new lambda.Function(this, "S3FilesFn", {
runtime: lambda.Runtime.NODEJS_22_X, // overridden to nodejs24.x below
handler: "index.handler",
code: lambda.Code.fromAsset("src"),
timeout: cdk.Duration.minutes(1),
memorySize: 512,
vpc,
vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS },
securityGroups: [s3FilesSg],
environment: { MOUNT_PATH: mountPath },
description: "Lambda function with S3 Files mount",
});

// Attach S3 Files filesystem config via escape hatch
const cfnFn = fn.node.defaultChild as lambda.CfnFunction;
cfnFn.addOverride("Properties.Runtime", "nodejs24.x");
cfnFn.fileSystemConfigs = [
{
arn: accessPoint.getAtt("AccessPointArn").toString(),
localMountPath: mountPath,
},
];

// Ensure mount targets are ready before Lambda
mountTargets.forEach((mt) => cfnFn.addDependency(mt));
cfnFn.addDependency(accessPoint);

// Lambda permissions for S3 Files and direct S3 reads
fn.addToRolePolicy(
new iam.PolicyStatement({
actions: ["s3files:ClientMount", "s3files:ClientWrite"],
resources: [accessPoint.getAtt("AccessPointArn").toString()],
})
);
bucket.grantRead(fn);

new cdk.CfnOutput(this, "FunctionName", { value: fn.functionName });
new cdk.CfnOutput(this, "BucketName", { value: bucket.bucketName });
new cdk.CfnOutput(this, "FileSystemId", {
value: fileSystem.getAtt("FileSystemId").toString(),
});
}
}
20 changes: 20 additions & 0 deletions lambda-s3-files-cdk/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"name": "lambda-s3-files-cdk",
"version": "1.0.0",
"bin": {
"app": "bin/app.ts"
},
"scripts": {
"build": "tsc",
"cdk": "cdk"
},
"dependencies": {
"aws-cdk-lib": "2.180.0",
"constructs": "10.4.2"
},
"devDependencies": {
"@types/node": "^22.0.0",
"ts-node": "^10.9.0",
"typescript": "~5.7.0"
}
}
Loading