Skip to content

Write a guest Dockerfile

Terminal window
microvm dockerfile --workdir /workspace > guest.Dockerfile
# add your RUN layers between the chmod line and the ENV lines
docker buildx build --platform linux/arm64 -t guest-check -f guest.Dockerfile .
microvm build --reuse --name my-task-image --dockerfile guest.Dockerfile

A 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.

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-minimal
COPY agentd /agentd
RUN chmod 0755 /agentd
RUN mkdir -p /workspace
WORKDIR /workspace
ENV AGENTD_PORT=9000
ENV AGENTD_LOG=info
EXPOSE 9000
ENTRYPOINT []
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.

Terminal window
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.

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/group
RUN mkdir -p /workspace && chown 1000:1000 /workspace
WORKDIR /workspace

The 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.

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.

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.

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.

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.