GuidesGetting started

Why your OpenClaw agent can't install anything in Docker, and what survives an image update

The official image runs as a non-root user with a lean toolset, so apt is out and anything installed into the container layer dies at the next pull. OPENCLAW_IMAGE_APT_PACKAGES, a persistent home volume, and what the docs say survives.

September 5, 2026Updated October 13, 2026The Everpod team
The short answer

Two facts about the official image explain it. The container runs as the node user (uid 1000), not root, so apt-get install is refused. And the image is deliberately lean: as of August 2026 the stock image has no SSH client, no pip, and no compiler, so an agent asked to clone a private repository over SSH finds nothing to do it with. Worse, anything the agent improvises into the container (a package unpacked into /tmp, a key generated into ~/.ssh) disappears the next time the container is replaced, because by default only the config directory, the workspace and one legacy secrets folder are mounted from the host. The documented fixes are OPENCLAW_IMAGE_APT_PACKAGES for system packages at build time and OPENCLAW_HOME_VOLUME to make the whole home directory persist.

Why the agent cannot help itself

On a laptop, an agent that needs a tool installs it and moves on. Inside the official container it hits both halves of how containers are built. A container is an immutable image plus a thin writable layer; the image is decided when it is built, and the writable layer is thrown away when the container is recreated, which every docker compose pull && up -d does. And the OpenClaw image runs its process as an unprivileged user: the docs state it plainly, “The image runs as node (uid 1000),” and recommend matching your bind mounts with chown -R 1000:1000. Without root there is no package manager to call, so the agent’s only moves are user-space ones: pip install --user if pip exists, npm install -g into a home prefix, downloading a static binary. All of which land in the home directory or in /tmp, which brings us to what survives.

What survives a container replacement by default

The docs list three bind mounts that persist across container replacement:

OPENCLAW_CONFIG_DIR               -> /home/node/.openclaw          # openclaw.json, SQLite state
OPENCLAW_WORKSPACE_DIR            -> /home/node/.openclaw/workspace
OPENCLAW_AUTH_PROFILE_SECRET_DIR  -> /home/node/.config/openclaw    # legacy encryption keys
Copied

Everything else under /home/node is the image’s, and gone on recreate. That is the list that bites: ~/.ssh (a deploy key the agent generated so it could reach GitHub), ~/.gitconfig (its name and email for commits), ~/.local (every pip install --user and every npm package installed into a home prefix), ~/.cache, and any tool the agent unpacked by hand. An agent with no SSH client and no way to install one can improvise: download the Debian package, extract it under /tmp, generate a key into ~/.ssh and get as far as GitHub’s key check. Both the binary and the key vanish at the next update with nobody the wiser, and the repository work then fails for no visible reason.

One more line from the same docs page belongs here: current OAuth token material “is stored as plaintext in SQLite under OPENCLAW_CONFIG_DIR, including access, refresh, and ID-token values.” The config directory is a credential store. Treat its backups as such.

Fix one: bake the system packages in

For anything Debian packages, the docs’ recommended route is to install it at image build time through the official setup script:

export OPENCLAW_IMAGE_APT_PACKAGES="openssh-client git build-essential python3-pip"
./scripts/docker/setup.sh
Copied

The variable takes a space-separated package list (the older name OPENCLAW_DOCKER_APT_PACKAGES still works as an alias), and because the packages become part of the image they are there after every recreate. The test for what belongs on the list is simple: anything the agent cannot obtain without administrator rights. An SSH client, git, a compiler, working pip. Application-level tools the agent can install as a user do not need to be here.

Fix two: make the home directory persistent

export OPENCLAW_HOME_VOLUME="openclaw_home"
./scripts/docker/setup.sh
Copied

This creates a named Docker volume for all of /home/node, so the keys, the git identity, the user-installed packages and the caches live through replacements exactly as they would on a real computer. The docs introduce it for one specific case, the Claude CLI and its sign-in state, but the reasoning is general: an agent’s home is where its working life accumulates. If you prefer a bind mount to a named volume, mounting a host directory at /home/node achieves the same thing with the ownership caveat above; check that nothing the image ships inside the home (the browser variant’s Chromium cache, for instance) is shadowed by the mount, and relocate it if it is.

Two things to know about the image itself

There are three variants: the base image, a -slim one, and -browser, which “includes Chromium + Xvfb baked in” so the browser tool works without a first-run Playwright download. And the tags move: latest, main and extended-stable are rebuilt weekly for OS security updates, while version tags and dated tags like 2026.8.1-r20260820 are immutable. The docs’ advice is to “pin either form when you do not want a deployment to follow a moving tag,” which is also the difference between an update you chose and one that arrived overnight and took the container layer with it. The Docker guide covers volumes and the port-binding trap; the GitHub guide has the deploy-key pattern that needs the SSH client this page is about.

Give your AI agents a permanent cloud computer.

We set it up and secure it, so all you do is use them. Run Claude Code, Codex or any agent you use, more at once than your laptop can hold, and keep them working when it’s closed. Or have OpenClaw set up and run for you.

See the developer pod

From $24/mo · your own plan or API key

Get your OpenClaw agent

$29/mo, cancel anytime · model usage included