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

Đóng gói Docker

Một số generator (như ts#strands-agentpy#strands-agent) tạo ra 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 thiết kế mà chúng tuân theo để bạn có thể áp dụng cho các trường hợp sử dụng khác — ví dụ, chạy dự án py#fast-api trên Amazon ECS, hoặc triển khai máy chủ Express được container hóa.

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

  1. Target bundle trên dự án của bạn tạo ra một thư mục độc lập chứa các artifact runtime. Đối với TypeScript, đây là một JavaScript bundle đơn file đã được tree-shaken do Rolldown tạo ra; đối với Python, đây là requirements.txt và các dependency đã cài đặt do uv tạo ra.
  2. Dockerfile tối giản chỉ đơn giản COPY đầu ra bundle vào base image. Vì việc bundling đã xử lý tree-shaking và cài đặt dependency, Dockerfile không cần chạy npm install hoặc uv sync.
  3. Target docker 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. 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 target bundle gọi Rolldown. Nếu bạn bắt đầu 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 file rolldown.config.ts tại thư mục gốc của dự á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',
inlineDynamicImports: true,
},
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 Dockerfile trong thư mục source của dự án. File này chỉ làm việc COPY bundle vào Node base image, cộng với npm install bất kỳ package external nào không thể được bundle. Đặt bước RUN npm install trước lệnh COPY, để Docker có thể cache layer node_modules đã cài đặt và chỉ chạy lại khi danh sách dependency 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 image local được tag my-scope-my-project:latest, được build từ context tối giản tại dist/packages/my-project/bundle/:

Terminal window
pnpm nx docker my-project

Cấu hình target bundle sử dụng uv để export và cài đặt dependency cho nền tảng đích 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 source của dự án, các dependency, và 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.12-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 target docker 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

Kết nối thư mục build-context kết quả với infrastructure as code là giống nhau cho cả TypeScript và Python — chỉ có đườ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ẽ build image và publish 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 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).