| name | containerization |
| description | Containerize legacy applications with production-grade defaults โ multi-stage Dockerfile, docker buildx multi-arch (amd64/arm64), Trivy/grype security scanning, Health Check, non-root user, minimal base (distroless/alpine), ECR push automation, and ECS/EKS manifest templates. Use after to-be-architecture is approved and risk-discovery PASSes. |
| argument-hint | [app-path, target-orchestrator (ecs|eks), base-image-hint (alpine|distroless|ubi)] |
| user-invocable | true |
| model | claude-sonnet-4-6 |
| allowed-tools | Read,Write,Edit,Bash,Grep,Glob,mcp__ecs,mcp__eks,mcp__aws-documentation,mcp__aws-iac |
์ธ์ ์ฌ์ฉํ๋์
to-be-architecture.md ๊ฐ ECS Fargate ๋๋ EKS ๋ฅผ compute ๋ก ์ง์ ํ์ ๋
- ๊ธฐ์กด JAR/WAR/Node.js/Python ์ ํ๋ฆฌ์ผ์ด์
์ Docker ์ด๋ฏธ์ง๋ก ๋น๋ํ๋ ๋จ๊ณ
- ์ด๋ฏธ ์๋ Dockerfile ์ด multi-stage, multi-arch, non-root ์ค ํ๋๋ผ๋ ๋ฏธ์ ์ฉ ์ํ์ธ ๊ฒฝ์ฐ์ ๋ฆฌํฉํฐ๋ง
- ECR ์ push ํ ECS Task Definition ๋๋ Kubernetes Deployment ๋งค๋ํ์คํธ ์ด์์ด ํ์ํ ๋
์ธ์ ์ฌ์ฉํ์ง ์๋์
decided_pattern == Rehost ์ EC2 ๋์๋ง ๋ฐฐํฌํ๋ ๊ฒฝ์ฐ โ ์ปจํ
์ด๋ํ ๋ฏธํด๋น
- ์๋ฒ๋ฆฌ์ค Lambda ์ ์ฉ ๊ฒฝ๋ก โ zip ํจํค์ง ๋๋ Container Image for Lambda ๋ณ๋ skill ์ฌ์ฉ
- ์ด๋ฏธ production ์ด์ ์ค์ด๊ณ CVE ํจ์น ๋ชฉ์ ๋ง ์๋ ๊ฒฝ์ฐ โ ๋ณ๋ ํจ์น ํ๋ก์ธ์ค
์ ์ ์กฐ๊ฑด
.omao/plans/modernization/to-be-architecture.md ์กด์ฌ ๋ฐ compute ํ๋ ํ์
aidlc/skills/construction/risk-discovery PASS (๋ฐ์ดํฐ ์ ํฉ์ฑ, ๋กค๋ฐฑ ๊ฒฝ๋ก ๊ฒ์ฆ ์๋ฃ)
- Docker 24+ ์ค์น,
docker buildx ํ๋ฌ๊ทธ์ธ ํ์ฑํ
- AWS CLI + ECR ํธ์ ๊ถํ (IRSA ๋๋ ๋ก์ปฌ profile)
- Trivy v0.48+ ๋๋ grype ์ค์น (๋ณด์ ์ค์บ์ฉ)
์ ์ฐจ
Step 1. Multi-Stage Dockerfile ์์ฑ
๋น๋ ์์กด์ฑ๊ณผ ๋ฐํ์์ ๋ถ๋ฆฌํฉ๋๋ค. ์ธ์ด๋ณ ์ต์ ๋ฒ ์ด์ค ์ด๋ฏธ์ง๋ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
| ๋ฐํ์ | Builder | Runtime |
|---|
| Node.js 20 | node:20-alpine | gcr.io/distroless/nodejs20-debian12 |
| Python 3.11 | python:3.11-slim | gcr.io/distroless/python3-debian12 |
| Java 21 | eclipse-temurin:21-jdk-alpine | eclipse-temurin:21-jre-alpine |
| Go 1.22 | golang:1.22-alpine | gcr.io/distroless/static-debian12 |
# Build stage
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
RUN npm run build
# Runtime stage (distroless, non-root)
FROM gcr.io/distroless/nodejs20-debian12
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
USER nonroot
EXPOSE 8080
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
CMD ["/nodejs/bin/node", "dist/health.js"]
CMD ["dist/main.js"]
Step 2. Multi-Arch ๋น๋ (amd64 + arm64)
AWS Graviton(arm64) ์ด ๋์ผ ์ฑ๋ฅ ๋๋น ์ต๋ 40% ์ ๋ ดํ๋ฏ๋ก multi-arch ๋น๋๋ฅผ ๊ธฐ๋ณธ์ผ๋ก ํฉ๋๋ค.
docker buildx create --name oma-builder --use
docker buildx inspect --bootstrap
ACCOUNT=$(aws sts get-caller-identity --query Account --output text)
REGION=ap-northeast-2
REPO=${ACCOUNT}.dkr.ecr.${REGION}.amazonaws.com/${APP_NAME}
aws ecr get-login-password --region ${REGION} | \
docker login --username AWS --password-stdin ${REPO}
docker buildx build \
--platform linux/amd64,linux/arm64 \
-t ${REPO}:${VERSION} \
-t ${REPO}:latest \
--push \
.
Step 3. ๋ณด์ ์ค์บ (Trivy + grype)
CI ํ์ดํ๋ผ์ธ์์ ์ด๋ฏธ์ง push ์ ๋ ์ค์บ๋๋ฅผ ๋ณ๋ ฌ ์คํํ๊ณ HIGH/CRITICAL 0๊ฑด์ ๊ฒ์ดํธ๋ก ์ค์ ํฉ๋๋ค.
trivy image --severity HIGH,CRITICAL --exit-code 1 ${REPO}:${VERSION}
grype ${REPO}:${VERSION} --fail-on high
ํ์ง ์ ๋์:
- Base ์ด๋ฏธ์ง ์
๊ทธ๋ ์ด๋ (์:
node:20.11-alpine โ node:20.12-alpine)
- ์ทจ์ฝ ํจํค์ง ์ ๊ฑฐ ๋๋ pin
- ๋ถ๊ฐํผํ ๊ฒฝ์ฐ
.trivyignore ์ CVE ๋ฒํธ์ ๋ง๋ฃ์ผ ๊ธฐ๋ก + audit.md ์ ๊ทผ๊ฑฐ ๋ช
์
Step 4. ECR Lifecycle Policy
ํ๊ทธ๋์ง ์์ ์ด๋ฏธ์ง ๋์ ๋ฐฉ์ง๋ฅผ ์ํด lifecycle policy ๋ฅผ ์ ์ฉํฉ๋๋ค.
{
"rules": [
{
"rulePriority": 1,
"description": "Expire untagged images older than 14 days",
"selection": {
"tagStatus": "untagged",
"countType": "sinceImagePushed",
"countUnit": "days",
"countNumber": 14
},
"action": {"type": "expire"}
}
]
}
Step 5. ECS Task Definition ํ
ํ๋ฆฟ
to-be-architecture.compute == ECS Fargate ๊ฒฝ๋ก์ผ ๋:
{
"family": "${APP_NAME}",
"networkMode": "awsvpc",
"requiresCompatibilities": ["FARGATE"],
"cpu": "1024",
"memory": "2048",
"runtimePlatform": {"cpuArchitecture": "ARM64", "operatingSystemFamily": "LINUX"},
"executionRoleArn": "arn:aws:iam::${ACCOUNT}:role/${APP_NAME}-exec",
"taskRoleArn": "arn:aws:iam::${ACCOUNT}:role/${APP_NAME}-task",
"containerDefinitions": [
{
"name": "${APP_NAME}",
"image": "${REPO}:${VERSION}",
"essential": true,
"portMappings": [{"containerPort": 8080, "protocol": "tcp"}],
"healthCheck": {
"command": ["CMD-SHELL", "wget -qO- http://localhost:8080/healthz || exit 1"],
"interval": 30,
"timeout": 5,
"retries": 3,
"startPeriod": 10
},
"logConfiguration": {
"logDriver": "awslogs",
"options": {
"awslogs-group": "/ecs/${APP_NAME}",
"awslogs-region": "${REGION}",
"awslogs-stream-prefix": "ecs"
}
}
}
]
}
Step 6. EKS Deployment + Service ํ
ํ๋ฆฟ
to-be-architecture.compute == EKS ๊ฒฝ๋ก์ผ ๋:
apiVersion: apps/v1
kind: Deployment
metadata:
name: ${APP_NAME}
namespace: ${NS}
spec:
replicas: 3
selector:
matchLabels: {app: ${APP_NAME}}
template:
metadata:
labels: {app: ${APP_NAME}}
spec:
serviceAccountName: ${APP_NAME}-sa
securityContext:
runAsNonRoot: true
runAsUser: 65532
fsGroup: 65532
seccompProfile: {type: RuntimeDefault}
containers:
- name: app
image: ${REPO}:${VERSION}
ports: [{containerPort: 8080}]
resources:
requests: {cpu: 500m, memory: 512Mi}
limits: {cpu: 1000m, memory: 1Gi}
livenessProbe:
httpGet: {path: /healthz, port: 8080}
initialDelaySeconds: 10
periodSeconds: 30
readinessProbe:
httpGet: {path: /readyz, port: 8080}
initialDelaySeconds: 5
periodSeconds: 10
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities: {drop: [ALL]}
Step 7. Output ์ฐ์ถ
.omao/plans/modernization/containerization-report.md ์ ๊ธฐ๋กํฉ๋๋ค.
# Containerization Report
- app: ${APP_NAME}
- base_image: gcr.io/distroless/nodejs20-debian12
- architectures: [linux/amd64, linux/arm64]
- image_size_mb: 142
- cve_scan: PASS (0 HIGH/CRITICAL)
- ecr_uri: ${REPO}:${VERSION}
- manifest_path: manifests/${APP_NAME}-task-def.json (ECS) | deployment.yaml (EKS)
- next_skill: cutover-planning
์ข์ ์์
gcr.io/distroless/nodejs20-debian12 + buildx multi-arch + Trivy 0 HIGH โ production ๋ฐฐํฌ ์ค๋น ์๋ฃ
- Java Spring Boot โ
eclipse-temurin:21-jre-alpine + non-root user 10001 + JLink slim JRE
- Go static binary โ
distroless/static + CGO_ENABLED=0
๋์ ์์ (๊ธ์ง)
FROM ubuntu:latest ๋๋ FROM node:latest โ ์ฌํ์ฑ ์์
USER root ๋๋ USER ์ง์์ด ๋๋ฝ โ ๋ณด์ ํต์ ์๋ฐ
- Health Check ๋ฏธ์ ์ โ ECS/EKS ๊ฐ ๋น์ ์ Task/Pod ์ฌ์์ ๋ถ๊ฐ
- multi-arch ๋น๋ ์๋ต ํ Graviton NodePool ์ ๋ฐฐํฌ ์คํจ
- Trivy HIGH ๋ฌด์ +
.trivyignore ๊ทผ๊ฑฐ ๋ฏธ๊ธฐ๋ก
์ฐธ๊ณ ์๋ฃ
๊ณต์ ๋ฌธ์
์์ฒ ๋ฐฉ๋ฒ๋ก (MIT-0)
๊ด๋ จ ๋ฌธ์ (๋ด๋ถ)
../to-be-architecture/SKILL.md โ ์ ํ skill
../cutover-planning/SKILL.md โ ํ์ skill
/home/ubuntu/workspace/oh-my-aidlcops/plugins/aidlc/CLAUDE.md โ risk-discovery ์ ๊ณต