Docker 打包
多个生成器(例如 ts#strands-agent 和 py#strands-agent)会生成一个 Docker 镜像,该镜像被推送到 Amazon ECR 并由 AWS 基础设施使用。本指南描述了它们遵循的模式,以便您可以将其应用于其他用例——例如,在 Amazon ECS 上运行 py#fast-api 项目,或部署容器化的 Express 服务器。
推荐的模式包含三个部分:
- 项目上的
bundle目标,它生成一个包含运行时构件的自包含目录。对于 TypeScript,这是由 Rolldown 生成的经过 tree-shaking 的单文件 JavaScript 包;对于 Python,这是由 uv 生成的requirements.txt和已安装的依赖项。 - 最小化的
Dockerfile,它只是将包输出COPY到基础镜像中。由于打包已经处理了 tree-shaking 和依赖项安装,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', inlineDynamicImports: true, }, platform: 'node', },]);运行 bundle 目标以生成 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。该文件只是将包 COPY 到 Node 基础镜像中,加上 npm install 任何无法打包的 external 包。将 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"] } }}运行此目标会生成一个标记为 my-scope-my-project:latest 的本地镜像,从 dist/packages/my-project/bundle/ 的最小上下文构建:
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 会生成 dist/packages/my-project/bundle-arm/,其中包含项目的源代码、依赖项和 requirements.txt——镜像在运行时需要的一切。
Dockerfile
Section titled “Dockerfile”Dockerfile 只是将包复制到 Python 基础镜像中。因为 uv 已经将所有依赖项安装到包目录中,所以您不需要在镜像内运行 pip install:
FROM public.ecr.aws/docker/library/python:3.12-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 目标”添加一个 docker 目标,它将 Dockerfile 复制到包输出目录,然后运行 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"] } }}这会清除输出目录,然后将包内容和 Dockerfile 都复制到 dist/.../docker 中,该目录成为 Docker 构建上下文。
pnpm nx docker my-projectyarn nx docker my-projectnpx nx docker my-projectbunx nx docker my-project将生成的构建上下文目录连接到基础设施即代码对于 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 provider 没有一流的”构建和推送 Docker 镜像”资源。生成器使用的模式是:
- 一个
aws_ecr_repository来保存镜像。 - 一个带有
local-execprovisioner 的null_resource,它向 ECR 进行身份验证,重新标记本地构建的镜像,然后推送它。 - 下游资源(例如
aws_ecs_task_definition)引用"${aws_ecr_repository.repo.repository_url}:latest"。
resource "aws_ecr_repository" "repo" { name = "my-project-repository" image_tag_mutability = "MUTABLE" 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}}')'\"}'"]}
resource "null_resource" "docker_publish" { triggers = { docker_digest = data.external.docker_digest.result.digest repository_url = aws_ecr_repository.repo.repository_url }
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}:latest docker push ${self.triggers.repository_url}:latest EOT }}data.external.docker_digest 块确保每当本地镜像哈希更改时 null_resource 重新运行,在每次有意义的代码更改时触发新的推送。
ts#strands-agent生成器 —— 部署到 Bedrock AgentCore Runtime 的 TypeScript agent 的完整模式示例。py#strands-agent生成器 —— Python 的等效示例。- Rolldown 文档 —— TypeScript 打包器的配置参考。
uv文档 —— Python 依赖项导出和安装的参考。