Empaquetado Docker
Varios generadores (como ts#strands-agent y py#strands-agent) producen una imagen Docker que se envía a Amazon ECR y es consumida por la infraestructura de AWS. Esta guía describe el patrón que siguen para que puedas aplicarlo a otros casos de uso — por ejemplo, ejecutar un proyecto py#fast-api en Amazon ECS, o desplegar un servidor Express en contenedor.
El Patrón
Sección titulada «El Patrón»El patrón recomendado tiene tres piezas:
- Un target
bundleen tu proyecto que produce un directorio autocontenido de artefactos de tiempo de ejecución. Para TypeScript esto es un bundle JavaScript de un solo archivo con tree-shaking producido por Rolldown; para Python esto es unrequirements.txty las dependencias instaladas producidas por uv. - Un
Dockerfilemínimo que simplemente haceCOPYde la salida del bundle en una imagen base. Debido a que el bundling ya manejó el tree-shaking y la instalación de dependencias, elDockerfileno necesita ejecutarnpm installouv sync. - Un target
dockerque copia elDockerfilejunto con la salida del bundle (para que el contexto de construcción de Docker solo contenga archivos necesarios en tiempo de ejecución), y luego ejecutadocker build.
El contexto de construcción de Docker se escribe en la carpeta dist de tu proyecto. Tu infraestructura como código (CDK o Terraform) luego apunta a ese directorio para enviar la imagen a ECR.
TypeScript
Sección titulada «TypeScript»Bundle Target
Sección titulada «Bundle Target»Configura un target bundle que invoque Rolldown. Si estás comenzando desde un ts#project, agrega lo siguiente a tu 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"] } }}Y un rolldown.config.ts en la raíz de tu proyecto:
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', },]);Ejecuta el target bundle para producir dist/packages/my-project/bundle/index.js:
pnpm nx bundle my-projectyarn nx bundle my-projectnpx nx bundle my-projectbunx nx bundle my-projectDockerfile
Sección titulada «Dockerfile»Crea un Dockerfile en el directorio fuente de tu proyecto. El archivo no hace nada más que hacer COPY del bundle en una imagen base de Node, además de npm install de cualquier paquete external que no pudo ser empaquetado. Coloca el paso RUN npm install antes del COPY, para que Docker pueda cachear la capa de node_modules instalados y solo volver a ejecutarla cuando la lista de dependencias realmente cambie:
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
Sección titulada «Docker Target»Agrega un target docker que:
- Copie el
Dockerfileen el directorio de salida del bundle (para que el contexto de construcción contenga solo el bundle +Dockerfile), y - Ejecute
docker build(opcional para CDK — ver abajo).
{ "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"] } }}Ejecutar este target produce una imagen local etiquetada como my-scope-my-project:latest, construida desde el contexto mínimo en dist/packages/my-project/bundle/:
pnpm nx docker my-projectyarn nx docker my-projectnpx nx docker my-projectbunx nx docker my-projectBundle Target
Sección titulada «Bundle Target»Configura un target bundle que use uv para exportar e instalar dependencias para tu plataforma objetivo. El generador py#project y el generador py#lambda-function configuran esto por ti. La configuración del target se ve así:
{ "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"] } }}Ejecutar nx bundle my-project produce dist/packages/my-project/bundle-arm/ conteniendo el código fuente de tu proyecto, sus dependencias y un requirements.txt — todo lo que la imagen necesita en tiempo de ejecución.
Dockerfile
Sección titulada «Dockerfile»El Dockerfile simplemente copia el bundle en una imagen base de Python. Debido a que uv ya instaló todas las dependencias en el directorio del bundle, no necesitas ejecutar pip install dentro de la imagen:
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 Target
Sección titulada «Docker Target»Agrega un target docker que copie el Dockerfile en el directorio de salida del bundle, y luego ejecute 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"] } }}Esto limpia el directorio de salida, luego copia tanto el contenido del bundle como el Dockerfile en dist/.../docker, que se convierte en el contexto de construcción de Docker.
pnpm nx docker my-projectyarn nx docker my-projectnpx nx docker my-projectbunx nx docker my-projectInfraestructura
Sección titulada «Infraestructura»Conectar el directorio de contexto de construcción resultante a la infraestructura como código es lo mismo tanto para TypeScript como para Python — solo difiere la ruta al directorio de contexto de construcción (dist/packages/my-project/bundle para TypeScript, dist/packages/my-project/docker para Python).
Usa DockerImageAsset de CDK apuntando al directorio de contexto de construcción. CDK construirá la imagen y la publicará en el repositorio ECR de activos de CDK en el momento del despliegue:
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,});El helper findWorkspaceRoot es generado por el generador ts#infra y exportado desde :my-scope/common-constructs. Si no estás usando constructos compartidos, puedes codificar la ruta al directorio dist relativa a donde se invoca cdk — típicamente la raíz del workspace — y omitir la llamada a findWorkspaceRoot por completo.
Usa el DockerImageAsset con cualquier constructo de AWS que acepte una imagen de contenedor, por ejemplo aws_ecs.ContainerImage.fromDockerImageAsset(image).
El proveedor de AWS de Terraform no tiene un recurso de primera clase para “construir y enviar una imagen Docker”. El patrón usado por los generadores es:
- Un
aws_ecr_repositorypara almacenar la imagen. - Un
null_resourcecon un provisionadorlocal-execque autentica en ECR, re-etiqueta la imagen construida localmente y la envía. - El recurso downstream (por ejemplo,
aws_ecs_task_definition) referencia"${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 }}El bloque data.external.docker_digest asegura que el null_resource se vuelva a ejecutar cada vez que cambie el hash de la imagen local, desencadenando un nuevo push en cada cambio de código significativo.
Lectura Adicional
Sección titulada «Lectura Adicional»- Generador
ts#strands-agent— un ejemplo completo de este patrón para un agente TypeScript desplegado en Bedrock AgentCore Runtime. - Generador
py#strands-agent— el equivalente para Python. - Documentación de Rolldown — referencia de configuración para el bundler de TypeScript.
- Documentación de
uv— referencia para exportación e instalación de dependencias de Python.