Container targets
What container targets give you
Section titled “What container targets give you”Each session on a container target runs in its own disposable, labeled
container: local Podman or Docker on Linux or WSL2, Apple’s container
runtime on macOS 26 or newer on Apple silicon, or Podman over SSH. Container
isolation always
selects Mjolnir’s unconstrained execution policy. The permissions setting is
only available for raw ssh-bare targets. Mjolnir translates the policy into the
selected harness’s own control: Codex agent-full-access, Claude Code
bypassPermissions, Kimi Code auto, Grok Build’s --always-approve launch
flag, or DeepSeek Harness’s danger-full-access permission mode. Every one of
those approves every call. Note that Kimi Code’s mode is named auto but is
not a guardian policy that approves only low-risk calls.
Raw localhost worktrees preserve the profile and harness’s configured approval behavior instead. Codex, Claude Code, and Grok Build expose guardian modes through their harnesses; Kimi Code and DeepSeek Harness do not. Mjolnir warns against running either unsupported harness on a raw, unsandboxed target.
Closing a session first writes and verifies a recovery archive, then removes that exact container. No mutable session workspace persists past the session except what the recovery archive captured and whatever you pushed to a remote. Mjolnir may retain read-only Git objects in the host clone cache described below.
Prerequisites
Section titled “Prerequisites”Install each runtime you want to use as a target:
- Rootless Podman 4.0 or newer on Linux or WSL2. See Podman for Mjolnir for installation and verification steps.
- Docker with a reachable Linux daemon on Linux or WSL2. See Docker for Mjolnir for its OverlayFS and lifecycle contract.
- Apple’s
containerCLI on macOS 26 or newer on Apple silicon.
Linux releases are static musl binaries, so the controller itself runs the
session relay in same-architecture Linux containers. The installer also places
the other supported Linux architecture’s
mj-worker-<arch>-unknown-linux-musl companion next to mj. On macOS it
installs both Linux companions.
Get the agent-dev image
Section titled “Get the agent-dev image”Mjolnir ships a reference container image with everything a session needs
pre-installed: Rust, cargo-nextest, Node 24, OpenJDK 25, Git, GitHub CLI, the
Codex and Claude ACP bridges, and pinned DeepSeek Harness plus
dsh-acp-server. It also carries Playwright’s Chromium system libraries and
the pre-installed Chromium headless shell in
PLAYWRIGHT_BROWSERS_PATH=/ms-playwright, so headless browser tests need no
privileged install and no run-time browser download, and the profiling tools
perf, cargo-flamegraph, samply, and heaptrack; perf additionally needs
the host’s kernel.perf_event_paranoid set to 1 or lower, or a host/runtime
that already provides the required capability. Mjolnir does not expose a
container-capability override. Optional local coverage tooling includes the
llvm-tools-preview component, pinned cargo-llvm-cov, and lcov for
genhtml. It’s published at
ghcr.io/brokkai/mjolnir/agent-dev:latest, public and
multi-arch for both linux/amd64 and linux/arm64, so the same image name
works whether Mjolnir is running it through Podman, Docker, Apple’s container
runtime, or an arm64 SSH host.
You don’t need to do anything to get it: mj setup’s image prompt already
defaults to this published image, and Podman, Docker, and Apple’s container
runtime pull an image automatically the first time it’s needed. Accepting the
default when you run mj setup, below, is enough.
Building it yourself remains a supported alternative, for example to customize the image or to work offline:
podman build --pull=always \ --file containers/Containerfile.agent-dev \ --tag localhost/mjolnir/agent-dev:latest \ containersRun mj setup
Section titled “Run mj setup”mj setupSetup reports the Codex, Claude Code, Kimi Code, Grok Build, and DeepSeek Harness homes it found, the GitHub origin of the current directory, and which local container runtimes are usable. If one or more are usable, it prompts you for:
- The container image, defaulting to
ghcr.io/brokkai/mjolnir/agent-dev:latest— press Enter to accept it, or enterlocalhost/mjolnir/agent-dev:latesthere if you built the image yourself above.
It creates one ordinary target for every usable runtime (for example, podman
and docker). Those targets appear independently in Mjolnir’s normal target
picker; setup does not choose one on your behalf.
A plain image such as ubuntu:24.04 still works if you enter it here: Mjolnir
auto-installs Git, GitHub CLI, and Node the first time a session needs them.
But that installation runs inside every new container, which slows down the
start of each session. The default agent-dev image avoids that cost.
Container targets default to pull_policy = "auto". Podman and Docker launches
do not wait on a registry under that default: they start from the image the host
already has and pull only when the host has no copy at all. The Mjolnir daemon
refreshes eligible remote :latest images for local or SSH Podman and local
Docker once an hour, and removes the dangling images each pull leaves behind.
Versioned tags remain cached, digest references stay pinned, and
localhost/... images remain local. Apple container is not part of that
background loop; it resolves auto and refreshes the image while provisioning
a session.
Set pull_policy beside image to always, newer, missing, or never
when a target needs an explicit policy. On Podman and Docker, always or
newer pulls during launch and remains eligible for the background refresh.
Apple evaluates the policy only during provisioning; always and newer
request a pull there. Existing running containers are never replaced in place.
Git clone cache
Section titled “Git clone cache”Local Podman, local Docker, SSH Podman, and Apple container targets cache GitHub repository
objects under the container host user’s ~/.cache/mjolnir/git. Before launch, Mjolnir
refreshes a bare mirror and creates an isolated session snapshot whose
immutable objects are shared with ordinary filesystem hardlinks. The snapshot
is mounted read-only and the normal in-container clone borrows its objects, so
branch selection, checkout filters, and image-specific Git behavior remain
unchanged.
This is an optimization rather than a prerequisite. If host Git, credentials,
or local hardlink cloning are unavailable, Mjolnir reports the cache miss and uses
the ordinary network clone. The first launch still populates the complete
mirror. Mjolnir removes session snapshots after their container, removes mirrors
unused for 30 days, and enforces a 20 GiB least-recently-used soft cap. The
cache can contain objects from private repositories and is created with
user-only permissions. You can remove ~/.cache/mjolnir/git/mirrors while no
launch is updating it; do not remove the sessions directory while managed
containers are running.
It then shows a summary of what it’s about to write and asks you to confirm
before writing config.toml. After you confirm, it runs a smoke test: it
creates a disposable container from the configured image, runs a trivial
command in it, and removes it, to prove the runtime actually works before you
start a real session.
Verify with mj doctor
Section titled “Verify with mj doctor”mj doctor --jsonThis prints a machine-readable array of prerequisite checks. Resolve every
check reported as fixable — each one includes what’s wrong and how to fix
it — then run mj doctor --json again. Repeat until none remain. The set of
checks Mjolnir runs is still growing, so treat the fixable status as
authoritative rather than checking for specific check names.
Once every check passes, run the same command with --smoke for an
end-to-end test: it creates and removes a disposable container the same way
mj setup does, confirming the full path works, not just static
prerequisites. For Docker, this also verifies that a temporary writable
attachment is copy-on-write and that its managed OverlayFS volume cleans up.
mj doctor --json --smokeFirst session
Section titled “First session”mjThis opens Mjolnir’s terminal surface. Press Alt-N from anywhere to start the new-session wizard. It walks you through picking a profile, a target, and a bundle.
Before launch, you can size the container’s CPU and memory allocation. The wizard starts with the allocation remembered for that physical host, or with 8 CPUs and 32 GiB when no allocation has been remembered:
| Key | Effect |
|---|---|
+ | Doubles the current allocation |
- | Halves the current allocation |
c | Adds 8 CPUs |
m | Adds 50% memory |
r | Resets to the 8-CPU/32-GiB baseline |
The wizard ends on a review screen where you can add, edit, or remove attached directories before launch. On container targets, each attached directory is mounted using the runtime’s isolated mount mode, so a container can’t write back into your host filesystem through it.
Each attached directory also has a read-only checkbox. Podman and Docker use copy-on-write OverlayFS mounts, which some filesystems cannot host: when Mjolnir finds a source on NFS, SMB, FUSE, a FAT-family filesystem, or another overlay, it attaches that directory read-only instead and says so while the session launches.
Two useful facts
Section titled “Two useful facts”If the gh CLI on the machine running Mjolnir is authenticated, Mjolnir continuously
syncs its active GitHub token into every live non-local session. That includes
managed containers, EC2, SSH Podman, and raw SSH targets, and lets gh and
HTTPS Git pushes work without copying SSH keys. The token never goes into a
recovery archive. Raw SSH targets are therefore inside the token’s trust
boundary; raw localhost sessions are deliberately excluded.
If Mjolnir or the host crashes, containers it was managing can be orphaned —
still running, but no longer tracked in Mjolnir’s state. Use mj recover to
find and reclaim them:
mj recover scan --jsonmj recover adopt --session <session-id> --target <target-id>mj recover destroy --session <session-id> --target <target-id> --confirm <session-id>scan lists managed containers that exist but aren’t in Mjolnir’s state. adopt
reconnects one back into Mjolnir as a tracked session; add --profile and
--bundle when the orphan predates Mjolnir’s ownership markers and can’t be
matched to a profile and bundle automatically. destroy removes one without
adopting it first; --confirm must repeat the session ID exactly, as a
safeguard against destroying the wrong container.