Docker 번들링
여러 제너레이터(예: ts#strands-agent 및 py#strands-agent)는 Amazon ECR로 푸시되고 AWS 인프라에서 사용되는 Docker 이미지를 생성합니다. 이 가이드는 이들이 따르는 패턴을 설명하므로 다른 사용 사례에 적용할 수 있습니다. 예를 들어 Amazon ECS에서 py#fast-api 프로젝트를 실행하거나 컨테이너화된 Express 서버를 배포하는 경우입니다.
권장 패턴은 세 가지 요소로 구성됩니다:
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
섹션 제목: “TypeScript”Bundle 타겟
섹션 제목: “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', },]);번들 타겟을 실행하여 dist/packages/my-project/bundle/index.js를 생성합니다:
pnpm nx bundle my-projectyarn nx bundle my-projectnpx nx bundle my-projectbunx nx bundle my-projectDockerfile
섹션 제목: “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 타겟
섹션 제목: “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
섹션 제목: “Python”Bundle 타겟
섹션 제목: “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
섹션 제목: “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 타겟
섹션 제목: “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-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 호출을 완전히 생략할 수 있습니다.
컨테이너 이미지를 허용하는 모든 AWS 구성과 함께 DockerImageAsset을 사용합니다. 예를 들어 aws_ecs.ContainerImage.fromDockerImageAsset(image).
Terraform의 AWS 프로바이더에는 일급 “Docker 이미지 빌드 및 푸시” 리소스가 없습니다. 제너레이터가 사용하는 패턴은 다음과 같습니다:
- 이미지를 보관할
aws_ecr_repository. - ECR에 인증하고, 로컬에서 빌드된 이미지를 다시 태그하고, 푸시하는
local-exec프로비저너가 있는null_resource. - 다운스트림 리소스(예:
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 에이전트에 대한 이 패턴의 완전한 예제.py#strands-agent제너레이터 — Python에 해당하는 예제.- Rolldown documentation — TypeScript 번들러의 구성 참조.
uvdocumentation — Python 종속성 내보내기 및 설치에 대한 참조.