Docker Bundling
いくつかのジェネレーター(ts#agent や py#agent など)は、Amazon ECR にプッシュされ、AWS インフラストラクチャによって使用される Docker イメージを生成します。このガイドでは、これらが従うパターンについて説明し、他のユースケース(たとえば、Amazon ECS で FastAPI プロジェクトを実行したり、コンテナ化された Express サーバーをデプロイしたりする場合)に適用できるようにします。
推奨されるパターンには 3 つの要素があります:
bundleターゲット - ランタイムアーティファクトの自己完結型ディレクトリを生成するプロジェクトのターゲット。TypeScript の場合、これは Rolldown によって生成されるツリーシェイクされた単一ファイルの JavaScript バンドルです。Python の場合、これは uv によって生成されるrequirements.txtとインストールされた依存関係です。- 最小限の
Dockerfile- バンドル出力をベースイメージに単純にCOPYするだけのもの。バンドリングがすでにツリーシェイクと依存関係のインストールを処理しているため、Dockerfileはnpm installやuv syncを実行する必要がありません。 dockerターゲット -Dockerfileをバンドル出力と一緒にコピーし(Docker ビルドコンテキストにランタイムに必要なファイルのみが含まれるようにします)、その後docker buildを実行します。
Docker ビルドコンテキストは、プロジェクトの dist フォルダーに書き込まれます。その後、インフラストラクチャコード(CDK または Terraform)がそのディレクトリを指して、イメージを ECR にプッシュします。
TypeScript
Section titled “TypeScript”Bundle ターゲット
Section titled “Bundle ターゲット”Rolldown を呼び出す bundle ターゲットを設定します。ts#project から始める場合は、project.json に以下を追加します:
{ "targets": { "bundle": { "cache": true, "executor": "nx:run-commands", "outputs": ["{workspaceRoot}/dist/{projectRoot}/bundle"], "options": { "command": "rolldown -c rolldown.config.ts", "cwd": "{projectRoot}" }, "dependsOn": ["compile"] } }}そして、プロジェクトのルートに rolldown.config.ts を作成します:
import { defineConfig } from 'rolldown';
export default defineConfig([ { tsconfig: 'tsconfig.lib.json', input: 'src/index.ts', output: { file: '../../dist/packages/my-project/bundle/index.js', format: 'cjs', codeSplitting: false, }, platform: 'node', },]);バンドルターゲットを実行して dist/packages/my-project/bundle/index.js を生成します:
pnpm nx bundle my-projectyarn nx bundle my-projectnpx nx bundle my-projectbunx nx bundle my-projectDockerfile
Section titled “Dockerfile”プロジェクトのソースディレクトリに Dockerfile を作成します。このファイルは、バンドルを Node ベースイメージに COPY するだけで、バンドルできなかった external パッケージを npm install します。RUN npm install ステップを COPY の前に配置して、Docker がインストールされた node_modules レイヤーをキャッシュし、依存関係リストが実際に変更された場合にのみ再実行できるようにします:
FROM public.ecr.aws/docker/library/node:lts
WORKDIR /app
# Install packages that cannot be bundled (declared as "external" in rolldown.config.ts).# Kept above the COPY so this layer is cached and only invalidated when the install list changes.RUN npm install @aws/aws-distro-opentelemetry-node-autoinstrumentation@0.10.0
# Copy bundled applicationCOPY index.js /app
EXPOSE 8080
CMD ["node", "index.js"]Docker ターゲット
Section titled “Docker ターゲット”以下を行う docker ターゲットを追加します:
Dockerfileをバンドル出力ディレクトリにコピーします(ビルドコンテキストにバンドル +Dockerfileのみが含まれるようにします)、そしてdocker buildを実行します(CDK の場合はオプション — 以下を参照)。
{ "targets": { "docker": { "cache": true, "executor": "nx:run-commands", "options": { "commands": [ "ncp packages/my-project/src/Dockerfile dist/packages/my-project/bundle/Dockerfile", "docker build --platform linux/arm64 -t my-scope-my-project:latest dist/packages/my-project/bundle" ], "parallel": false }, "dependsOn": ["bundle"] } }}このターゲットを実行すると、dist/packages/my-project/bundle/ の最小限のコンテキストからビルドされた、my-scope-my-project:latest とタグ付けされたローカルイメージが生成されます:
pnpm nx docker my-projectyarn nx docker my-projectnpx nx docker my-projectbunx nx docker my-projectPython
Section titled “Python”Bundle ターゲット
Section titled “Bundle ターゲット”uv を使用してターゲットプラットフォームの依存関係をエクスポートおよびインストールする bundle ターゲットを設定します。py#project ジェネレーターと py#lambda-function ジェネレーターの両方がこれを設定します。ターゲット設定は次のようになります:
{ "targets": { "bundle-arm": { "cache": true, "executor": "nx:run-commands", "outputs": ["{workspaceRoot}/dist/{projectRoot}/bundle-arm"], "options": { "commands": [ "uv export --frozen --no-dev --no-editable --project {projectRoot} --package my_project -o dist/{projectRoot}/bundle-arm/requirements.txt", "uv pip install -n --no-deps --no-installer-metadata --no-compile-bytecode --python-platform aarch64-manylinux_2_28 --target dist/{projectRoot}/bundle-arm -r dist/{projectRoot}/bundle-arm/requirements.txt" ], "parallel": false }, "dependsOn": ["compile"] } }}nx bundle my-project を実行すると、プロジェクトのソース、その依存関係、および requirements.txt を含む dist/packages/my-project/bundle-arm/ が生成されます — イメージがランタイムに必要とするすべてのものです。
Dockerfile
Section titled “Dockerfile”Dockerfile は、バンドルを Python ベースイメージに単純にコピーします。uv がすでにすべての依存関係をバンドルディレクトリにインストールしているため、イメージ内で pip install を実行する必要はありません:
FROM public.ecr.aws/docker/library/python:3.14-slim
WORKDIR /app
# Copy bundled package (source + installed dependencies)COPY . /app
EXPOSE 8080
ENV PYTHONPATH=/appENV PATH="/app/bin:${PATH}"
CMD ["python", "-m", "my_project.main"]Docker ターゲット
Section titled “Docker ターゲット”Dockerfile をバンドル出力ディレクトリにコピーし、その後 docker build を実行する docker ターゲットを追加します:
{ "targets": { "docker": { "cache": true, "executor": "nx:run-commands", "options": { "commands": [ "rimraf dist/packages/my-project/docker", "make-dir dist/packages/my-project/docker", "ncp dist/packages/my-project/bundle-arm dist/packages/my-project/docker", "ncp packages/my-project/src/Dockerfile dist/packages/my-project/docker/Dockerfile", "docker build --platform linux/arm64 -t my-scope-my-project:latest dist/packages/my-project/docker" ], "parallel": false }, "dependsOn": ["bundle-arm"] } }}これにより、出力ディレクトリがクリアされ、バンドルの内容と Dockerfile の両方が dist/.../docker にコピーされ、これが Docker ビルドコンテキストになります。
pnpm nx docker my-projectyarn nx docker my-projectnpx nx docker my-projectbunx nx docker my-projectTrivy でイメージをスキャンする
Section titled “Trivy でイメージをスキャンする”イメージに既知の脆弱性がないかスキャンすることは良い習慣です。このパターンに従うジェネレーターは、Trivy でビルドされたイメージをスキャンする trivy ターゲットを追加します。これは ECR でホストされている Trivy イメージから実行され、HIGH または CRITICAL の検出結果でゼロ以外の終了コードを返します。
docker ターゲットに dependsOn する trivy ターゲットを追加します。ビルドされたイメージを tarball に保存し、ワークスペース相対のバインドマウントを介してスキャンするため、同じコマンドが docker と finch の両方で機能します:
{ "targets": { "trivy": { "cache": true, "inputs": ["default", "^production"], "outputs": ["{workspaceRoot}/dist/{projectRoot}/trivy"], "executor": "nx:run-commands", "options": { "commands": [ "rimraf dist/packages/my-project/trivy", "make-dir dist/packages/my-project/trivy", "ncp packages/my-project/.trivyignore dist/packages/my-project/trivy/.trivyignore", "docker save -o dist/packages/my-project/trivy/image.tar my-scope-my-project:latest", "docker run --rm -v \"./dist/packages/my-project/trivy\":/scan public.ecr.aws/aquasecurity/trivy:0.72.0 image --input /scan/image.tar --ignorefile /scan/.trivyignore --scanners vuln --severity HIGH,CRITICAL --ignore-unfixed --exit-code 1 --no-progress -q" ], "parallel": false }, "dependsOn": ["docker"] } }}各プロジェクトのイメージごとのスキャンターゲットは、ワークスペース全体の trivy ターゲットの下に集約され、提供される trivy ルートスクリプトが実行します(nx run-many --target trivy)。
pnpm trivyyarn trivynpm run trivybun trivyInfrastructure
Section titled “Infrastructure”結果のビルドコンテキストディレクトリをインフラストラクチャコードに接続することは、TypeScript と Python の両方で同じです — ビルドコンテキストディレクトリへのパスのみが異なります(TypeScript の場合は dist/packages/my-project/bundle、Python の場合は dist/packages/my-project/docker)。
ビルドコンテキストディレクトリを指す CDK の DockerImageAsset を使用します。CDK は、デプロイ時にイメージをビルドし、CDK アセット ECR リポジトリに公開します:
import { DockerImageAsset, Platform } from 'aws-cdk-lib/aws-ecr-assets';import { findWorkspaceRoot } from '@my-scope/common-constructs';import * as path from 'path';import * as url from 'url';
const image = new DockerImageAsset(this, 'MyImage', { directory: path.join( // Resolve from the compiled construct location to the workspace root findWorkspaceRoot(url.fileURLToPath(new URL(import.meta.url))), 'dist/packages/my-project/bundle', ), platform: Platform.LINUX_ARM64,});findWorkspaceRoot ヘルパーは、ts#infra ジェネレーターによって生成され、@my-scope/common-constructs からエクスポートされます。共有コンストラクトを使用していない場合は、cdk が呼び出される場所(通常はワークスペースルート)からの dist ディレクトリへのパスをハードコードし、findWorkspaceRoot 呼び出しを完全に省略できます。
DockerImageAsset を、コンテナイメージを受け入れる任意の AWS コンストラクトで使用します。たとえば、aws_ecs.ContainerImage.fromDockerImageAsset(image) などです。
Terraform の AWS プロバイダーには、「Docker イメージをビルドしてプッシュする」ファーストクラスのリソースがありません。ジェネレーターが使用するパターンは次のとおりです:
- プロジェクトの
buildターゲットがdocker buildを実行し、my-scope-my-project:latestとタグ付けされたローカルイメージを生成します。 - イメージを保持する
aws_ecr_repository。 - ECR に認証し、ローカルにビルドされたイメージをそのコンテンツダイジェストで再タグ付けし、プッシュする
local-execプロビジョナーを持つnull_resource。 - ダウンストリームリソース(例:
aws_ecs_task_definition)は、不変のダイジェストベースのタグでイメージを参照します。
resource "aws_ecr_repository" "repo" { name = "my-project-repository" image_tag_mutability = "IMMUTABLE" force_delete = true}
# Invalidate the push whenever the locally-built image digest changesdata "external" "docker_digest" { program = ["sh", "-c", "echo '{\"digest\":\"'$(docker inspect my-scope-my-project:latest --format '{{.Id}}')'\"}'"]}
locals { # Content-based, immutable image tag derived from the local image digest image_tag = replace(data.external.docker_digest.result.digest, "sha256:", "")}
resource "null_resource" "docker_publish" { triggers = { docker_digest = data.external.docker_digest.result.digest repository_url = aws_ecr_repository.repo.repository_url image_tag = local.image_tag }
provisioner "local-exec" { command = <<-EOT aws ecr get-login-password --region ${data.aws_region.current.id} \ | docker login --username AWS --password-stdin ${self.triggers.repository_url} docker tag my-scope-my-project:latest ${self.triggers.repository_url}:${self.triggers.image_tag} docker push ${self.triggers.repository_url}:${self.triggers.image_tag} EOT }}data.external.docker_digest ブロックは、ローカルイメージハッシュが変更されるたびに null_resource が再実行されることを保証し、意味のあるコード変更ごとに新しいプッシュをトリガーします。イメージは、そのダイジェストから派生した不変のコンテンツベースのタグでプッシュされるため、ECR リポジトリは IMMUTABLE タグの可変性を使用でき、既存のタグを上書きしようとする試みを拒否できます。
ts#agentジェネレーター — Bedrock AgentCore Runtime にデプロイされる TypeScript エージェントのこのパターンの完全な例。py#agentジェネレーター — Python の同等のもの。- Rolldown ドキュメント — TypeScript バンドラーの設定リファレンス。
uvドキュメント — Python 依存関係のエクスポートとインストールのリファレンス。