Bỏ qua để đến nội dung

Docker Bundling

Một số generators (như ts#agentpy#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.

Diagram

Mẫu được khuyến nghị có ba phần:

  1. Một bundle target 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ột requirements.txt và các dependencies đã cài đặt được tạo ra bởi uv.
  2. Một Dockerfile tối thiểu chỉ đơn giản COPY đầu ra bundle vào một base image. Vì bundling đã xử lý tree-shaking và cài đặt dependencies, Dockerfile không cần chạy npm install hoặc uv sync.
  3. Một docker target sao chép Dockerfile cùng với đầu ra bundle (để Docker build context chỉ chứa các file cần thiết tại runtime), sau đó chạy docker 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.

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:

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',
},
]);

Chạy target bundle để tạo ra dist/packages/my-project/bundle/index.js:

Terminal window
pnpm nx bundle my-project

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 application
COPY index.js /app
EXPOSE 8080
CMD ["node", "index.js"]

Thêm target docker để:

  1. Sao chép Dockerfile vào thư mục đầu ra bundle (để build context chỉ chứa bundle + Dockerfile), và
  2. 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/:

Terminal window
pnpm nx docker my-project

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 đơ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=/app
ENV PATH="/app/bin:${PATH}"
CMD ["python", "-m", "my_project.main"]

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.

Terminal window
pnpm nx docker my-project

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ả dockerfinch:

{
"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).

Terminal window
pnpm trivy

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:

Diagram
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).

  • 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.