mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-24 03:19:49 +00:00
feat(docker): add image apt package build arg
feat(docker): add image apt package build arg Add OPENCLAW_IMAGE_APT_PACKAGES as the preferred runtime-neutral image build arg for Docker and Podman apt package installs while keeping OPENCLAW_DOCKER_APT_PACKAGES as the legacy fallback. Maintainer verification: - pnpm docs:list - node scripts/run-vitest.mjs run --config test/vitest/vitest.e2e.config.ts src/docker-setup.e2e.test.ts - node scripts/run-vitest.mjs src/dockerfile.test.ts test/scripts/test-install-sh-docker.test.ts - node scripts/run-vitest.mjs run --config test/vitest/vitest.cron.config.ts src/cron/isolated-agent.model-overrides.test.ts - pnpm exec oxfmt --check --threads=1 docs/install/docker.md docs/install/podman.md scripts/clawdock/README.md docs/help/faq.md CHANGELOG.md - git diff --check origin/main...HEAD - .agents/skills/autoreview/scripts/autoreview --mode local - .agents/skills/autoreview/scripts/autoreview --mode branch - pnpm check:changed via Blacksmith Testbox tbx_01krwqmfhcdekaczvrkxnb7t59, Actions run 26014630478, exit 0 Known CI note: checks-node-core-runtime-shared timed out repeatedly in unrelated src/cron/isolated-agent.model-overrides.test.ts on GitHub Actions; the same test passes locally after this rebase. Co-authored-by: Said Urtabajev <said@bumpclub.ee>
This commit is contained in:
@@ -7,6 +7,7 @@ Docs: https://docs.openclaw.ai
|
||||
### Changes
|
||||
|
||||
- Dependencies: update Pi packages to 0.75.1 and raise the minimum supported Node.js 22 line to 22.19.
|
||||
- Docker/Podman: add `OPENCLAW_IMAGE_APT_PACKAGES` as the runtime-neutral image build arg for extra apt packages while keeping `OPENCLAW_DOCKER_APT_PACKAGES` as a legacy fallback. (#62431) Thanks @urtabajev.
|
||||
- Mac app: redesign Settings pages with consistent card layouts, cached navigation, cleaner permissions/voice/skills/cron/exec/debug panes, and steadier spacing around the native sidebar.
|
||||
- Skills: rename the repo-local Codex closeout review skill and helper to `autoreview` while preserving the Codex-first fallback behavior.
|
||||
- Skills: add a meme-maker skill for curated template search, local SVG/PNG rendering, Imgflip hosted rendering, and Know Your Meme provenance links.
|
||||
|
||||
@@ -198,13 +198,16 @@ RUN install -d -m 0755 "$COREPACK_HOME" && \
|
||||
chmod -R a+rX "$COREPACK_HOME"
|
||||
|
||||
# Install additional system packages needed by your skills or extensions.
|
||||
# Example: docker build --build-arg OPENCLAW_DOCKER_APT_PACKAGES="python3 wget" .
|
||||
# Example: docker build --build-arg OPENCLAW_IMAGE_APT_PACKAGES="python3 wget" .
|
||||
# Legacy alias: OPENCLAW_DOCKER_APT_PACKAGES is still accepted as a fallback.
|
||||
ARG OPENCLAW_IMAGE_APT_PACKAGES
|
||||
ARG OPENCLAW_DOCKER_APT_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_DOCKER_APT_PACKAGES" ]; then \
|
||||
packages="${OPENCLAW_IMAGE_APT_PACKAGES-$OPENCLAW_DOCKER_APT_PACKAGES}"; \
|
||||
if [ -n "$packages" ]; then \
|
||||
apt-get update && \
|
||||
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends $OPENCLAW_DOCKER_APT_PACKAGES; \
|
||||
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends $packages; \
|
||||
fi
|
||||
|
||||
# Optionally install Chromium and Xvfb for browser automation.
|
||||
|
||||
@@ -451,7 +451,7 @@ lives on the [First-run FAQ](/help/faq-first-run).
|
||||
include system packages, Homebrew, or bundled browsers. For a fuller setup:
|
||||
|
||||
- Persist `/home/node` with `OPENCLAW_HOME_VOLUME` so caches survive.
|
||||
- Bake system deps into the image with `OPENCLAW_DOCKER_APT_PACKAGES`.
|
||||
- Bake system deps into the image with `OPENCLAW_IMAGE_APT_PACKAGES`.
|
||||
- Install Playwright browsers via the bundled CLI:
|
||||
`node /app/node_modules/playwright-core/cli.js install chromium`
|
||||
- Set `PLAYWRIGHT_BROWSERS_PATH` and ensure the path is persisted.
|
||||
|
||||
@@ -123,30 +123,31 @@ and setup-time config writes through `openclaw-gateway` with
|
||||
|
||||
The setup script accepts these optional environment variables:
|
||||
|
||||
| Variable | Purpose |
|
||||
| ------------------------------------------ | --------------------------------------------------------------- |
|
||||
| `OPENCLAW_IMAGE` | Use a remote image instead of building locally |
|
||||
| `OPENCLAW_DOCKER_APT_PACKAGES` | Install extra apt packages during build (space-separated) |
|
||||
| `OPENCLAW_EXTENSIONS` | Include selected bundled plugin helpers at build time |
|
||||
| `OPENCLAW_EXTRA_MOUNTS` | Extra host bind mounts (comma-separated `source:target[:opts]`) |
|
||||
| `OPENCLAW_HOME_VOLUME` | Persist `/home/node` in a named Docker volume |
|
||||
| `OPENCLAW_SANDBOX` | Opt in to sandbox bootstrap (`1`, `true`, `yes`, `on`) |
|
||||
| `OPENCLAW_SKIP_ONBOARDING` | Skip the interactive onboarding step (`1`, `true`, `yes`, `on`) |
|
||||
| `OPENCLAW_DOCKER_SOCKET` | Override Docker socket path |
|
||||
| `OPENCLAW_DISABLE_BONJOUR` | Disable Bonjour/mDNS advertising (defaults to `1` for Docker) |
|
||||
| `OPENCLAW_DISABLE_BUNDLED_SOURCE_OVERLAYS` | Disable bundled plugin source bind-mount overlays |
|
||||
| `OTEL_EXPORTER_OTLP_ENDPOINT` | Shared OTLP/HTTP collector endpoint for OpenTelemetry export |
|
||||
| `OTEL_EXPORTER_OTLP_*_ENDPOINT` | Signal-specific OTLP endpoints for traces, metrics, or logs |
|
||||
| `OTEL_EXPORTER_OTLP_PROTOCOL` | OTLP protocol override. Only `http/protobuf` is supported today |
|
||||
| `OTEL_SERVICE_NAME` | Service name used for OpenTelemetry resources |
|
||||
| `OTEL_SEMCONV_STABILITY_OPT_IN` | Opt in to latest experimental GenAI semantic attributes |
|
||||
| `OPENCLAW_OTEL_PRELOADED` | Skip starting a second OpenTelemetry SDK when one is preloaded |
|
||||
| Variable | Purpose |
|
||||
| ------------------------------------------ | --------------------------------------------------------------------- |
|
||||
| `OPENCLAW_IMAGE` | Use a remote image instead of building locally |
|
||||
| `OPENCLAW_IMAGE_APT_PACKAGES` | Install extra apt 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 |
|
||||
| `OPENCLAW_SANDBOX` | Opt in to sandbox bootstrap (`1`, `true`, `yes`, `on`) |
|
||||
| `OPENCLAW_SKIP_ONBOARDING` | Skip the interactive onboarding step (`1`, `true`, `yes`, `on`) |
|
||||
| `OPENCLAW_DOCKER_SOCKET` | Override Docker socket path |
|
||||
| `OPENCLAW_DISABLE_BONJOUR` | Disable Bonjour/mDNS advertising (defaults to `1` for Docker) |
|
||||
| `OPENCLAW_DISABLE_BUNDLED_SOURCE_OVERLAYS` | Disable bundled plugin source bind-mount overlays |
|
||||
| `OTEL_EXPORTER_OTLP_ENDPOINT` | Shared OTLP/HTTP collector endpoint for OpenTelemetry export |
|
||||
| `OTEL_EXPORTER_OTLP_*_ENDPOINT` | Signal-specific OTLP endpoints for traces, metrics, or logs |
|
||||
| `OTEL_EXPORTER_OTLP_PROTOCOL` | OTLP protocol override. Only `http/protobuf` is supported today |
|
||||
| `OTEL_SERVICE_NAME` | Service name used for OpenTelemetry resources |
|
||||
| `OTEL_SEMCONV_STABILITY_OPT_IN` | Opt in to latest experimental GenAI semantic attributes |
|
||||
| `OPENCLAW_OTEL_PRELOADED` | Skip starting a second OpenTelemetry SDK when one is preloaded |
|
||||
|
||||
The official Docker image does not ship Homebrew. During onboarding, OpenClaw
|
||||
hides brew-only skill dependency installers when it is running in a Linux
|
||||
container without `brew`; those dependencies must be provided by a custom image
|
||||
or installed manually. For dependencies available from Debian packages, use
|
||||
`OPENCLAW_DOCKER_APT_PACKAGES` during image build.
|
||||
`OPENCLAW_IMAGE_APT_PACKAGES` during image build. The legacy
|
||||
`OPENCLAW_DOCKER_APT_PACKAGES` name is still accepted.
|
||||
|
||||
Maintainers can test bundled plugin source against a packaged image by mounting
|
||||
one plugin source directory over its packaged source path, for example
|
||||
@@ -421,7 +422,7 @@ See [ClawDock](/install/clawdock) for the full helper guide.
|
||||
full-featured container:
|
||||
|
||||
1. **Persist `/home/node`**: `export OPENCLAW_HOME_VOLUME="openclaw_home"`
|
||||
2. **Bake system deps**: `export OPENCLAW_DOCKER_APT_PACKAGES="git curl jq"`
|
||||
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**:
|
||||
```bash
|
||||
|
||||
@@ -61,7 +61,7 @@ You can also set `OPENCLAW_PODMAN_QUADLET=1`.
|
||||
Optional build/setup env vars:
|
||||
|
||||
- `OPENCLAW_IMAGE` or `OPENCLAW_PODMAN_IMAGE` -- use an existing/pulled image instead of building `openclaw:local`
|
||||
- `OPENCLAW_DOCKER_APT_PACKAGES` -- install extra apt packages during image build
|
||||
- `OPENCLAW_IMAGE_APT_PACKAGES` -- install extra apt packages during image build (also accepts legacy `OPENCLAW_DOCKER_APT_PACKAGES`)
|
||||
- `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)
|
||||
|
||||
|
||||
@@ -182,7 +182,7 @@ See `.env.example` for all supported keys.
|
||||
|
||||
The `Dockerfile` supports two optional build args:
|
||||
|
||||
- `OPENCLAW_DOCKER_APT_PACKAGES` — extra apt packages to install (e.g. `ffmpeg`)
|
||||
- `OPENCLAW_IMAGE_APT_PACKAGES` — extra apt packages to install (e.g. `ffmpeg`); also accepts legacy `OPENCLAW_DOCKER_APT_PACKAGES`
|
||||
- `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
|
||||
|
||||
@@ -296,7 +296,7 @@ export OPENCLAW_BRIDGE_PORT="${OPENCLAW_BRIDGE_PORT:-18790}"
|
||||
export OPENCLAW_GATEWAY_BIND="${OPENCLAW_GATEWAY_BIND:-lan}"
|
||||
export OPENCLAW_DISABLE_BONJOUR="${OPENCLAW_DISABLE_BONJOUR:-}"
|
||||
export OPENCLAW_IMAGE="$IMAGE_NAME"
|
||||
export OPENCLAW_DOCKER_APT_PACKAGES="${OPENCLAW_DOCKER_APT_PACKAGES:-}"
|
||||
export OPENCLAW_IMAGE_APT_PACKAGES="${OPENCLAW_IMAGE_APT_PACKAGES-${OPENCLAW_DOCKER_APT_PACKAGES:-}}"
|
||||
export OPENCLAW_EXTENSIONS="${OPENCLAW_EXTENSIONS:-}"
|
||||
export OPENCLAW_INSTALL_BROWSER="${OPENCLAW_INSTALL_BROWSER:-}"
|
||||
export OPENCLAW_EXTRA_MOUNTS="$EXTRA_MOUNTS"
|
||||
@@ -500,7 +500,7 @@ upsert_env "$ENV_FILE" \
|
||||
OPENCLAW_IMAGE \
|
||||
OPENCLAW_EXTRA_MOUNTS \
|
||||
OPENCLAW_HOME_VOLUME \
|
||||
OPENCLAW_DOCKER_APT_PACKAGES \
|
||||
OPENCLAW_IMAGE_APT_PACKAGES \
|
||||
OPENCLAW_EXTENSIONS \
|
||||
OPENCLAW_INSTALL_BROWSER \
|
||||
OPENCLAW_SANDBOX \
|
||||
@@ -522,7 +522,7 @@ upsert_env "$ENV_FILE" \
|
||||
if [[ "$IMAGE_NAME" == "openclaw:local" ]]; then
|
||||
echo "==> Building Docker image: $IMAGE_NAME"
|
||||
run_docker_build \
|
||||
--build-arg "OPENCLAW_DOCKER_APT_PACKAGES=${OPENCLAW_DOCKER_APT_PACKAGES}" \
|
||||
--build-arg "OPENCLAW_IMAGE_APT_PACKAGES=${OPENCLAW_IMAGE_APT_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:-}" \
|
||||
|
||||
@@ -358,9 +358,10 @@ install -d -m 700 "$OPENCLAW_CONFIG_DIR" "$OPENCLAW_WORKSPACE_DIR"
|
||||
ensure_private_existing_dir_owned_by_user "config directory" "$OPENCLAW_CONFIG_DIR"
|
||||
ensure_private_existing_dir_owned_by_user "workspace directory" "$OPENCLAW_WORKSPACE_DIR"
|
||||
|
||||
OPENCLAW_IMAGE_APT_PACKAGES="${OPENCLAW_IMAGE_APT_PACKAGES-${OPENCLAW_DOCKER_APT_PACKAGES:-}}"
|
||||
BUILD_ARGS=()
|
||||
if [[ -n "${OPENCLAW_DOCKER_APT_PACKAGES:-}" ]]; then
|
||||
BUILD_ARGS+=(--build-arg "OPENCLAW_DOCKER_APT_PACKAGES=${OPENCLAW_DOCKER_APT_PACKAGES}")
|
||||
if [[ -n "$OPENCLAW_IMAGE_APT_PACKAGES" ]]; then
|
||||
BUILD_ARGS+=(--build-arg "OPENCLAW_IMAGE_APT_PACKAGES=${OPENCLAW_IMAGE_APT_PACKAGES}")
|
||||
fi
|
||||
if [[ -n "${OPENCLAW_EXTENSIONS:-}" ]]; then
|
||||
BUILD_ARGS+=(--build-arg "OPENCLAW_EXTENSIONS=${OPENCLAW_EXTENSIONS}")
|
||||
|
||||
@@ -258,13 +258,13 @@ describe("scripts/docker/setup.sh", () => {
|
||||
const activeSandbox = requireSandbox(sandbox);
|
||||
|
||||
const result = runDockerSetup(activeSandbox, {
|
||||
OPENCLAW_DOCKER_APT_PACKAGES: "ffmpeg build-essential",
|
||||
OPENCLAW_DOCKER_APT_PACKAGES: "curl wget",
|
||||
OPENCLAW_EXTRA_MOUNTS: undefined,
|
||||
OPENCLAW_HOME_VOLUME: "openclaw-home",
|
||||
});
|
||||
expect(result.status).toBe(0);
|
||||
const envFile = await readFile(join(activeSandbox.rootDir, ".env"), "utf8");
|
||||
expect(envFile).toContain("OPENCLAW_DOCKER_APT_PACKAGES=ffmpeg build-essential");
|
||||
expect(envFile).toContain("OPENCLAW_IMAGE_APT_PACKAGES=curl wget");
|
||||
expect(envFile).toContain("OPENCLAW_EXTRA_MOUNTS=");
|
||||
expect(envFile).toContain("OPENCLAW_HOME_VOLUME=openclaw-home"); // pragma: allowlist secret
|
||||
expect(envFile).toContain("OPENCLAW_DISABLE_BONJOUR=");
|
||||
@@ -282,7 +282,7 @@ describe("scripts/docker/setup.sh", () => {
|
||||
expect(extraCompose).toContain("volumes:");
|
||||
expect(extraCompose).toContain("openclaw-home:");
|
||||
const log = await readDockerLog(activeSandbox);
|
||||
expect(log).toContain("--build-arg OPENCLAW_DOCKER_APT_PACKAGES=ffmpeg build-essential");
|
||||
expect(log).toContain("--build-arg OPENCLAW_IMAGE_APT_PACKAGES=curl wget");
|
||||
expect(log).toContain(
|
||||
`run --rm --no-deps ${prestartContainerEnvFlags} --entrypoint node openclaw-gateway dist/index.js onboard --mode local --no-install-daemon`,
|
||||
);
|
||||
@@ -304,6 +304,61 @@ describe("scripts/docker/setup.sh", () => {
|
||||
expect(envFile).toContain("OPENCLAW_DISABLE_BONJOUR=0");
|
||||
});
|
||||
|
||||
it("normalizes legacy OPENCLAW_DOCKER_APT_PACKAGES into OPENCLAW_IMAGE_APT_PACKAGES", async () => {
|
||||
const activeSandbox = requireSandbox(sandbox);
|
||||
await resetDockerLog(activeSandbox);
|
||||
|
||||
const result = runDockerSetup(activeSandbox, {
|
||||
OPENCLAW_DOCKER_APT_PACKAGES: "curl wget",
|
||||
});
|
||||
expect(result.status).toBe(0);
|
||||
|
||||
const envFile = await readFile(join(activeSandbox.rootDir, ".env"), "utf8");
|
||||
expect(envFile).toContain("OPENCLAW_IMAGE_APT_PACKAGES=curl wget");
|
||||
expect(envFile).not.toContain("OPENCLAW_DOCKER_APT_PACKAGES");
|
||||
|
||||
const log = await readDockerLog(activeSandbox);
|
||||
expect(log).toContain("--build-arg OPENCLAW_IMAGE_APT_PACKAGES=curl wget");
|
||||
expect(log).not.toContain("--build-arg OPENCLAW_DOCKER_APT_PACKAGES");
|
||||
});
|
||||
|
||||
it("prefers OPENCLAW_IMAGE_APT_PACKAGES over legacy OPENCLAW_DOCKER_APT_PACKAGES", async () => {
|
||||
const activeSandbox = requireSandbox(sandbox);
|
||||
await resetDockerLog(activeSandbox);
|
||||
|
||||
const result = runDockerSetup(activeSandbox, {
|
||||
OPENCLAW_IMAGE_APT_PACKAGES: "curl wget httpie",
|
||||
OPENCLAW_DOCKER_APT_PACKAGES: "curl wget",
|
||||
});
|
||||
expect(result.status).toBe(0);
|
||||
|
||||
const envFile = await readFile(join(activeSandbox.rootDir, ".env"), "utf8");
|
||||
expect(envFile).toContain("OPENCLAW_IMAGE_APT_PACKAGES=curl wget httpie");
|
||||
expect(envFile).not.toContain("OPENCLAW_DOCKER_APT_PACKAGES");
|
||||
|
||||
const log = await readDockerLog(activeSandbox);
|
||||
expect(log).toContain("--build-arg OPENCLAW_IMAGE_APT_PACKAGES=curl wget httpie");
|
||||
expect(log).not.toMatch(/--build-arg OPENCLAW_IMAGE_APT_PACKAGES=curl wget(?! httpie)/);
|
||||
});
|
||||
|
||||
it("explicitly empty OPENCLAW_IMAGE_APT_PACKAGES suppresses legacy fallback", async () => {
|
||||
const activeSandbox = requireSandbox(sandbox);
|
||||
await resetDockerLog(activeSandbox);
|
||||
|
||||
const result = runDockerSetup(activeSandbox, {
|
||||
OPENCLAW_IMAGE_APT_PACKAGES: "",
|
||||
OPENCLAW_DOCKER_APT_PACKAGES: "curl wget",
|
||||
});
|
||||
expect(result.status).toBe(0);
|
||||
|
||||
const envFile = await readFile(join(activeSandbox.rootDir, ".env"), "utf8");
|
||||
expect(envFile).toContain("OPENCLAW_IMAGE_APT_PACKAGES=");
|
||||
expect(envFile).not.toContain("curl wget");
|
||||
|
||||
const log = await readDockerLog(activeSandbox);
|
||||
expect(log).not.toContain("--build-arg OPENCLAW_IMAGE_APT_PACKAGES=curl wget");
|
||||
});
|
||||
|
||||
it("avoids shared-network openclaw-cli before the gateway is started", async () => {
|
||||
const activeSandbox = requireSandbox(sandbox);
|
||||
|
||||
@@ -742,4 +797,19 @@ describe("scripts/docker/setup.sh", () => {
|
||||
compose.match(/OPENCLAW_WORKSPACE_DIR: \/home\/node\/\.openclaw\/workspace$/gm),
|
||||
).toHaveLength(2);
|
||||
});
|
||||
|
||||
it("Dockerfile ARG OPENCLAW_IMAGE_APT_PACKAGES must not have a default value", async () => {
|
||||
// If the ARG has a default (e.g. ARG OPENCLAW_IMAGE_APT_PACKAGES=""), Docker treats it as
|
||||
// "set" even when no --build-arg is passed. That breaks the RUN fallback expression
|
||||
// ${OPENCLAW_IMAGE_APT_PACKAGES-$OPENCLAW_DOCKER_APT_PACKAGES} because the variable is
|
||||
// never truly unset, so legacy-only callers using --build-arg OPENCLAW_DOCKER_APT_PACKAGES
|
||||
// get nothing installed — a backward-compat regression.
|
||||
const dockerfile = await readFile(join(repoRoot, "Dockerfile"), "utf8");
|
||||
const argLine = dockerfile
|
||||
.split("\n")
|
||||
.find((line) => line.startsWith("ARG OPENCLAW_IMAGE_APT_PACKAGES"));
|
||||
expect(argLine).toBeDefined();
|
||||
// Must be bare `ARG OPENCLAW_IMAGE_APT_PACKAGES` with no default assignment
|
||||
expect(argLine).toBe("ARG OPENCLAW_IMAGE_APT_PACKAGES");
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user