For those who are interested, here is the Japanese version of this blog.
Hello! I'm Law, who loves my cat Ringo-chan(リンゴちゃん).
In this article, I'll share how we achieved complete automation of AI agent deployment to Amazon Bedrock AgentCore Runtime using AWS CDK L2 Construct.
Notice
The@aws-cdk/aws-bedrock-agentcore-alphapackage used in this article is an alpha release. Please carefully consider its use in production environments at your own responsibility. Also, the API may change.
- Introduction
- Solution Architecture
- Project Overview
- Power of L2 Construct: Comparison of Required Resource Definitions
- Internal Operation of fromAsset() and DockerImageAsset
- Two Approaches to Image Building
- ARM64 Build Challenges and Solutions
- CI/CD Pipeline
- Gotchas
- Summary
- References
- Bonus
Introduction
Amazon Bedrock AgentCore Runtime is a managed service that allows you to run AI agents serverlessly. However, traditional deployment methods had the following challenges:
- Explicitly defining numerous AWS resources such as ECR repositories, CodeBuild, Lambda, and custom resources
- Managing complex build pipeline dependencies
- Detailed configuration of IAM roles and policies
By leveraging AWS CDK L2 Construct and GitHub Actions, we achieved complete automation with minimal resource definitions.
Solution Architecture
Below is an architecture diagram showing the overall flow from GitHub Actions to Bedrock AgentCore Runtime deployment:

Key Components:
- GitHub Actions: Access AWS with OIDC authentication, build Docker images
- AWS CDK: Define infrastructure as code, deploy CloudFormation stacks
- ECR: Store images in shared repository created by CDK bootstrap
- IAM: Automatically create execution roles and permissions for AgentCore Runtime
- AgentCore Runtime: Run containers and invoke Bedrock models
Project Overview
Structure
.
├── .github/
│ └── workflows/
│ └── deploy.yml # CI/CD pipeline
├── agent/
│ ├── agent.py # Strands agent
│ ├── Dockerfile # ARM64 container
│ └── requirements.txt # Python dependencies
├── lib/
│ └── stack.ts # CDK stack (using L2 construct)
└── bin/
└── app.ts # CDK app
Technology Stack
- AWS CDK: L2 Construct (
@aws-cdk/aws-bedrock-agentcore-alpha) - GitHub Actions: CI/CD pipeline
- Docker Buildx + QEMU: ARM64 cross-platform build
- OIDC Authentication: No long-term credentials required
Power of L2 Construct: Comparison of Required Resource Definitions
Before: L1 Construct - Explicitly Define 6 AWS Resources
With traditional L1 Construct, you needed to explicitly define all of the following resources to deploy AgentCore Runtime:
// 1. ECR Repository const repository = new ecr.Repository(this, 'Repository', { repositoryName: 'simple-agent', removalPolicy: cdk.RemovalPolicy.DESTROY, }); // 2. CodeBuild Project (for Docker build) const buildProject = new codebuild.Project(this, 'BuildProject', { environment: { buildImage: codebuild.LinuxArmBuildImage.AMAZON_LINUX_2_STANDARD_3_0, privileged: true, }, buildSpec: codebuild.BuildSpec.fromObject({ version: '0.2', phases: { pre_build: { commands: [ 'echo Logging in to Amazon ECR...', 'aws ecr get-login-password --region $AWS_DEFAULT_REGION | docker login ...', ], }, build: { commands: [ 'docker build --platform linux/arm64 -t $IMAGE_REPO_NAME:$IMAGE_TAG .', 'docker tag $IMAGE_REPO_NAME:$IMAGE_TAG $AWS_ACCOUNT_ID.dkr.ecr...', ], }, post_build: { commands: ['docker push $AWS_ACCOUNT_ID.dkr.ecr...'], }, }, }), }); // 3. Lambda Function (for CodeBuild trigger) const buildTrigger = new lambda.Function(this, 'BuildTrigger', { runtime: lambda.Runtime.PYTHON_3_12, handler: 'index.handler', code: lambda.Code.fromInline(` import boto3 import cfnresponse def handler(event, context): codebuild = boto3.client('codebuild') # Build start logic (50+ lines) ... `), timeout: cdk.Duration.minutes(5), }); // 4. Custom Resource (execute build on deployment) const triggerBuild = new cdk.CustomResource(this, 'TriggerBuild', { serviceToken: buildTrigger.functionArn, properties: { ProjectName: buildProject.projectName, Timestamp: Date.now(), }, }); // 5. IAM Role (for AgentCore Runtime) const agentRole = new iam.Role(this, 'AgentRole', { assumedBy: new iam.ServicePrincipal('bedrock-agentcore.amazonaws.com'), managedPolicies: [ iam.ManagedPolicy.fromAwsManagedPolicyName('CloudWatchLogsFullAccess'), ], }); repository.grantPull(agentRole); // 6. AgentCore Runtime (L1 Construct) const agentRuntime = new cdk.CfnResource(this, 'AgentRuntime', { type: 'AWS::BedrockAgentCore::Runtime', properties: { AgentRuntimeName: 'simpleagent', RoleArn: agentRole.roleArn, AgentRuntimeArtifact: { ContainerConfiguration: { ContainerUri: `${repository.repositoryUri}:latest`, }, }, }, }); // Explicitly set dependencies agentRuntime.node.addDependency(triggerBuild);
Resources that need explicit definition:
- ECR Repository
- CodeBuild Project
- Lambda Function
- Custom Resource
- IAM Role
- AgentCore Runtime
After: L2 Construct - Define Only 1 Resource
With L2 Construct, you only need to define AgentCore Runtime:
import * as agentcore from '@aws-cdk/aws-bedrock-agentcore-alpha'; const runtime = new agentcore.Runtime(this, 'AgentRuntime', { runtimeName: 'simpleagent2', agentRuntimeArtifact: agentcore.AgentRuntimeArtifact.fromAsset('./agent', { platform: cdk.aws_ecr_assets.Platform.LINUX_ARM64, }), networkConfiguration: agentcore.RuntimeNetworkConfiguration.usingPublicNetwork(), protocolConfiguration: agentcore.ProtocolType.HTTP, }); // Add Bedrock invocation permissions runtime.addToRolePolicy( new iam.PolicyStatement({ actions: ["bedrock:InvokeModel*"], resources: [`arn:aws:bedrock:*::foundation-model/*`], }) );
Note: With L2 Construct, Docker container builds are performed in the environment where
cdk deployis executed (local machine or CI/CD runner), not in CodeBuild.
Resources that need explicit definition: 1. AgentCore Runtime (only!)
Automatically created and managed resources:
- ECR Repository (uses shared repository pre-created by CDK bootstrap)
- Docker image build and push (automatically executed by DockerImageAsset)
- IAM Role (automatically created by Runtime)
- ECR pull permissions (automatically granted)
- Dependency resolution (automatic)
What's Great About This?
1. Clear Infrastructure Intent
- L1: Write implementation details of "how to build"
- L2: Write only "what to deploy"
2. Reduced Maintenance Burden
- L1: Manage all of CodeBuild's buildspec, Lambda's build logic, and IAM policies
- L2: Manage only AgentCore Runtime configuration
3. Automatic Application of Best Practices
- L1: Implement IAM permissions, dependencies, and error handling yourself
- L2: Automatically apply patterns verified by the CDK team
4. Flexibility for Changes
- L1: Changing build method requires modifying multiple resources
- L2: Just change
fromAsset()options
Internal Operation of fromAsset() and DockerImageAsset
The fromAsset() of L2 Construct operates with the following processing flow:

1. Constructor Execution (new Runtime())
// fromAsset() just returns an AssetImage instance agentRuntimeArtifact: agentcore.AgentRuntimeArtifact.fromAsset('./agent', { platform: cdk.aws_ecr_assets.Platform.LINUX_ARM64, })
At this point, DockerImageAsset is not created.
2. CloudFormation Template Generation (cdk synth)
Lazy evaluation is registered via Lazy.any() inside the Runtime constructor:
// Internal processing of runtime.js (inside constructor) const cfnProps = { agentRuntimeName: this.agentRuntimeName, roleArn: this.role.roleArn, agentRuntimeArtifact: Lazy.any({ produce: () => this.renderAgentRuntimeArtifact() // Register lazy evaluation }), // ... };
When cdk synth is executed, produce() is called at CloudFormation template generation timing, and renderAgentRuntimeArtifact() is executed:
// Implementation of renderAgentRuntimeArtifact() renderAgentRuntimeArtifact() { this.agentRuntimeArtifact.bind(this, this); // Call bind() here const config = this.agentRuntimeArtifact._render(); // ... }
3. Details of bind() Method
The bind() method is called on the AssetImage instance created by fromAsset() and creates the actual DockerImageAsset:
// Implementation of runtime-artifact.js class AssetImage extends AgentRuntimeArtifact { private asset?: assets.DockerImageAsset; private bound = false; public bind(scope: Construct, runtime: Runtime): void { // Create DockerImageAsset (first time only) if (!this.asset) { const hash = md5hash(this.directory); // Calculate hash from directory path this.asset = new assets.DockerImageAsset(scope, `AgentRuntimeArtifact${hash}`, { directory: this.directory, ...this.options, // Options like platform: LINUX_ARM64 }); } // Grant ECR pull permissions (first time only) if (!this.bound) { this.asset.repository.grantPull(runtime.role); this.bound = true; } } }
What the bind() method does:
- Hash Calculation: Generate hash from directory path with
md5hash(directory) - DockerImageAsset Creation: Create unique resource including hash in ID
- Record Asset Information: Record build information in
cdk.out/manifest.json - Grant IAM Permissions: Automatically grant ECR pull permissions to Runtime's IAM role
- Ensure Idempotency: Safe even if called multiple times with
boundflag
Important: Lazy evaluation is registered with Lazy.any(), and actual processing is performed inside the bind() method.
4. Deployment (cdk deploy)
The cdk-assets tool reads manifest.json:
- Build Docker image
- Authenticate to ECR
- Push image
- Deploy CloudFormation stack
Important Point: Docker build is executed during cdk deploy, not during cdk synth. If the hash doesn't change, rebuild can be skipped.
Two Approaches to Image Building
The method adopted this time uses CDK's fromAsset() to automatically build and push images during cdk deploy. However, another approach is also possible:
Approach 1: CDK Integrated (Adopted in This Article)
agentRuntimeArtifact: agentcore.AgentRuntimeArtifact.fromAsset('./agent', { platform: cdk.aws_ecr_assets.Platform.LINUX_ARM64, })
- Pros: Simple, minimal resource definitions
- Cons: Build time is included in
cdk deploy
Approach 2: Pre-Build Type
Define ECR repository independently with CDK, call CodeBuild from GitHub Actions to build and push image, then execute cdk deploy:
const repository = new ecr.Repository(this, 'Repository'); const buildProject = new codebuild.Project(this, 'BuildProject', { /* ... */ }); agentRuntimeArtifact: agentcore.AgentRuntimeArtifact.fromEcrRepository( repository, 'latest' )
# GitHub Actions - name: Build and push image run: aws codebuild start-build --project-name $PROJECT_NAME - name: Deploy CDK stack run: npx cdk deploy --require-approval never
- Pros: Separation of build and deployment, flexible control of build cache
- Cons: More resource definitions, more complex pipeline
Both approaches are valid, but choose Approach 1 if you prioritize simplicity, and Approach 2 if you want fine-grained control over the build process.
ARM64 Build Challenges and Solutions
AgentCore Runtime requires ARM64 architecture, but how do you build on GitHub Actions (x86_64)?
Solution: QEMU + Docker Buildx
- name: Set up QEMU uses: docker/setup-qemu-action@v3 # ARM64 emulation - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 # Cross-platform build - name: Deploy CDK stack run: npx cdk deploy --require-approval never
Explicitly specify platform in Dockerfile as well:
FROM --platform=linux/arm64 python:3.12-slim WORKDIR /app COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt COPY agent.py . EXPOSE 8080 CMD ["uvicorn", "agent:app", "--host", "0.0.0.0", "--port", "8080"]
CI/CD Pipeline
Secure with OIDC Authentication
Adopted OIDC authentication instead of traditional Access Key/Secret Key:
- name: Configure AWS credentials uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: ${{ secrets.AWS_ROLE_ARN }} aws-region: us-west-2
Benefits:
- No need to manage long-term credentials
- Automatically issue temporary credentials
- Access only from specific repositories and branches
Deployment Flow
Code Change → Create PR → Review → Merge to main → Auto Deploy (5 min)
Avoid unnecessary builds with path filters:
on: push: branches: - main paths: - 'folder/subfolder/**'
Gotchas
1. Bedrock Permissions Not Automatically Granted
The IAM role automatically created by L2 Construct does not include permissions to call Bedrock API.
// Need to add manually runtime.addToRolePolicy( new iam.PolicyStatement({ actions: ["bedrock:InvokeModel*"], resources: [`arn:aws:bedrock:*::foundation-model/*`], }) );
2. Cross-Region Access
Even if Runtime is deployed to US West (Oregon, us-west-2), it may call Claude Sonnet 4 in US East (N. Virginia, us-east-1). Handle this by using wildcard (*) for region:
resources: [`arn:aws:bedrock:*::foundation-model/*`]
3. CDK Bootstrap Required
L2 Construct uses CDK's asset publishing system, so cdk bootstrap is required in advance:
cdk bootstrap aws://ACCOUNT_ID/us-west-2
Summary
By leveraging AWS CDK L2 Construct, we were able to greatly simplify the complex deployment of AgentCore Runtime.
What We Learned:
- High level of abstraction in L2 Construct
- Lazy evaluation mechanism with
fromAsset()andbind()methods - Processing flow during CloudFormation generation using
Lazy.any() - Implementation of ARM64 cross-platform build
- Practical use of OIDC authentication
References
Bonus
For those wondering who Ringo-chan(リンゴちゃん) is, here's a photo. Christmas is still about 2 months away, but the temperature has dropped suddenly, so please be careful not to catch a cold!

ロータッヘイ(執筆記事の一覧)
24卒入社の香港人です。
2025 Japan All AWS Certifications Engineers
リンゴちゃん(デボンレックス)にいつも癒されています。