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, 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: when we asked an agent on the stock image to clone a private repository over SSH in August 2026, it found no SSH client, no pip, and no compiler. 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

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. In the August incident the agent, finding no SSH client and no way to install one, downloaded the Debian package, extracted it under /tmp, generated a key into ~/.ssh and got as far as GitHub’s key check. Both the binary and the key would have vanished at the next update with nobody the wiser, and the repository work would have failed a week later 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

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

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.

Your own cloud agent, set up for you.

Everpod runs OpenClaw on a private, always-on computer of its own: set up, secured and backed up, with model usage included. You name your agent, and say hello about fifteen minutes later.

Create your agent

First month half price, then $29/mo · model usage included · cancel anytime

Wondering what you’d do with one? See what a cloud agent can do