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.DockerfilePrepare and check the image locally before starting a billable AWS build. These steps catch common Dockerfile and architecture mistakes. A successful local build still needs verification against the service.
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. 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.
An exec without --user runs as root unless the image sets a default user:
ENV AGENTD_DEFAULT_USER=agentThe daemon resolves the value at each exec to its /etc/passwd row, so the row can come from an earlier exec. A name and its uid are the same: AGENTD_DEFAULT_USER=1000 runs as agent with group 1000 and HOME=/workspace, and a uid with no row fails the exec rather than run it in root’s group. An exec with --user runs as that user instead, --user root included, and hook handlers still run as root.
Uploads don’t follow the variable. microvm cp and run . --exec write through the daemon as root, so what they create is root’s, with no group or other write bit on a synced tree. The default user can read the synced files but can’t change them or write into the directories the sync made, even under a /workspace the image handed to it. An exec that must write there runs with --user 0. Otherwise, a chown run with --user 0 gives the tree to the default user first, as this section’s Dockerfile does for /workspace.
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 are delivered 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 the Dockerfile and the daemon
Section titled “6. The build context holds the Dockerfile and the daemon”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 widens it: 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. AGENTD_DEFAULT_USER doesn’t fall back: a user the guest lacks fails each exec without --user with unknown_user, and the error mentions the variable. 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 daemon configuration is defined in crates/agentd/src/config.rs;
Protocol documents the request limits and hook behavior.
8. Run code on suspend and resume
Section titled “8. Run code on suspend and resume”A suspend keeps the VM’s memory and disk, but outbound connections are aborted on resume and the clock jumps by the time spent suspended. To react, put an executable at /etc/agentd/hooks.d/<hook> for run, suspend, resume, or terminate. The daemon runs it before answering that hook, kills it after AGENTD_HOOK_HANDLER_TIMEOUT_SECS (default 20), and reports the outcome on microvm health:
RUN mkdir -p /etc/agentd/hooks.d \ && printf '#!/bin/sh\nsystemctl restart my-worker 2>/dev/null || pkill -HUP -f my-worker\n' \ > /etc/agentd/hooks.d/resume \ && chmod 0755 /etc/agentd/hooks.d/resumeHandlers run as root, and any process in the VM can trigger one by posting a hook path, so make them idempotent. The hook answers 200 even when the handler fails. The contract is in Protocol and the trust notes in Trust.
9. Working examples
Section titled “9. Working examples”The example Dockerfiles in the repository respect all of the above: coding-agents-on-bedrock adds Node, Python, and the Claude Code and Codex 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.