
AgentCore RuntimeにSession Storage・S3 Files・EFSを同時マウントしてCDKで構築・検証してみた
はじめに
前回の記事ではインタラクティブシェルの基本操作を試しましたが、今回はストレージマウント機能にフォーカスした追加検証です。
公式ドキュメントでは最大5つのファイルシステムをマウントできるとされています。利用可能なストレージは3種類です。
| ストレージ | 提供形態 | セッション間のデータ共有 | データの寿命 | 構成更新(デプロイ)時 | VPC |
|---|---|---|---|---|---|
| Session Storage | マネージド (Preview) | 不可 | 同一セッションの stop/resume 間で保持。構成更新・14日間アイドルで削除 | データ削除 | 不要 |
| S3 Files | 自前(BYO) | 同じ Access Point をマウントしたセッション間で可 | 削除するまで永続(S3と同期) | データ自体への影響なし | 必要 |
| EFS | 自前(BYO) | 同じ Access Point をマウントしたセッション間で可 | 削除するまで永続 | データ自体への影響なし | 必要 |
比較表の永続性は公式ドキュメントの記載に基づいています。セッション間の共有・分離については今回の検証で動作確認しました。
VPCやNAT Gatewayの構築手順についてはこちらの記事で説明しています。
検証環境
| 項目 | 値 |
|---|---|
| リージョン | ap-northeast-1(東京) |
| aws-cdk-lib | 2.258.0 |
| aws-cdk CLI | 2.1126.0 |
| @aws/agentcore CLI | 0.18.0 |
| VPC | 既存VPC(Isolated Subnet 3AZ) |
| 検証日 | 2026-06-09 |
aws-cdk-lib/aws-s3files モジュール(L1コンストラクト)を利用するため、aws-cdk-lib 2.258.0以降が必要です。
環境構築(CDK)
アーキテクチャ
┌─ StorageTestStack ─────────────────────────┐
│ EFS FileSystem + Access Point │
│ S3 Files FileSystem + Access Point │
│ 共有 Security Group (TCP 2049) │
│ マウントターゲット (3AZ) │
└────────────────────────────────────────────┘
↓ 参照
┌─ RuntimeTestStack ─────────────────────────┐
│ AgentCore Runtime (VPCモード) │
│ /mnt/workspace ← Session Storage │
│ /mnt/efs ← EFS │
│ /mnt/s3data ← S3 Files │
│ Security Group (→ TCP 2049 アウトバウンド) │
│ IAM Role (EFS + S3 Files 権限) │
└────────────────────────────────────────────┘
プロジェクト構成
agentcore-storage-test/
├── bin/app.ts
├── lib/
│ ├── storage-stack.ts # EFS + S3 Files + SG + マウントターゲット
│ └── runtime-stack.ts # AgentCore Runtime (VPCモード + filesystem)
├── docker-compose.yml # インタラクティブシェル接続用
├── cdk.json
├── package.json
└── tsconfig.json
CDK L2 の制限と escape hatch
aws-cdk-lib/aws-bedrockagentcore のRuntime L2コンストラクトは filesystemConfigurations プロパティを未サポートです。
2026-06-09時点、CDK 2.258.0で確認しました。escape hatchでCfnRuntimeに直接設定しています。
const cfnRuntime = runtime.node.defaultChild as agentcore.CfnRuntime;
cfnRuntime.addPropertyOverride('FilesystemConfigurations', [
{
SessionStorage: {
MountPath: '/mnt/workspace',
},
},
{
EfsAccessPoint: {
AccessPointArn: storage.efsAccessPoint.accessPointArn,
MountPath: '/mnt/efs',
},
},
{
S3FilesAccessPoint: {
AccessPointArn: storage.s3FilesAccessPointArn,
MountPath: '/mnt/s3data',
},
},
]);
VPC 接続と Security Group
既存VPCを参照し、EFS / S3 Filesのマウントターゲット用に共有のSecurity Groupを作成します。
const vpcId = this.node.tryGetContext('vpcId');
this.vpc = ec2.Vpc.fromLookup(this, 'Vpc', { vpcId });
this.mountTargetSg = new ec2.SecurityGroup(this, 'MountTargetSg', {
vpc: this.vpc,
description: 'Allow NFS from AgentCore Runtime',
allowAllOutbound: false,
});
Runtime側からマウントターゲットへTCP 2049を許可します。connections.allowTo はRuntime側のアウトバウンドとマウントターゲット側のインバウンドの両方にルールを追加します。
runtime.connections.allowTo(
storage.mountTargetSg, ec2.Port.tcp(2049), 'NFS to EFS/S3Files'
);
IAM 権限
Runtimeのロールには以下の権限を付与しました。ClientMount / ClientWrite に加え、今回の構成ではRuntime作成時のバリデーションでDescribe系も必要でした。
| サービス | アクション | 用途 |
|---|---|---|
| EFS | elasticfilesystem:ClientMount | マウント |
| EFS | elasticfilesystem:ClientWrite | 書き込み |
| EFS | elasticfilesystem:DescribeAccessPoints | 作成時バリデーション |
| EFS | elasticfilesystem:DescribeMountTargets | 作成時バリデーション |
| S3 Files | s3files:ClientMount | マウント |
| S3 Files | s3files:ClientWrite | 書き込み |
| S3 Files | s3files:GetAccessPoint | 作成時バリデーション |
| S3 Files | s3files:ListMountTargets | 作成時バリデーション |
| S3 Files | s3files:DescribeMountTargets | 作成時バリデーション |
今回の検証では、Describe系が不足している状態でRuntime作成時に400エラーが発生し、エラーメッセージに不足権限が示されました。まとめて付与しておくのがおすすめです。
デプロイ
npm install
npx cdk deploy StorageTestStack --require-approval never
npx cdk deploy RuntimeTestStack --require-approval never
インタラクティブシェル接続
接続手順の詳細は前回の記事を参照してください。今回は以下のdocker-compose.ymlを使用しました。Runtime ARNは自分の環境に合わせて変更してください。
services:
shell:
image: node:20-slim
stdin_open: true
tty: true
environment:
- AWS_ACCESS_KEY_ID
- AWS_SECRET_ACCESS_KEY
- AWS_SESSION_TOKEN
- AWS_REGION=ap-northeast-1
working_dir: /work
command:
- sh
- -c
- |
npm install -g @aws/agentcore 2>/dev/null &&
agentcore exec --it --runtime <YOUR_RUNTIME_ARN> --region ap-northeast-1
検証結果
マウントの確認
df でマウント状態を確認しました。
Filesystem 1K-blocks Used Available Use% Mounted on
overlay 9186644 1759644 6938760 21% /
127.0.0.1:/export 1048576 0 1048576 0% /mnt/workspace
127.0.0.1:/ 9007199254739968 0 9007199254739968 0% /mnt/efs
127.0.0.1:/ 9007199254739968 0 9007199254739968 0% /mnt/s3data
3種すべてが正常にマウントされています。
- Session Storage (
/mnt/workspace): 約1GiB(df上の表示値) - EFS (
/mnt/efs): 約8EiB(NFS上の表示値) - S3 Files (
/mnt/s3data): 約8EiB(同上)
セッション間のデータ共有テスト
2つのターミナルから別セッションに接続しました。それぞれ異なるinstance IDが割り当てられていました。
- セッション1: instance A
- セッション2: instance B
セッション1で書き込み
echo "hello from session1" > /mnt/s3data/test.txt
echo "hello from session1" > /mnt/efs/test.txt
echo "hello from session1" > /mnt/workspace/test.txt
セッション2で確認
cat /mnt/s3data/test.txt # → "hello from session1" ✅
cat /mnt/efs/test.txt # → "hello from session1" ✅
ls /mnt/workspace/ # → 空(ファイルなし) ✅
結果まとめ
| マウント | セッション間 | 反映タイミング |
|---|---|---|
/mnt/s3data (S3 Files) |
共有 | close-to-open(ファイルを閉じた後、別セッションで開くと反映) |
/mnt/efs (EFS) |
共有 | 即時(今回の検証では書き込み直後に別セッションから参照可能) |
/mnt/workspace (Session Storage) |
独立 | 自セッションのみ |
S3 バケットへの同期確認
今回の検証では、Runtime内で /mnt/s3data/test.txt に書き込み後、約1分後にS3バケット側で確認できました。
$ aws s3 ls s3://storageteststack-s3filesbucketXXXXXXXX-XXXXXXXXXXXX/agentcore/
2026-06-09 15:20:09 21 test.txt
Runtimeのインタラクティブシェルにはファイル転送機能がないため、S3 FilesはRuntime外部との入出力の中継点として活用できます。S3バケット側に反映されたオブジェクトはRuntimeから参照でき、Runtimeで作成したファイルもS3バケット側への同期後にダウンロードできます。
注意事項
通信経路
ローカル PC
↓ HTTPS/WebSocket (SigV4認証)
bedrock-agentcore.ap-northeast-1.amazonaws.com
↓ AWS内部経路(利用者のVPCを通らない)
microVM
↓ TCP 2049 (NFS over TLS)
マウントターゲット (EFS / S3 Files) ← 利用者VPC内
- シェル接続はAgentCore API経由で、NAT GatewayやSSMは不要です
- ファイルシステムへのアクセスはmicroVM → マウントターゲット間の通信です。マウントターゲットのSecurity GroupでRuntimeからのTCP 2049(NFS)インバウンドを許可する必要があります
Session Storage のデータ消失条件
Session Storageのデータは、Runtimeの構成更新(デプロイ)時に削除されます。セッションが14日間アイドル状態の場合も削除されます。
EFS / S3 Files の共有利用について
今回の構成ではAccess Pointの posixUser を 1000:1000 に固定しているため、全セッションが同じUID/GIDでアクセスします。ファイルパーミッションによる利用者単位のアクセス制御はできないため、複数名で同じRuntimeを共有する場合は他者が読み書きできる前提でご利用ください。
S3 Filesを中継用・一時データ置き場として使う場合は、バッキングバケットにライフサイクルルールを設定して保持期間を限定しておくと管理しやすくなります。
まとめ
AgentCore RuntimeのSession Storage・S3 Files・EFSを1つのRuntimeに同時マウントし、セッション間でのデータ共有・分離を検証しました。
S3 FilesとEFSはセッション間で共有され、Session Storageはセッションごとに独立していることを確認できました。ファイルパスへの入出力を前提とするアプリケーションでは、S3 FilesやEFSをマウントしておくことで、通常のファイル操作として外部ストレージにデータを保存できます。
一方、出力先をスクリプト側で制御できる場合は、SDKやCLIで直接S3に保存する方がシンプルです。VPCやマウントターゲットの構築要否も含め、要件に応じて使い分けるのがよさそうです。
参考リンク
CDKコード全文
lib/storage-stack.ts
import * as cdk from 'aws-cdk-lib';
import * as ec2 from 'aws-cdk-lib/aws-ec2';
import * as efs from 'aws-cdk-lib/aws-efs';
import * as s3 from 'aws-cdk-lib/aws-s3';
import * as s3files from 'aws-cdk-lib/aws-s3files';
import * as iam from 'aws-cdk-lib/aws-iam';
import { Construct } from 'constructs';
export class StorageStack extends cdk.Stack {
public readonly vpc: ec2.IVpc;
public readonly mountTargetSg: ec2.SecurityGroup;
public readonly efsAccessPoint: efs.AccessPoint;
public readonly s3FilesAccessPointArn: string;
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
const vpcId = this.node.tryGetContext('vpcId');
this.vpc = ec2.Vpc.fromLookup(this, 'Vpc', { vpcId });
this.mountTargetSg = new ec2.SecurityGroup(this, 'MountTargetSg', {
vpc: this.vpc,
description: 'Allow NFS from AgentCore Runtime',
allowAllOutbound: false,
});
// EFS
const fileSystem = new efs.FileSystem(this, 'Efs', {
vpc: this.vpc,
vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE_ISOLATED },
securityGroup: this.mountTargetSg,
performanceMode: efs.PerformanceMode.GENERAL_PURPOSE,
removalPolicy: cdk.RemovalPolicy.DESTROY,
});
this.efsAccessPoint = fileSystem.addAccessPoint('AgentCoreAP', {
path: '/agentcore',
createAcl: { ownerGid: '1000', ownerUid: '1000', permissions: '755' },
posixUser: { gid: '1000', uid: '1000' },
});
// S3 Files
const bucket = new s3.Bucket(this, 'S3FilesBucket', {
versioned: true,
removalPolicy: cdk.RemovalPolicy.DESTROY,
autoDeleteObjects: true,
});
const s3FilesRole = new iam.Role(this, 'S3FilesRole', {
assumedBy: new iam.ServicePrincipal('elasticfilesystem.amazonaws.com'),
});
s3FilesRole.addToPolicy(new iam.PolicyStatement({
actions: ['s3:ListBucket*'],
resources: [bucket.bucketArn],
}));
s3FilesRole.addToPolicy(new iam.PolicyStatement({
actions: [
's3:AbortMultipartUpload', 's3:DeleteObject',
's3:GetObject*', 's3:List*', 's3:PutObject*',
],
resources: [bucket.arnForObjects('*')],
}));
s3FilesRole.addToPolicy(new iam.PolicyStatement({
actions: [
'events:DeleteRule', 'events:DisableRule', 'events:EnableRule',
'events:PutRule', 'events:PutTargets', 'events:RemoveTargets',
],
resources: [`arn:${cdk.Aws.PARTITION}:events:*:*:rule/DO-NOT-DELETE-S3-Files*`],
conditions: {
StringEquals: { 'events:ManagedBy': 'elasticfilesystem.amazonaws.com' },
},
}));
s3FilesRole.addToPolicy(new iam.PolicyStatement({
actions: [
'events:DescribeRule', 'events:ListRuleNamesByTarget',
'events:ListRules', 'events:ListTargetsByRule',
],
resources: [`arn:${cdk.Aws.PARTITION}:events:*:*:rule/*`],
}));
const s3FilesFs = new s3files.CfnFileSystem(this, 'S3FilesFs', {
bucket: bucket.bucketArn,
roleArn: s3FilesRole.roleArn,
});
const subnets = this.vpc.isolatedSubnets.length > 0
? this.vpc.isolatedSubnets
: this.vpc.privateSubnets;
subnets.forEach((subnet, i) => {
new s3files.CfnMountTarget(this, `S3FilesMt${i}`, {
fileSystemId: s3FilesFs.attrFileSystemId,
subnetId: subnet.subnetId,
securityGroups: [this.mountTargetSg.securityGroupId],
});
});
const s3FilesAp = new s3files.CfnAccessPoint(this, 'S3FilesAP', {
fileSystemId: s3FilesFs.attrFileSystemId,
rootDirectory: {
path: '/agentcore',
creationPermissions: { ownerGid: '1000', ownerUid: '1000', permissions: '755' },
},
posixUser: { gid: '1000', uid: '1000' },
});
this.s3FilesAccessPointArn = s3FilesAp.attrAccessPointArn;
}
}
lib/runtime-stack.ts
import * as cdk from 'aws-cdk-lib';
import * as ec2 from 'aws-cdk-lib/aws-ec2';
import * as iam from 'aws-cdk-lib/aws-iam';
import * as agentcore from 'aws-cdk-lib/aws-bedrockagentcore';
import { Construct } from 'constructs';
import { StorageStack } from './storage-stack';
interface RuntimeStackProps extends cdk.StackProps {
storage: StorageStack;
}
export class RuntimeStack extends cdk.Stack {
constructor(scope: Construct, id: string, props: RuntimeStackProps) {
super(scope, id, props);
const { storage } = props;
const imageUri = this.node.tryGetContext('agentcoreImageUri');
const runtime = new agentcore.Runtime(this, 'Runtime', {
runtimeName: 'storageTest',
agentRuntimeArtifact: agentcore.AgentRuntimeArtifact.fromImageUri(imageUri),
networkConfiguration: agentcore.RuntimeNetworkConfiguration.usingVpc(this, {
vpc: storage.vpc,
vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE_ISOLATED },
}),
});
runtime.connections.allowTo(
storage.mountTargetSg, ec2.Port.tcp(2049), 'NFS to EFS/S3Files'
);
runtime.role.addToPrincipalPolicy(new iam.PolicyStatement({
actions: ['ecr:GetAuthorizationToken'],
resources: ['*'],
}));
runtime.role.addToPrincipalPolicy(new iam.PolicyStatement({
actions: [
'ecr:BatchGetImage',
'ecr:GetDownloadUrlForLayer',
'ecr:BatchCheckLayerAvailability',
],
resources: [
`arn:aws:ecr:${this.region}:${this.account}:repository/bedrock-agentcore-agent`,
],
}));
runtime.role.addToPrincipalPolicy(new iam.PolicyStatement({
actions: ['elasticfilesystem:ClientMount', 'elasticfilesystem:ClientWrite'],
resources: [storage.efsAccessPoint.fileSystem.fileSystemArn],
conditions: {
StringEquals: {
'elasticfilesystem:AccessPointArn': storage.efsAccessPoint.accessPointArn,
},
},
}));
runtime.role.addToPrincipalPolicy(new iam.PolicyStatement({
actions: [
'elasticfilesystem:DescribeAccessPoints',
'elasticfilesystem:DescribeMountTargets',
],
resources: ['*'],
}));
runtime.role.addToPrincipalPolicy(new iam.PolicyStatement({
actions: [
's3files:ClientMount', 's3files:ClientWrite', 's3files:GetAccessPoint',
's3files:ListMountTargets', 's3files:DescribeMountTargets',
],
resources: ['*'],
}));
const cfnRuntime = runtime.node.defaultChild as agentcore.CfnRuntime;
cfnRuntime.addPropertyOverride('FilesystemConfigurations', [
{
SessionStorage: {
MountPath: '/mnt/workspace',
},
},
{
EfsAccessPoint: {
AccessPointArn: storage.efsAccessPoint.accessPointArn,
MountPath: '/mnt/efs',
},
},
{
S3FilesAccessPoint: {
AccessPointArn: storage.s3FilesAccessPointArn,
MountPath: '/mnt/s3data',
},
},
]);
}
}







