Write a guest Dockerfile
microvm dockerfile --workdir /workspace > guest.Dockerfile# add your RUN layers between the chmod line and the ENV linesdocker buildx build --platform linux/arm64 -t guest-check -f guest.Dockerfile .microvm build --reuse --name my-task-image --dockerfile guest.DockerfileA server-side build cycle is roughly three minutes plus, on some failures, an image name you cannot reuse. Each item below cost a real cycle to find. At the end of this page you will have a Dockerfile that builds locally under arm64 and passes every check the client runs before it makes a billable call.
1. Start from the generated stanza
Section titled “1. Start from the generated stanza”microvm dockerfile prints the Dockerfile that a default build bakes, generated by the same code that builds it, with the platform constraints annotated inline. --from sets the image ref for the FROM line and defaults to the managed al2023 base’s pair, --port (default 9000) is the port agentd listens on inside the guest, and --workdir is a working directory to create and set, which the help calls strongly recommended. The output opens with comment lines naming the constraints below; stripped of those, the stanza is this:
FROM public.ecr.aws/amazonlinux/amazonlinux:2023-minimalCOPY agentd /agentdRUN chmod 0755 /agentdRUN mkdir -p /workspaceWORKDIR /workspaceENV AGENTD_PORT=9000ENV AGENTD_LOG=infoEXPOSE 9000ENTRYPOINT []CMD ["/agentd"]Append your RUN layers after the chmod line and pass the result to --dockerfile. When you pass --dockerfile you are replacing the default, so you carry its daemon lines yourself.
2. Build it locally under arm64 first
Section titled “2. Build it locally under arm64 first”docker buildx build --platform linux/arm64 -t guest-check -f guest.Dockerfile .A dnf typo or a missing package is free to find here and expensive to find server-side. With qemu-aarch64 binfmt registered this builds the real target architecture on an x86 host. Two errors in the repository’s own example Dockerfile were caught this way in seconds.
3. The rules the client checks before any call
Section titled “3. The rules the client checks before any call”The FROM must match the managed base’s registry ref. The build runs your Dockerfile on top of the base the create call names, and a mismatch builds against a base none of the measured platform behavior applies to, so the client refuses it locally. A @sha256: digest suffix on that same ref is accepted and is what the shipped examples pin.
Set a WORKDIR explicitly. al2023-minimal, like most public ARM64 bases, leaves WorkingDir empty, so an omitted --cwd on every later exec resolves against /. The client requires one when the base declares none. Check that the user your workload runs as can write to it; a root-owned WORKDIR under a non-root workload fails at the first write.
ENV AGENTD_PORT must agree with the client’s port. The platform dials its build-time ready and validate hooks on the port from the create call, so a guest listening elsewhere answers neither and the build fails with CREATE_FAILED after a completely clean build log. The daemon’s own agentd listening line appears, with the wrong address, and no error line follows. The client refuses the disagreement locally, including the case where the Dockerfile names no port while you have moved the client off the default with --port: an unset variable leaves the daemon on 9000 rather than on your port.
Keep ENTRYPOINT [] and CMD ["/agentd"]. That pair is the trust boundary. It guarantees no workload runs before the platform’s run hook lands and the token arrives, and it is what makes an omitted cwd inherit the image WORKDIR. An ENTRYPOINT that swallows CMD produces a VM whose daemon never starts, and the symptom is a run-hook timeout that mentions neither. A base image that starts its own background process before bootstrap breaks the invariant too, and enforcing it belongs to whoever builds the image. Trust is the threat model.
Leave AGENTD_SSE_KEEPALIVE_SECS alone unless you also raise the client’s stream idle timeout. The client treats sixty seconds of silence as a dead connection because the daemon’s keepalive is fifteen; a longer interval makes healthy streams look dead. This too is refused locally.
4. On a -minimal base
Section titled “4. On a -minimal base”Spell weak dependencies off as --setopt=install_weak_deps=0. The integer is what works; False is not accepted.
useradd is not in the minimal base, and pulling shadow-utils in for one line is a bigger image for no more correctness. The examples append to /etc/passwd directly and hand the working directory to that uid, because WORKDIR is where every exec lands and a root-owned one fails a demoted workload at its first write:
RUN echo "agent:x:1000:1000::/workspace:/bin/bash" >> /etc/passwd \ && echo "agent:x:1000:" >> /etc/groupRUN mkdir -p /workspace && chown 1000:1000 /workspaceWORKDIR /workspaceThe daemon itself stays root; demotion is per command, with exec --user 1000 --group 1000. Run coding agents on Bedrock shows why that uid is not decoration.
5. No secret goes in the image
Section titled “5. No secret goes in the image”The image becomes a shared snapshot, so every VM launched from it sees the same bytes. Per-VM credentials travel through the platform’s one-shot runHookPayload at launch: the agent token always, and anything you add with run --launch-env KEY=VALUE, which shares the payload’s 4096-byte ceiling. Larger material goes in after bootstrap with microvm cp --mode 0600 over the authenticated channel.
6. The build context holds two members
Section titled “6. The build context holds two members”The artifact microvm build uploads carries exactly the Dockerfile and the agentd binary, so there is no file beside them for a COPY to find. A script the image needs is materialized in the Dockerfile with printf; Prefetch S3 content at image build shows a start wrapper written that way. build --project is the one widening: it adds the project’s manifest and lockfile pair at the archive root, so COPY pyproject.toml uv.lock ./ finds them.
7. The daemon’s knobs
Section titled “7. The daemon’s knobs”Every AGENTD_* variable is read at startup, and an unset or unparseable value keeps the default rather than refusing to boot, because a daemon that will not start strands the VM with no way in. Set them as ENV lines. The stanza already sets AGENTD_PORT and AGENTD_LOG; the others bound request bodies, captured output, stream replay, stdin writes, the disk reserve, and the identity repair that gives each VM restored from one snapshot its own machine-id, hostname, and boot_id. The table with defaults is in Embedding.
8. Working examples
Section titled “8. Working examples”Three Dockerfiles in the repository respect all of the above: coding-agents-on-bedrock adds Node, Python, and two agent CLIs; code-server-remote-dev installs an IDE from a release RPM; s3-prefetch-at-build replaces CMD with a wrapper that fetches before it hands off to the daemon.
When a build fails anyway, Debug a failed build says where the reason lives. The measurements behind every rule above are in Platform.