FROM SCRIPT TO RUNTIME
Put the local Strands agent behind an AgentCore entrypoint
The local starter already knows how to create a Strands agent, connect it to an Amazon Bedrock model, and register a custom tool. Deployment adds one important boundary: instead of calling the agent directly from a script, AgentCore invokes a stable application entrypoint with a request payload.
This project keeps the agent intentionally small. A caller sends a prompt, the runtime builds the Bedrock-backed agent, and the response returns the agent’s message. The simplicity makes the deployment path easy to see.
The complete implementation is available in p-cap/fluffy-enigma on GitHub ↗.
AGENTCORE APPLICATION
Turn a Python function into the invocation endpoint
BedrockAgentCoreApp owns the HTTP runtime contract. The @app.entrypoint decorator marks the function AgentCore calls for each invocation, while the Strands @tool decorator exposes deterministic application logic to the model.
app = BedrockAgentCoreApp()
@tool
def count_words(text: str) -> int:
"""Count whitespace-separated words in the provided text."""
return len(text.split())
@app.entrypoint
def test_strands(payload):
load_dotenv()
user_message = payload.get("prompt", "Hello")
model = BedrockModel(
model_id=os.environ["BEDROCK_MODEL_ID"],
region_name=os.environ["AWS_REGION"],
)
agent = Agent(
model=model,
system_prompt=(
"You are a helpful assistant. "
"Use the count_words tool when asked to count words."
),
tools=[count_words],
)
result = agent(user_message)
return result.message
if __name__ == "__main__":
app.run()The runtime package serves the AgentCore HTTP API on port 8080. Locally, a request to /invocations exercises the same entrypoint that runs after deployment.
CONTAINER BOUNDARY
Build once for the AgentCore runtime architecture
The image starts from the Python 3.13 uv base, installs the locked dependencies, copies only the application directory, and starts the runtime without resolving packages again.
FROM ghcr.io/astral-sh/uv:python3.13-bookworm-slim
WORKDIR /app
COPY pyproject.toml uv.lock ./
RUN uv sync --frozen --no-dev
COPY app/ ./app/
EXPOSE 8080
CMD ["uv", "run", "--no-sync", "python", "app/main.py"]The repository builds for linux/arm64, matching the runtime target rather than the developer machine.
docker buildx build --platform linux/arm64 --tag hello-strands:local --load .TEST THE IMAGE
Pass temporary AWS credentials into the local container
The container needs the same AWS identity, Region, and model configuration as the local script. AWS CLI can export the active session as environment variables, while the non-secret application settings come from .env.
credentials=$(aws configure export-credentials --format env) || exit 1
eval "$credentials"
docker run --rm --name hello-strands-local --platform linux/arm64 -p 127.0.0.1:8080:8080 --env-file .env -e AWS_ACCESS_KEY_ID -e AWS_SECRET_ACCESS_KEY -e AWS_SESSION_TOKEN hello-strands:localThis keeps credentials out of the image and source tree. They exist only in the environment of the local container process.
ECR DELIVERY
Push the image, then deploy its immutable digest
The deployment shell script reads configuration from .env, checks that Docker is available, builds the image when it is missing, resolves the current AWS account, and creates the ECR repository if necessary. It then authenticates Docker, tags the local image, and pushes it.
./push-agentcore-runtime.shAlthough the pushed tag defaults to latest, the Python deployer resolves that tag to its ECR digest and sends the digest URI to AgentCore. Every runtime version therefore points to the exact image that was pushed.
image = ecr.describe_images(
repositoryName=repository,
imageIds=[{"imageTag": os.environ["IMAGE_TAG"]}],
)["imageDetails"][0]
uri = (
f"{account}.dkr.ecr.{region}.amazonaws.com/"
f"{repository}@{image['imageDigest']}"
)CREATE OR REDEPLOY
Make the deployment safe to run more than once
The deployer searches every page of AgentCore runtimes for the configured name. If none exists, it creates one. If it finds exactly one, it updates that runtime while preserving its execution role, networking, authorization, request headers, lifecycle settings, and other existing configuration.
That distinction matters: a deployment script should not silently replace security or networking choices on every release. It should update the artifact and required environment while retaining intentional runtime settings.
Create when missing. Update when present. Refuse to guess when duplicate names exist.
The script also waits through CREATING or UPDATING until the selected version reaches READY. Failure states include the AgentCore failure reason, and the wait stops after fifteen minutes.
RUNTIME EXECUTION ROLE
Give AgentCore only the services the container needs
When a role ARN is not supplied, the deployer reuses or creates a role trusted by bedrock-agentcore.amazonaws.com. Its inline policy covers four responsibilities: authorize with ECR, pull this repository’s image layers, write AgentCore CloudWatch logs, and invoke Bedrock models or inference profiles.
New IAM roles can take a moment to propagate. The creation path retries the specific role-assumption validation error instead of treating every exception as temporary. If an existing RUNTIME_ROLE_ARN is supplied, the script uses it without rewriting its permissions.
DEPLOYMENT TESTS
Test the decisions around the AWS calls
The repository’s unit tests mock the AWS clients and verify the behaviors that are easiest to break: digest-based creation, configuration-preserving updates, pagination, access-denied handling, failure reporting, and execution-role setup.
These tests do not claim that AWS is available. They protect the deployment logic so a real AWS run is reserved for validating integration, permissions, and runtime readiness.
uv run python -m unittest scripts/test_deploy_runtime.py