Docker Bundling
Một số generators (như ts#agent và py#agent) tạo ra một Docker image được đẩy lên Amazon ECR và được sử dụng bởi hạ tầng AWS. Hướng dẫn này mô tả mẫu mà chúng tuân theo để bạn có thể áp dụng nó cho các trường hợp sử dụng khác — ví dụ, chạy một dự án FastAPI trên Amazon ECS, hoặc triển khai một Express server được container hóa.
Mẫu được khuyến nghị có ba phần:
- Một
bundletarget trên dự án của bạn tạo ra một thư mục tự chứa các artifact runtime. Đối với TypeScript, đây là một JavaScript bundle đơn file, được tree-shaken, được tạo ra bởi Rolldown; đối với Python, đây là mộtrequirements.txtvà các dependencies đã cài đặt được tạo ra bởi uv. - Một
Dockerfiletối thiểu chỉ đơn giảnCOPYđầu ra bundle vào một base image. Vì bundling đã xử lý tree-shaking và cài đặt dependencies,Dockerfilekhông cần chạynpm installhoặcuv sync. - Một
dockertarget sao chépDockerfilecùng với đầu ra bundle (để Docker build context chỉ chứa các file cần thiết tại runtime), sau đó chạydocker build.
Docker build context được ghi vào thư mục dist của dự án bạn. Infrastructure as code của bạn (CDK hoặc Terraform) sau đó trỏ đến thư mục đó để đẩy image lên ECR.
TypeScript
Phần tiêu đề “TypeScript”Bundle Target
Phần tiêu đề “Bundle Target”Cấu hình một bundle target gọi Rolldown. Nếu bạn đang bắt đầu từ một ts#project, thêm phần sau vào project.json của bạn:
{ "targets": { "bundle": { "cache": true, "executor": "nx:run-commands", "outputs": ["{workspaceRoot}/dist/{projectRoot}/bundle"], "options": { "command": "rolldown -c rolldown.config.ts", "cwd": "{projectRoot}" }, "dependsOn": ["compile"] } }}Và một rolldown.config.ts tại thư mục gốc của dự án bạn:
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', },]);Chạy target bundle để tạo ra dist/packages/my-project/bundle/index.js:
pnpm nx bundle my-projectyarn nx bundle my-projectnpx nx bundle my-projectbunx nx bundle my-projectDockerfile
Phần tiêu đề “Dockerfile”Tạo một Dockerfile trong thư mục nguồn dự án của bạn. File này không làm gì nhiều hơn là COPY bundle vào một Node base image, cộng với npm install bất kỳ packages external nào không thể được bundle. Đặt bước RUN npm install trước COPY, để Docker có thể cache layer node_modules đã cài đặt và chỉ chạy lại nó khi danh sách dependencies thực sự thay đổi:
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 Target
Phần tiêu đề “Docker Target”Thêm target docker để:
- Sao chép
Dockerfilevào thư mục đầu ra bundle (để build context chỉ chứa bundle +Dockerfile), và - Chạy
docker build(tùy chọn cho CDK — xem bên dưới).
{ "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"] } }}Chạy target này tạo ra một local image được gắn thẻ my-scope-my-project:latest, được xây dựng từ context tối thiểu tại dist/packages/my-project/bundle/:
pnpm nx docker my-projectyarn nx docker my-projectnpx nx docker my-projectbunx nx docker my-projectPython
Phần tiêu đề “Python”Bundle Target
Phần tiêu đề “Bundle Target”Cấu hình một bundle target sử dụng uv để export và cài đặt dependencies cho nền tảng mục tiêu của bạn. Generator py#project và generator py#lambda-function đều cấu hình điều này cho bạn. Cấu hình target trông như sau:
{ "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"] } }}Chạy nx bundle my-project tạo ra dist/packages/my-project/bundle-arm/ chứa nguồn dự án của bạn, các dependencies của nó, và một requirements.txt — mọi thứ image cần tại runtime.
Dockerfile
Phần tiêu đề “Dockerfile”Dockerfile đơn giản sao chép bundle vào Python base image. Vì uv đã cài đặt tất cả dependency vào thư mục bundle, bạn không cần chạy pip install bên trong image:
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 Target
Phần tiêu đề “Docker Target”Thêm một docker target sao chép Dockerfile vào thư mục đầu ra bundle, sau đó chạy docker build:
{ "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"] } }}Điều này xóa thư mục đầu ra, sau đó sao chép cả nội dung bundle và Dockerfile vào dist/.../docker, trở thành Docker build context.
pnpm nx docker my-projectyarn nx docker my-projectnpx nx docker my-projectbunx nx docker my-projectQuét Images với Trivy
Phần tiêu đề “Quét Images với Trivy”Thực hành tốt là quét images của bạn để tìm các lỗ hổng đã biết. Các generators tuân theo mẫu này thêm một trivy target quét built image với Trivy, chạy từ ECR-hosted Trivy image, và thoát với mã khác không khi có phát hiện HIGH hoặc CRITICAL.
Thêm một trivy target dependsOn docker target của bạn. Nó lưu built image vào một tarball và quét nó thông qua một bind mount tương đối với workspace, do đó cùng một lệnh hoạt động dưới cả docker và 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"] } }}Các scan targets per-image của mỗi dự án được tổng hợp dưới một trivy target toàn workspace, mà script gốc trivy được cung cấp chạy (nx run-many --target trivy).
pnpm trivyyarn trivynpm run trivybun trivyInfrastructure
Phần tiêu đề “Infrastructure”Kết nối thư mục build-context kết quả với infrastructure as code giống nhau cho cả TypeScript và Python — chỉ đường dẫn đến thư mục build-context khác nhau (dist/packages/my-project/bundle cho TypeScript, dist/packages/my-project/docker cho Python).
Sử dụng DockerImageAsset của CDK trỏ đến thư mục build-context. CDK sẽ xây dựng image và xuất bản nó lên CDK asset ECR repository tại thời điểm deploy:
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,});Helper findWorkspaceRoot được tạo ra bởi generator ts#infra và được export từ @my-scope/common-constructs. Nếu bạn không sử dụng shared constructs, bạn có thể hardcode đường dẫn đến thư mục dist tương đối với nơi cdk được gọi từ — thường là workspace root — và bỏ qua hoàn toàn lời gọi findWorkspaceRoot.
Sử dụng DockerImageAsset với bất kỳ AWS construct nào chấp nhận container image, ví dụ aws_ecs.ContainerImage.fromDockerImageAsset(image).
AWS provider của Terraform không có resource “build and push a Docker image” hạng nhất. Mẫu được sử dụng bởi các generators là:
buildtarget của dự án chạydocker build, tạo ra một local image được gắn thẻmy-scope-my-project:latest.- Một
aws_ecr_repositoryđể giữ image. - Một
null_resourcevới mộtlocal-execprovisioner xác thực với ECR, gắn lại thẻ locally-built image với content digest của nó, và đẩy nó. - Resource downstream (ví dụ:
aws_ecs_task_definition) tham chiếu image bằng thẻ bất biến, dựa trên digest của nó.
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 }}Block data.external.docker_digest đảm bảo null_resource chạy lại bất cứ khi nào local image hash thay đổi, kích hoạt một push mới trên mọi thay đổi code có ý nghĩa. Image được đẩy dưới một thẻ bất biến, dựa trên nội dung được lấy từ digest của nó, do đó ECR repository có thể sử dụng IMMUTABLE tag mutability và từ chối bất kỳ nỗ lực nào ghi đè một thẻ hiện có.
Đọc Thêm
Phần tiêu đề “Đọc Thêm”- Generator
ts#agent— một ví dụ hoàn chỉnh về mẫu này cho một TypeScript agent được triển khai đến Bedrock AgentCore Runtime. - Generator
py#agent— tương đương cho Python. - Tài liệu Rolldown — tham chiếu cấu hình cho TypeScript bundler.
- Tài liệu
uv— tham chiếu cho Python dependency export và install.