docker: support optional pip packages in local builds (#83850)

Summary:
- Adds `OPENCLAW_IMAGE_PIP_PACKAGES` as an opt-in Dockerfile build arg, passes it through Docker and Podman local setup, and documents/tests the new local image-build option.
- Reproducibility: not applicable. this is an additive Docker/Podman build capability, not a bug report. The s ... image importing requested Python packages, and the branch diff wires the renamed arg through Docker/Podman.

Automerge notes:
- PR branch already contained follow-up commit before automerge: docker: support optional pip packages in local builds

Validation:
- ClawSweeper review passed for head 0ccec19206.
- Required merge gates passed before the squash merge.

Prepared head SHA: 0ccec19206
Review: https://github.com/openclaw/openclaw/pull/83850#issuecomment-4483676614

Co-authored-by: Stephen Redmond <stephen.redmond@straiteis.ie>
Co-authored-by: clawsweeper <274271284+clawsweeper[bot]@users.noreply.github.com>
Co-authored-by: clawsweeper[bot] <274271284+clawsweeper[bot]@users.noreply.github.com>
Approved-by: takhoffman
Co-authored-by: takhoffman <781889+takhoffman@users.noreply.github.com>
This commit is contained in:
clawsweeper[bot]
2026-05-19 02:23:13 +00:00
committed by GitHub
parent a559ccc084
commit ff4bf0c367
8 changed files with 57 additions and 4 deletions

View File

@@ -21,6 +21,7 @@ Docs: https://docs.openclaw.ai
- Browser: surface pending and recently handled modal dialogs in snapshots, return `blockedByDialog` when an action opens a modal, and allow `browser dialog --dialog-id` to answer pending dialogs.
- Browser CLI: add `openclaw browser evaluate --timeout-ms` so long-running page functions can extend both the evaluate action and request timeout budgets. (#83447) Thanks @eefreenyc.
- Codex app-server: scope OpenClaw prompt guidance by runtime surface so native Codex keeps Codex-owned base/personality instructions while OpenClaw contributes only runtime context, delivery guidance, and explicitly scoped command hints. (#83454) Thanks @100yenadmin.
- Docker/Podman: add `OPENCLAW_IMAGE_PIP_PACKAGES` for opt-in Python package installation in local image builds. (#83771) Thanks @stephenredmond-straiteis.
- Agents/tools: shorten built-in tool descriptions and schema hints across media, messaging, sessions, cron, Gateway, web, image/PDF, TTS, nodes, and plan tools while preserving routing guardrails.
- Skills: add node inspector debugging, fused diagram generation, and throwaway spike workflow skills.
- CLI/plugins: add `defineToolPlugin` plus `openclaw plugins build`, `validate`, and `init` for typed simple tool plugins with generated manifest metadata, optional tool declarations, and context factories.

View File

@@ -210,6 +210,19 @@ RUN --mount=type=cache,id=openclaw-bookworm-apt-cache,target=/var/cache/apt,shar
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends $packages; \
fi
# Install additional Python packages needed by your plugins or skills.
# Example: docker build --build-arg OPENCLAW_IMAGE_PIP_PACKAGES="requests humanize" .
ARG OPENCLAW_IMAGE_PIP_PACKAGES=""
RUN --mount=type=cache,id=openclaw-bookworm-apt-cache,target=/var/cache/apt,sharing=locked \
--mount=type=cache,id=openclaw-bookworm-apt-lists,target=/var/lib/apt,sharing=locked \
if [ -n "$OPENCLAW_IMAGE_PIP_PACKAGES" ]; then \
if ! python3 -m pip --version >/dev/null 2>&1; then \
apt-get update && \
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends python3-pip; \
fi && \
python3 -m pip install --no-cache-dir --break-system-packages $OPENCLAW_IMAGE_PIP_PACKAGES; \
fi
# Optionally install Chromium and Xvfb for browser automation.
# Build with: docker build --build-arg OPENCLAW_INSTALL_BROWSER=1 ...
# Adds ~300MB but eliminates the 60-90s Playwright install on every container start.

View File

@@ -127,6 +127,7 @@ The setup script accepts these optional environment variables:
| ------------------------------------------ | --------------------------------------------------------------------- |
| `OPENCLAW_IMAGE` | Use a remote image instead of building locally |
| `OPENCLAW_IMAGE_APT_PACKAGES` | Install extra apt packages during build (space-separated) |
| `OPENCLAW_IMAGE_PIP_PACKAGES` | Install extra Python packages during build (space-separated) |
| `OPENCLAW_EXTENSIONS` | Pre-install plugin dependencies at build time (space-separated names) |
| `OPENCLAW_EXTRA_MOUNTS` | Extra host bind mounts (comma-separated `source:target[:opts]`) |
| `OPENCLAW_HOME_VOLUME` | Persist `/home/node` in a named Docker volume |
@@ -148,6 +149,9 @@ container without `brew`; those dependencies must be provided by a custom image
or installed manually. For dependencies available from Debian packages, use
`OPENCLAW_IMAGE_APT_PACKAGES` during image build. The legacy
`OPENCLAW_DOCKER_APT_PACKAGES` name is still accepted.
For Python dependencies, use `OPENCLAW_IMAGE_PIP_PACKAGES`. This runs
`python3 -m pip install --break-system-packages` during the image build, so pin
package versions and use only package indexes you trust.
Maintainers can test bundled plugin source against a packaged image by mounting
one plugin source directory over its packaged source path, for example
@@ -423,13 +427,14 @@ See [ClawDock](/install/clawdock) for the full helper guide.
1. **Persist `/home/node`**: `export OPENCLAW_HOME_VOLUME="openclaw_home"`
2. **Bake system deps**: `export OPENCLAW_IMAGE_APT_PACKAGES="git curl jq"`
3. **Bake Playwright Chromium**: `export OPENCLAW_INSTALL_BROWSER=1`
4. **Or install Playwright browsers into a persisted volume**:
3. **Bake Python deps**: `export OPENCLAW_IMAGE_PIP_PACKAGES="requests==2.32.5 humanize==4.14.0"`
4. **Bake Playwright Chromium**: `export OPENCLAW_INSTALL_BROWSER=1`
5. **Or install Playwright browsers into a persisted volume**:
```bash
docker compose run --rm openclaw-cli \
node /app/node_modules/playwright-core/cli.js install chromium
```
5. **Persist browser downloads**: use `OPENCLAW_HOME_VOLUME` or
6. **Persist browser downloads**: use `OPENCLAW_HOME_VOLUME` or
`OPENCLAW_EXTRA_MOUNTS`. OpenClaw auto-detects the Docker image's
Playwright-managed Chromium on Linux.

View File

@@ -62,6 +62,7 @@ Optional build/setup env vars:
- `OPENCLAW_IMAGE` or `OPENCLAW_PODMAN_IMAGE` -- use an existing/pulled image instead of building `openclaw:local`
- `OPENCLAW_IMAGE_APT_PACKAGES` -- install extra apt packages during image build (also accepts legacy `OPENCLAW_DOCKER_APT_PACKAGES`)
- `OPENCLAW_IMAGE_PIP_PACKAGES` -- install extra Python packages during image build; pin versions and use only package indexes you trust
- `OPENCLAW_EXTENSIONS` -- pre-install plugin dependencies at build time
- `OPENCLAW_INSTALL_BROWSER` -- pre-install Chromium and Xvfb for browser automation (set to `1` to enable)

View File

@@ -180,9 +180,10 @@ vim ~/.openclaw/.env
See `.env.example` for all supported keys.
The `Dockerfile` supports two optional build args:
The `Dockerfile` supports optional build args:
- `OPENCLAW_IMAGE_APT_PACKAGES` — extra apt packages to install (e.g. `ffmpeg`); also accepts legacy `OPENCLAW_DOCKER_APT_PACKAGES`
- `OPENCLAW_IMAGE_PIP_PACKAGES` — extra Python packages to install (e.g. `requests==2.32.5`); pin versions and use only package indexes you trust
- `OPENCLAW_INSTALL_BROWSER=1` — pre-install Chromium for browser automation (adds ~300MB, but skips the 60-90s Playwright install on each container start)
### How It Works in Docker

View File

@@ -297,6 +297,7 @@ export OPENCLAW_GATEWAY_BIND="${OPENCLAW_GATEWAY_BIND:-lan}"
export OPENCLAW_DISABLE_BONJOUR="${OPENCLAW_DISABLE_BONJOUR:-}"
export OPENCLAW_IMAGE="$IMAGE_NAME"
export OPENCLAW_IMAGE_APT_PACKAGES="${OPENCLAW_IMAGE_APT_PACKAGES-${OPENCLAW_DOCKER_APT_PACKAGES:-}}"
export OPENCLAW_IMAGE_PIP_PACKAGES="${OPENCLAW_IMAGE_PIP_PACKAGES:-}"
export OPENCLAW_EXTENSIONS="${OPENCLAW_EXTENSIONS:-}"
export OPENCLAW_INSTALL_BROWSER="${OPENCLAW_INSTALL_BROWSER:-}"
export OPENCLAW_EXTRA_MOUNTS="$EXTRA_MOUNTS"
@@ -501,6 +502,7 @@ upsert_env "$ENV_FILE" \
OPENCLAW_EXTRA_MOUNTS \
OPENCLAW_HOME_VOLUME \
OPENCLAW_IMAGE_APT_PACKAGES \
OPENCLAW_IMAGE_PIP_PACKAGES \
OPENCLAW_EXTENSIONS \
OPENCLAW_INSTALL_BROWSER \
OPENCLAW_SANDBOX \
@@ -523,6 +525,7 @@ if [[ "$IMAGE_NAME" == "openclaw:local" ]]; then
echo "==> Building Docker image: $IMAGE_NAME"
run_docker_build \
--build-arg "OPENCLAW_IMAGE_APT_PACKAGES=${OPENCLAW_IMAGE_APT_PACKAGES}" \
--build-arg "OPENCLAW_IMAGE_PIP_PACKAGES=${OPENCLAW_IMAGE_PIP_PACKAGES}" \
--build-arg "OPENCLAW_EXTENSIONS=${OPENCLAW_EXTENSIONS}" \
--build-arg "OPENCLAW_INSTALL_BROWSER=${OPENCLAW_INSTALL_BROWSER}" \
--build-arg "OPENCLAW_INSTALL_DOCKER_CLI=${OPENCLAW_INSTALL_DOCKER_CLI:-}" \

View File

@@ -359,10 +359,14 @@ ensure_private_existing_dir_owned_by_user "config directory" "$OPENCLAW_CONFIG_D
ensure_private_existing_dir_owned_by_user "workspace directory" "$OPENCLAW_WORKSPACE_DIR"
OPENCLAW_IMAGE_APT_PACKAGES="${OPENCLAW_IMAGE_APT_PACKAGES-${OPENCLAW_DOCKER_APT_PACKAGES:-}}"
OPENCLAW_IMAGE_PIP_PACKAGES="${OPENCLAW_IMAGE_PIP_PACKAGES:-}"
BUILD_ARGS=()
if [[ -n "$OPENCLAW_IMAGE_APT_PACKAGES" ]]; then
BUILD_ARGS+=(--build-arg "OPENCLAW_IMAGE_APT_PACKAGES=${OPENCLAW_IMAGE_APT_PACKAGES}")
fi
if [[ -n "$OPENCLAW_IMAGE_PIP_PACKAGES" ]]; then
BUILD_ARGS+=(--build-arg "OPENCLAW_IMAGE_PIP_PACKAGES=${OPENCLAW_IMAGE_PIP_PACKAGES}")
fi
if [[ -n "${OPENCLAW_EXTENSIONS:-}" ]]; then
BUILD_ARGS+=(--build-arg "OPENCLAW_EXTENSIONS=${OPENCLAW_EXTENSIONS}")
fi

View File

@@ -3,6 +3,7 @@ import { describe, expect, it } from "vitest";
const SCRIPT_PATH = "scripts/test-install-sh-docker.sh";
const DOCKER_SETUP_PATH = "scripts/docker/setup.sh";
const PODMAN_SETUP_PATH = "scripts/podman/setup.sh";
const SMOKE_RUNNER_PATH = "scripts/docker/install-sh-smoke/run.sh";
const BUN_GLOBAL_SMOKE_PATH = "scripts/e2e/bun-global-install-smoke.sh";
const BUN_GLOBAL_ASSERTIONS_PATH = "scripts/e2e/lib/bun-global-install/assertions.mjs";
@@ -95,6 +96,30 @@ describe("test-install-sh-docker", () => {
expect(script).toContain('--build-arg "OPENCLAW_INSTALL_BROWSER=${OPENCLAW_INSTALL_BROWSER}"');
});
it("passes image-scoped pip packages through Docker and Podman setup", () => {
const dockerSetup = readFileSync(DOCKER_SETUP_PATH, "utf8");
const podmanSetup = readFileSync(PODMAN_SETUP_PATH, "utf8");
const dockerfile = readFileSync("Dockerfile", "utf8");
expect(dockerfile).toContain("ARG OPENCLAW_IMAGE_PIP_PACKAGES");
expect(dockerfile).toContain(
"python3 -m pip install --no-cache-dir --break-system-packages $OPENCLAW_IMAGE_PIP_PACKAGES",
);
expect(dockerSetup).toContain(
'export OPENCLAW_IMAGE_PIP_PACKAGES="${OPENCLAW_IMAGE_PIP_PACKAGES:-}"',
);
expect(dockerSetup).toContain("OPENCLAW_IMAGE_PIP_PACKAGES \\");
expect(dockerSetup).toContain(
'--build-arg "OPENCLAW_IMAGE_PIP_PACKAGES=${OPENCLAW_IMAGE_PIP_PACKAGES}"',
);
expect(dockerSetup).not.toContain("OPENCLAW_DOCKER_PIP_PACKAGES");
expect(podmanSetup).toContain('OPENCLAW_IMAGE_PIP_PACKAGES="${OPENCLAW_IMAGE_PIP_PACKAGES:-}"');
expect(podmanSetup).toContain(
'BUILD_ARGS+=(--build-arg "OPENCLAW_IMAGE_PIP_PACKAGES=${OPENCLAW_IMAGE_PIP_PACKAGES}")',
);
expect(podmanSetup).not.toContain("OPENCLAW_DOCKER_PIP_PACKAGES");
});
it("allows repository branch history and release tags for secret-backed Docker release checks", () => {
const workflow = readFileSync(LIVE_E2E_WORKFLOW_PATH, "utf8");