The comment "Configure UV for container environment" sat above a six-variable
ENV block, but only three of those are uv settings. The other three are read by
completely unrelated consumers, and the comment silently claimed them:
UV_COMPILE_BYTECODE / UV_LINK_MODE / UV_NO_CACHE uv (confirmed via `uv help
sync`, which lists all three as `[env: ...]` on uv 0.9.30)
DOCKER_CONTAINER=1 bedrock_agentcore runtime
OTEL_PYTHON_LOG_CORRELATION=true opentelemetry logging
instrumentation
PYTHONUNBUFFERED=1 the CPython interpreter
DOCKER_CONTAINER is the dangerous one. In the installed tree it has two
consumers, not one:
.venv/lib/python3.13/site-packages/bedrock_agentcore/runtime/app.py:402
if os.path.exists("/.dockerenv") or os.environ.get("DOCKER_CONTAINER"):
host = "0.0.0.0" # nosec B104 - Docker needs this to expose the port
else:
host = "127.0.0.1"
.venv/lib/python3.13/site-packages/bedrock_agentcore/identity/auth.py:163
if os.getenv("DOCKER_CONTAINER") == "1":
raise ValueError("Workload access token has not been set. ...")
(Line numbers are from the langgraph image, bedrock-agentcore 1.0.6. The strands
image pins 1.2.0, where the same two checks live at app.py:450 and auth.py:284.)
Both agents reach that first path: each builds a BedrockAgentCoreApp and calls
app.run().
The hazard: a reader who trusts the header and prunes "uv config" they don't
recognise unbinds the agent from 0.0.0.0, and nothing tells them. The bind check
is an `or` against /.dockerenv, which plain `docker run` creates -- so a local
smoke test still passes. AgentCore's managed runtime has no /.dockerenv, so the
breakage appears only once deployed. The HEALTHCHECK cannot catch it either: it
reaches the server over localhost from inside the container, which a
127.0.0.1-bound server answers happily.
Split the block into three ENV instructions, each under a comment describing
what actually reads those variables, so no variable's purpose is misattributed.
Two more instances of the same pattern, fixed in both files:
- "Create non-root user" also covered the USER line beneath it, which switches
to that user rather than creating it.
- The strands file said "Copy agent code and shared utilities" above three
COPYs, one of which is tools/. Now matches its langgraph twin.
This is comment-only. No environment variable, value, or ordering changed.
Verification, both images built for linux/arm64 from context
examples/integrations/agentcore:
- `docker run --rm --platform linux/arm64 -e GATEWAY_CREDENTIAL_PROVIDER_NAME=dummy
-e AWS_DEFAULT_REGION=us-east-1 <tag> sh -c 'env | sort'` before vs after is
identical for both agents (modulo the per-container HOSTNAME).
- `docker inspect -f '{{range .Config.Env}}...'` before vs after is identical
for both agents including ordering, so the baked config is unchanged, not
merely equivalent at runtime.
- The DOCKER_CONTAINER claim was reproduced against the real code path with
/.dockerenv masked and uvicorn.run stubbed: set -> host 0.0.0.0, unset ->
host 127.0.0.1.
- The HEALTHCHECK command was run against a 127.0.0.1-bound server inside the
container and passed, confirming the failure mode is silent.
- The two Dockerfiles are byte-identical modulo the agent name.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The comment sat above the langgraph/langchain pins but read as a claim that the
whole dependency set tracks examples/integrations/langgraph-python. It does not —
copilotkit and ag-ui-protocol are both behind that example. Say which pair the
statement covers.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Several packages that agentcore code imports at module scope were never
declared in the pyproject.toml of the project that ships them. They only
resolved because something else happened to pull them in, so the next
`uv lock` that drops the intermediate would silently remove them.
That is newly dangerous: both agent Dockerfiles now install with
`uv sync --locked`, so the installed set is exactly the lockfile rather
than whatever pip incidentally resolved. A dropped transitive would turn
into an ImportError at container start instead of a quiet near-miss.
Undeclared but directly imported:
- boto3 — `agents/utils/ssm.py:12`. `agents/utils/` is COPY'd into BOTH
agent images, so both agent projects need it; neither declared it.
- PyJWT — `agents/utils/auth.py:11`. strands declared it, langgraph did
not and resolved it transitively only. langgraph now matches strands
(`PyJWT[crypto]>=2.10.1`) since it is the same shared module.
- langchain-core — `tools/todos.py:10` imports `langchain_core.messages`
in the langgraph agent; it rode in on `langchain`.
- botocore — `scripts/utils.py:17` imports `botocore.exceptions`; the
example-root project declared boto3 but not botocore.
Floors are set at or below what the existing lockfiles already resolve,
so nothing is bumped. The lock diffs are additive metadata only: zero
resolved versions changed and no new packages entered any lock.
Deliberately not declared: `docker/resolve-env.py` (boto3, PyYAML) is
already covered by the root project; `infra-cdk/lambdas/oauth2-provider/`
uses boto3 from the Lambda runtime and is bundled by CDK, not by any of
these three uv projects.
Verification (run, not read):
$ docker build --platform linux/arm64 \
-f agents/langgraph-single-agent/Dockerfile -t acuv-lg-a2:test .
naming to docker.io/library/acuv-lg-a2:test done
$ docker build --platform linux/arm64 \
-f agents/strands-single-agent/Dockerfile -t acuv-st-a2:test .
naming to docker.io/library/acuv-st-a2:test done
$ docker run --rm --platform linux/arm64 \
-e GATEWAY_CREDENTIAL_PROVIDER_NAME=dummy -e AWS_DEFAULT_REGION=us-east-1 \
acuv-lg-a2:test sh -c 'python -c "import langgraph_agent, boto3, jwt, langchain_core, utils.ssm, utils.auth, tools; ..."'
OK lg 1.43.78 2.13.0 1.6.0
$ docker run --rm --platform linux/arm64 ... acuv-st-a2:test \
sh -c 'python -c "import strands_agent, boto3, jwt, utils.ssm, utils.auth, tools; ..."'
OK st 1.43.78 2.13.0
$ uv run --locked scripts/test-agent.py --help # exit 0, usage printed
$ uv lock --check # passes for all three projects (14 / 144 / 123 packages)
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Both AgentCore agents installed from an unlocked `requirements.txt`, so every
image build re-resolved transitive dependencies from scratch. That had already
drifted into a broken state: `langgraph==1.0.10rc1` pulled in a langgraph-prebuilt
that reads `ExecutionInfo` off `langgraph.runtime`, which 1.0.x does not export,
so `import langgraph_agent` failed at container start.
Give each agent a `pyproject.toml` + `uv.lock` and install with `uv sync --locked`,
matching how every other Python integration example is set up. Bump langgraph to
1.1.6 and pin langchain to 1.2.15 — the pair used by
examples/integrations/langgraph-python — to resolve the import failure, and fold
the separately installed `aws-opentelemetry-distro` into the locked dependency
set so it is pinned too.
The example root also gains a `pyproject.toml` + `uv.lock` for the `scripts/`
helpers, whose dependencies were previously declared in a `requirements.txt`
that nothing installed.
Verified by building both images for linux/arm64 and importing the agent module
inside each container.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>