Subchapter 23.9
references/agentcore-runtime-container-build.mdMarkdown10 KBView on GitHub
Assets
Kb Shim PyDeterministic procedure for building an ARM64 container image that meets AgentCore Runtime’s container contract and pushing it to ECR. Each protocol has a different container contract — you MUST select the protocol before building.
http | mcp | a2a | ag-ui — see runtime reference for selection guidefastapi | express | flask | customConstraints for parameter acquisition:
protocol, ecr_repo) upfront in a single promptframework parameter in the same promptGeneral constraints:
Constraints:
docker buildx versionaws --version| Protocol | Health Endpoint | Port | Key Requirement |
|---|---|---|---|
| HTTP | /health | 8080 | JSON request/response |
| MCP | /mcp | 8080 | Streamable HTTP transport, tool registration |
| A2A | /.well-known/agent.json | 8080 | Agent Card discovery, task management |
| AG-UI | /ping | 8080 | SSE event stream via /invocations, health via /ping |
Constraints:
Example Dockerfile (HTTP/FastAPI):
FROM --platform=linux/arm64 python:3.12.4-slim AS builder
WORKDIR /app
RUN python -m venv /app/.venv
ENV PATH="/app/.venv/bin:$PATH"
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
FROM --platform=linux/arm64 python:3.12.4-slim
RUN useradd -r -u 1001 appuser
WORKDIR /app
COPY --from=builder /app /app
ENV PATH="/app/.venv/bin:$PATH"
USER appuser
EXPOSE 8080
# Binds to 0.0.0.0 for AgentCore internal routing. Do NOT expose directly to the internet.
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8080"]Constraints:
HTTP (FastAPI) example:
Note: These examples omit authentication because AgentCore handles auth at the platform layer. If running outside AgentCore (e.g., local testing), you MUST add authentication middleware before exposing to any network.
from fastapi import FastAPI
import signal, sys
app = FastAPI()
@app.get("/health")
async def health():
return {"status": "healthy"}
@app.post("/invoke")
async def invoke(request: dict):
# Agent logic here
return {"response": "..."}
def shutdown(sig, frame):
sys.exit(0)
signal.signal(signal.SIGTERM, shutdown)MCP example:
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("my-agent")
@mcp.tool()
def my_tool(query: str) -> str:
"""Tool description for discovery."""
return "result"
# Runs on /mcp with Streamable HTTP transport
mcp.run(transport="streamable-http", host="0.0.0.0", port=8080)Note: This minimal example omits SIGTERM handling for brevity. You MUST add graceful shutdown handling (see the HTTP example above) before deploying to AgentCore.
A2A example (minimal contract):
from fastapi import FastAPI
app = FastAPI()
# Agent Card discovery endpoint — REQUIRED for A2A protocol
@app.get("/.well-known/agent.json")
async def agent_card():
return {
"name": "my-agent",
"description": "Agent description",
"capabilities": ["task_execution"],
"endpoint": "http://localhost:8080", # Replace with AgentCore-assigned URL at deployment
}
@app.post("/tasks")
async def create_task(request: dict):
# Task execution logic
return {"taskId": "...", "status": "completed", "result": "..."}Note: This minimal example omits SIGTERM handling for brevity. You MUST add graceful shutdown handling (see the HTTP example above) before deploying to AgentCore.
AG-UI example (minimal contract):
from fastapi import FastAPI
from fastapi.responses import StreamingResponse, JSONResponse
import json
app = FastAPI()
@app.get("/ping")
async def ping():
return JSONResponse({"status": "Healthy"})
@app.post("/invocations")
async def invocations(request: dict):
async def event_stream():
yield f"data: {json.dumps({'type': 'RUN_STARTED', 'threadId': 'thread-1', 'runId': 'run-1'})}\n\n"
yield f"data: {json.dumps({'type': 'TEXT_MESSAGE_CONTENT', 'messageId': 'msg-1', 'delta': 'response'})}\n\n"
yield f"data: {json.dumps({'type': 'RUN_FINISHED', 'threadId': 'thread-1', 'runId': 'run-1'})}\n\n"
return StreamingResponse(event_stream(), media_type="text/event-stream")Note: This minimal example omits SIGTERM handling for brevity. You MUST add graceful shutdown handling (see the HTTP example above) before deploying to AgentCore.
Refer to the latest AWS documentation on AgentCore A2A protocol and AG-UI protocol for current full specifications — these protocols are evolving and the full contract may have changed.
Constraints:
You MUST build for ARM64: docker buildx build --platform linux/arm64 --load -t <tag> .
You MUST authenticate to ECR before pushing:
aws ecr get-login-password --region <region> | docker login --username AWS --password-stdin <account>.dkr.ecr.<region>.amazonaws.comYou MUST tag with both latest and a version tag for rollback:
docker tag <image> <ecr_repo>:latest
docker tag <image> <ecr_repo>:v1.0.0
docker push <ecr_repo>:latest
docker push <ecr_repo>:v1.0.0Constraints:
You MUST verify the image architecture is ARM64:
docker inspect <image> | grep ArchitectureYou SHOULD test locally before deploying to AgentCore:
docker run --platform linux/arm64 -p 8080:8080 <image>
# Use the health endpoint for your protocol:
# HTTP: /health | MCP: /mcp | A2A: /.well-known/agent.json | AG-UI: /ping
curl http://localhost:8080/<health-endpoint>If health check fails locally, it will fail on AgentCore — fix before deploying
Authentication and network exposure:
127.0.0.1 instead of 0.0.0.0 to prevent network exposure: uvicorn main:app --host 127.0.0.1 --port 8080--host 0.0.0.0 because AgentCore routes traffic to the container internally — do NOT expose port 8080 directlyTransport security:
Input validation:
X-Content-Type-Options: nosniff, X-Frame-Options: DENY, Cache-Control: no-storeContainer image security:
USER appuser — do not remove this)python:3.12.4-slim not python:3.12-slim) to avoid supply chain attacks from tag mutationaws ecr put-image-scanning-configuration --repository-name <repo> --image-scanning-configuration scanOnPush=trueECR access control:
ecr:* on Resource: "*"get-login-password is ephemeral (12 hours) — do not store or share itRuntime security: