Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
2eeb2b3
feat: add ECS and AgentCore container build/package/deploy support
singledigit May 15, 2026
56c763e
fix: resolve ruff PLR2004 lint error and Windows integration test fai…
singledigit May 18, 2026
ec83c23
fix: skip container image integration test on Windows CI
singledigit May 18, 2026
6ca8f92
fix: address PR reviewer findings - remove substring false-positive a…
singledigit May 18, 2026
977701d
ci: retrigger CI (pyinstaller-linux infra flake)
singledigit May 19, 2026
8d1a612
ci: retrigger CI (go setup infra flake)
singledigit May 19, 2026
ca5a310
fix: resolve merge conflict with upstream develop
singledigit May 28, 2026
abfdb31
fix: use empty ResourcesToBuildCollector in ECS sync _build_from_scratch
singledigit May 28, 2026
d992b50
fix(package): rewrite AWS::Include Location buried inside language-ex…
bnusunny May 28, 2026
3714cf8
refactor: Replace custom copytree() with stdlib shutil.copytree (#8934)
bnusunny May 29, 2026
e2044fe
chore: bump version to 1.161.1 (#9051)
bnusunny May 29, 2026
65c12d1
ci: unpin durable functions emulator image tag (#9054)
bnusunny Jun 1, 2026
bb2a26f
feat: add ECS and AgentCore container build/package/deploy support
singledigit May 15, 2026
0ba929d
ci: retrigger CI (pyinstaller-linux infra flake)
singledigit May 19, 2026
2915d43
ci: retrigger CI (go setup infra flake)
singledigit May 19, 2026
854065b
fix: address bnusunny PR review findings for ECS/AgentCore container …
singledigit Jun 6, 2026
99822c6
fix: raise ExportFailedError in _get_target_index for multi-container…
singledigit Jun 6, 2026
79965b4
style: black formatting fixes for ecs_container_sync_flow and app_bui…
singledigit Jun 6, 2026
b3a7232
style: black formatting fix for test_ecs_container_sync_flow
singledigit Jun 7, 2026
9d09336
fix: resolve mypy no-any-return in _register_updated_task_definition
singledigit Jun 7, 2026
5f39dd8
fix: update tests for intentional ECS/AgentCore split and multi-conta…
singledigit Jun 8, 2026
9c60e9e
chore: regenerate schema to include AWS::ECS::TaskDefinition in sync …
singledigit Jun 8, 2026
b4d96e4
ci: retrigger CI (windows uv setup infra flake)
singledigit Jun 8, 2026
1a7d7f3
ci: retrigger CI (pyinstaller-linux tar infra flake)
singledigit Jun 9, 2026
5c458ab
fix: remove duplicate resource_metadata assignment in artifact_exporter
singledigit Jun 10, 2026
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
1 change: 0 additions & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,6 @@ jobs:
run: pytest -vv ${{ matrix.tests_config.params }}
env:
FORCE_RUN_DOCKER_TEST: ${{ matrix.tests_config.name == 'durable-functions' && '1' || '' }}
DURABLE_EXECUTIONS_EMULATOR_IMAGE_TAG: ${{ matrix.tests_config.name == 'durable-functions' && 'v1.1.1' || '' }}

smoke-and-functional-tests:
name: ${{ matrix.tests_config.name }} / ${{ matrix.tests_config.os }} / ${{ matrix.python }}
Expand Down
68 changes: 68 additions & 0 deletions PR_DESCRIPTION.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
#### Which issue(s) does this change fix?

Fixes #8933

#### Why is this change necessary?

SAM CLI provides an excellent developer experience for Lambda Image functions (`sam build && sam deploy`), but users deploying containerized workloads to ECS (Fargate) or Bedrock AgentCore must manage their Docker build/push/deploy pipeline separately — even when these resources live in the same CloudFormation template. This creates a fragmented workflow requiring external tooling for an identical operation: build image → push to ECR → deploy.

#### How does it address the issue?

Extends the existing Lambda Image build pipeline to recognize `AWS::ECS::TaskDefinition` and `AWS::BedrockAgentCore::Runtime` resources with a `Metadata` block containing `Dockerfile` and `DockerContext`. No new commands — `sam build`, `sam package`, `sam deploy`, and `sam sync` gain awareness of these resource types.

**Template example:**
```yaml
Resources:
MyAgent:
Type: AWS::BedrockAgentCore::Runtime
Metadata:
Dockerfile: Dockerfile
DockerContext: ./agent
Architecture: arm64
Properties:
AgentRuntimeArtifact:
ContainerConfiguration:
ContainerUri: placeholder

MyTask:
Type: AWS::ECS::TaskDefinition
Metadata:
Dockerfile: Dockerfile
DockerContext: ./app
ContainerName: web
Properties:
ContainerDefinitions:
- Name: web
Image: placeholder
```

**Key implementation details:**
- Reuses `_build_lambda_image()` — same Docker build logic, buildkit support included
- `--resolve-image-repos` auto-creates ECR repos via companion stack
- `ContainerName` metadata targets specific containers in multi-container TaskDefinitions
- `Architecture` metadata sets `--platform` (e.g., `arm64` for AgentCore)
- `ARTIFACT_TYPE = ZIP` to pass the `PackageType` filter (these resources don't have `PackageType`)
- No SAM Transform changes needed — uses native CloudFormation resource types

**Design document:** `designs/container_image_builds_ecs_agentcore.md`

#### What side effects does this change have?

- `sam build` logs "Found N container service resource(s) to build" when applicable resources are present. No behavior change for templates without these resources.
- `--resolve-image-repos` creates ECR repos for ECS/AgentCore in addition to Lambda Image functions.
- `_update_built_resource` adds an optional `metadata` parameter (backward compatible, defaults to `None`).

#### Mandatory Checklist
**PRs will only be reviewed after checklist is complete**

- [x] Review the [generative AI contribution guidelines](https://github.com/aws/aws-sam-cli/blob/develop/CONTRIBUTING.md#ai-usage)
- [x] Add input/output [type hints](https://docs.python.org/3/library/typing.html) to new functions/methods
- [x] Write design document if needed ([Do I need to write a design document?](https://github.com/aws/aws-sam-cli/blob/develop/DEVELOPMENT_GUIDE.md#design-document))
- [x] Write/update unit tests
- [x] Write/update integration tests
- [x] Write/update functional tests if needed
- [x] `make pr` passes
- [x] `make update-reproducible-reqs` if dependencies were changed
- [ ] Write documentation

By submitting this pull request, I confirm that my contribution is made under the terms of the [Apache 2.0 license](https://www.apache.org/licenses/LICENSE-2.0).
234 changes: 234 additions & 0 deletions designs/container_image_builds_ecs_agentcore.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,234 @@
Container Image Builds for ECS and AgentCore
=============================================

This is the design for extending `sam build`, `sam package`, `sam deploy`, and `sam sync`
to support building and deploying container images for non-Lambda resources:
`AWS::ECS::TaskDefinition` and `AWS::BedrockAgentCore::Runtime`.

What is the problem?
--------------------

SAM CLI provides an excellent developer experience for Lambda Image functions: a single
`sam build && sam deploy` builds the Docker image, pushes to ECR, and deploys via
CloudFormation. However, users deploying containerized workloads to ECS (Fargate) or
Bedrock AgentCore must manage their Docker build/push/deploy pipeline separately, even
when these resources live in the same CloudFormation template alongside Lambda functions.

This creates a fragmented workflow where developers need external tooling (shell scripts,
Makefiles, or CI/CD steps) for the identical operation: build image → push to ECR →
update template with ECR URI → deploy.

What will be changed?
---------------------

We extend the existing Lambda Image build pipeline to recognize `AWS::ECS::TaskDefinition`
and `AWS::BedrockAgentCore::Runtime` resources that have a `Metadata` block with
`Dockerfile` and `DockerContext`. No new commands are introduced — the existing
`sam build`, `sam package`, `sam deploy`, and `sam sync` gain awareness of these
resource types.

### Design Principles

1. **Same convention** — Uses the identical Metadata block as Lambda Image functions
(Dockerfile, DockerContext, DockerTag, DockerBuildArgs, DockerBuildTarget)
2. **No Transform changes** — Works with native CloudFormation resource types
3. **Opt-in** — Only resources with the Metadata block are affected; existing templates
work unchanged
4. **Reuse** — Delegates to the same `_build_lambda_image()` Docker build logic

Success criteria for the change
-------------------------------

1. `sam build` discovers and builds container images for ECS TaskDefinitions and
AgentCore Runtimes that have Dockerfile metadata
2. `sam deploy --resolve-image-repos` auto-creates ECR repos for these resources
3. `sam package` / `sam deploy` pushes images to ECR and rewrites the template with
the ECR URI at the correct property path
4. `sam sync` builds, pushes, and triggers redeployment for these resources
5. Multi-container ECS TaskDefinitions can target a specific container via `ContainerName`
6. Architecture can be specified via `Architecture` metadata (e.g., `arm64`)
7. Buildkit support works automatically (shared with Lambda Image builds)
8. No regressions for existing Lambda, Layer, or API builds

User Experience
---------------

### Template Format

```yaml
Resources:
# AgentCore Runtime
MyAgent:
Type: AWS::BedrockAgentCore::Runtime
Metadata:
Dockerfile: Dockerfile
DockerContext: ./agent
DockerTag: latest
Architecture: arm64
Properties:
AgentRuntimeName: my_agent
AgentRuntimeArtifact:
ContainerConfiguration:
ContainerUri: placeholder
NetworkConfiguration:
NetworkMode: PUBLIC
RoleArn: !GetAtt AgentRole.Arn

# ECS TaskDefinition (multi-container)
MyTask:
Type: AWS::ECS::TaskDefinition
Metadata:
Dockerfile: Dockerfile
DockerContext: ./app
DockerTag: latest
ContainerName: web
Properties:
Family: my-app
ContainerDefinitions:
- Name: envoy
Image: public.ecr.aws/envoy:latest
- Name: web
Image: placeholder
```

### CLI Usage

```bash
# Build container images
sam build

# Deploy with auto ECR repo creation
sam deploy --resolve-image-repos

# Or with explicit repo
sam deploy --image-repositories SimpleAgent=123456789012.dkr.ecr.us-east-1.amazonaws.com/repo

# Live sync
sam sync --stack-name my-stack --watch --resolve-image-repos
```

### Metadata Fields

| Field | Required | Description |
|-------|----------|-------------|
| `Dockerfile` | Yes | Path to Dockerfile relative to DockerContext |
| `DockerContext` | Yes | Build context directory relative to template |
| `DockerTag` | No | Image tag (default: `latest`) |
| `DockerBuildArgs` | No | Dict of build args |
| `DockerBuildTarget` | No | Multi-stage build target |
| `DockerBuildExtraParams` | No | List of extra docker build params |
| `Architecture` | No | Target platform: `x86_64` (default) or `arm64` |
| `ContainerName` | No | ECS only: target container in multi-container TaskDefinition |

Implementation
--------------

### Architecture

```
┌─────────────────────────────────────────────────────────────────┐
│ sam build │
├─────────────────────────────────────────────────────────────────┤
│ BuildContext.run() │
│ ├── builder.build() → Lambda functions + layers │
│ └── _build_container_images() → ECS + AgentCore containers │
│ ├── SamContainerServiceProvider (discovery) │
│ ├── ContainerBuildDefinition (build graph) │
│ └── ApplicationBuilder.build_container_images() │
│ └── _build_lambda_image() (shared Docker logic) │
├─────────────────────────────────────────────────────────────────┤
│ sam package / sam deploy │
│ ├── sync_ecr_stack() → auto-creates ECR repos (companion) │
│ ├── ECSTaskDefinitionImageResource.export() → push + rewrite │
│ └── AgentCoreRuntimeImageResource.export() → push + rewrite │
├─────────────────────────────────────────────────────────────────┤
│ sam sync │
│ └── ECSContainerSyncFlow │
│ ├── gather_resources() → build image │
│ ├── sync() → push to ECR + force ECS deployment │
│ └── SyncFlowFactory (registered for both types) │
└─────────────────────────────────────────────────────────────────┘
```

### Key Components

**`samcli/lib/providers/sam_container_provider.py`** (new)
- `SamContainerServiceProvider`: Scans stacks for ECS/AgentCore resources with
Dockerfile+DockerContext metadata. Returns `ContainerService` NamedTuples.

**`samcli/lib/build/build_graph.py`** (modified)
- `ContainerBuildDefinition`: Parallel to `FunctionBuildDefinition`. Holds resource
identifier, type, metadata, and architecture. Reads `Architecture` from metadata.

**`samcli/lib/build/app_builder.py`** (modified)
- `build_container_images()`: Iterates container definitions and builds each.
- `_build_container_image()`: Delegates to `_build_lambda_image()` — same Docker logic.
- `_update_built_resource()`: Extended for ECS (`ContainerDefinitions[N].Image`) and
AgentCore (`AgentRuntimeArtifact.ContainerConfiguration.ContainerUri`). Accepts
optional `metadata` param for `ContainerName` targeting.

**`samcli/lib/package/packageable_resources.py`** (modified)
- `ECSTaskDefinitionImageResource`: Custom export for nested `ContainerDefinitions[0].Image`.
- `AgentCoreRuntimeImageResource`: Export using jmespath for deeply nested property path.
- Both use `ARTIFACT_TYPE = ZIP` to pass the `PackageType` filter (these resources
don't have a `PackageType` property).

**`samcli/lib/sync/flows/ecs_container_sync_flow.py`** (new)
- `ECSContainerSyncFlow`: Builds image, pushes to ECR, forces ECS service redeployment
by finding services using the task definition family.

**`samcli/lib/bootstrap/companion_stack/companion_stack_manager.py`** (modified)
- `sync_ecr_stack()`: Extended to include container service resources when creating
ECR repos via the companion stack.

### Property Path Mapping

| Resource Type | Property Path for Image URI |
|---------------|----------------------------|
| `AWS::Serverless::Function` | `ImageUri` |
| `AWS::Lambda::Function` | `Code.ImageUri` |
| `AWS::ECS::TaskDefinition` | `ContainerDefinitions[N].Image` |
| `AWS::BedrockAgentCore::Runtime` | `AgentRuntimeArtifact.ContainerConfiguration.ContainerUri` |

Alternatives Considered
-----------------------

### 1. New SAM Transform resource type (e.g., `AWS::Serverless::ContainerService`)

**Rejected because:**
- Requires changes to the SAM Transform (separate repo, separate approval process)
- Adds coupling between SAM CLI and the Transform
- Users would need to wait for Transform support in all regions
- Native CFN types already work and are well-understood

### 2. Separate `sam container build` command

**Rejected because:**
- Fragments the workflow — users would need to remember different commands
- Doesn't integrate with `sam deploy` and `sam sync` naturally
- The existing `sam build` already handles image builds for Lambda

### 3. Using `PackageType: Image` on ECS/AgentCore resources

**Rejected because:**
- `PackageType` is a Lambda-specific concept not present on ECS or AgentCore resources
- Would require CloudFormation schema changes
- The Metadata-based approach is already the established pattern

Breaking Changes
----------------

None. This is purely additive:
- Templates without ECS/AgentCore Metadata are unaffected
- The `_update_built_resource` signature change is backward compatible (optional param)
- No existing CLI flags or behaviors change

Future Extensions
-----------------

1. **Multiple Dockerfiles per ECS TaskDefinition** — Build multiple containers from
one resource using a list of Metadata entries
2. **`sam local start-ecs`** — Local testing of ECS containers (similar to `sam local start-api`)
3. **Health check integration** — Wait for container health before marking sync complete
4. **Build caching** — Layer-aware caching for container builds (currently rebuilds fully)
5. **`sam init` templates** — Starter templates for ECS+SAM and AgentCore+SAM projects
18 changes: 17 additions & 1 deletion docs/cfn-language-extensions.md
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,22 @@ The following intrinsic functions are resolved locally during expansion:

Functions that require deployed resources (`Fn::GetAtt`, `Fn::ImportValue`, `Fn::GetAZs`) are preserved for CloudFormation to resolve at deploy time.

## AWS::Include processing order

When a template uses both `Fn::Transform: AWS::Include` and
`Transform: AWS::LanguageExtensions`, SAM CLI processes the inline
`AWS::Include` macros **before** running language-extension expansion
locally. This mirrors CloudFormation's server-side transform pipeline,
where `Fn::Transform` macros are resolved before
`AWS::LanguageExtensions`.

The practical effect is that `AWS::Include` `Location` rewrites work
correctly even when the include lives buried inside language-extension
functions like `Fn::ToJsonString` or `Fn::ForEach` bodies, because the
include is rewritten while still structurally visible — before
`Fn::ToJsonString` collapses subtrees into JSON-string literals or
`Fn::ForEach` expands resources.

## Validation errors

The following template issues are caught locally before the SAM transform runs:
Expand All @@ -330,7 +346,7 @@ The following template issues are caught locally before the SAM transform runs:
| The `Fn::ForEach` value is malformed — not a list, doesn't have exactly 3 elements, or has a non-string loop identifier. | `Fn::ForEach::<key> layout is incorrect` (raised as `InvalidTemplateException`; see `samcli/lib/cfn_language_extensions/processors/foreach.py`). |
| More than 5 levels of `Fn::ForEach` are nested. | `Fn::ForEach nesting depth of <N> exceeds the maximum allowed depth of 5. CloudFormation supports up to 5 nested Fn::ForEach loops.` |
| The collection resolves to an empty list (e.g., a `CommaDelimitedList` parameter with `Default: ""`). | No error — the loop is silently skipped and no resources are emitted. |
| The `!Ref` in the collection points at a parameter that is not declared in the template. | No error in the typical `sam build` / `sam package` flow. SAM CLI runs intrinsic resolution in PARTIAL mode and preserves the unresolved `{"Ref": "<name>"}`. CloudFormation will reject the unresolved ref at deploy time. |
| The `!Ref` in the collection points at a parameter that is not declared in the template. | No error in the typical `sam build` / `sam package` flow. SAM CLI runs intrinsic resolution in PARTIAL mode and preserves the unresolved `{"Ref": "<name>"}`. At deploy time, CloudFormation will resolve it server side. |

## Limitations

Expand Down
2 changes: 1 addition & 1 deletion samcli/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
SAM CLI version
"""

__version__ = "1.161.0"
__version__ = "1.161.1"
Loading