mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-19 12:48:38 +00:00
container builds: opt-in extension deps via OPENCLAW_EXTENSIONS build arg (#32223)
* Docker: opt-in extension deps via OPENCLAW_EXTENSIONS build arg Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> Signed-off-by: sallyom <somalley@redhat.com> * CI: clarify extension smoke scope * Tests: allow digest-pinned multi-stage FROM lines * Changelog: note container extension preinstall option --------- Signed-off-by: sallyom <somalley@redhat.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
This commit is contained in:
10
.github/workflows/install-smoke.yml
vendored
10
.github/workflows/install-smoke.yml
vendored
@@ -41,6 +41,16 @@ jobs:
|
|||||||
docker build -t openclaw-dockerfile-smoke:local -f Dockerfile .
|
docker build -t openclaw-dockerfile-smoke:local -f Dockerfile .
|
||||||
docker run --rm --entrypoint sh openclaw-dockerfile-smoke:local -lc 'which openclaw && openclaw --version'
|
docker run --rm --entrypoint sh openclaw-dockerfile-smoke:local -lc 'which openclaw && openclaw --version'
|
||||||
|
|
||||||
|
# This smoke only validates that the build-arg path preinstalls selected
|
||||||
|
# extension deps without breaking image build or basic CLI startup. It
|
||||||
|
# does not exercise runtime loading/registration of diagnostics-otel.
|
||||||
|
- name: Smoke test Dockerfile with extension build arg
|
||||||
|
run: |
|
||||||
|
docker build \
|
||||||
|
--build-arg OPENCLAW_EXTENSIONS="diagnostics-otel" \
|
||||||
|
-t openclaw-ext-smoke:local -f Dockerfile .
|
||||||
|
docker run --rm --entrypoint sh openclaw-ext-smoke:local -lc 'which openclaw && openclaw --version'
|
||||||
|
|
||||||
- name: Run installer docker tests
|
- name: Run installer docker tests
|
||||||
env:
|
env:
|
||||||
CLAWDBOT_INSTALL_URL: https://openclaw.ai/install.sh
|
CLAWDBOT_INSTALL_URL: https://openclaw.ai/install.sh
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ Docs: https://docs.openclaw.ai
|
|||||||
- Hooks/Compaction lifecycle: emit `session:compact:before` and `session:compact:after` internal events plus plugin compaction callbacks with session/count metadata, so automations can react to compaction runs consistently. (#16788) thanks @vincentkoc.
|
- Hooks/Compaction lifecycle: emit `session:compact:before` and `session:compact:after` internal events plus plugin compaction callbacks with session/count metadata, so automations can react to compaction runs consistently. (#16788) thanks @vincentkoc.
|
||||||
- Agents/context engine plugin interface: add `ContextEngine` plugin slot with full lifecycle hooks (`bootstrap`, `ingest`, `assemble`, `compact`, `afterTurn`, `prepareSubagentSpawn`, `onSubagentEnded`), slot-based registry with config-driven resolution, `LegacyContextEngine` wrapper preserving existing compaction behavior, scoped subagent runtime for plugin runtimes via `AsyncLocalStorage`, and `sessions.get` gateway method. Enables plugins like `lossless-claw` to provide alternative context management strategies without modifying core compaction logic. Zero behavior change when no context engine plugin is configured. (#22201) thanks @jalehman.
|
- Agents/context engine plugin interface: add `ContextEngine` plugin slot with full lifecycle hooks (`bootstrap`, `ingest`, `assemble`, `compact`, `afterTurn`, `prepareSubagentSpawn`, `onSubagentEnded`), slot-based registry with config-driven resolution, `LegacyContextEngine` wrapper preserving existing compaction behavior, scoped subagent runtime for plugin runtimes via `AsyncLocalStorage`, and `sessions.get` gateway method. Enables plugins like `lossless-claw` to provide alternative context management strategies without modifying core compaction logic. Zero behavior change when no context engine plugin is configured. (#22201) thanks @jalehman.
|
||||||
- CLI: make read-only SecretRef status flows degrade safely (#37023) thanks @joshavant.
|
- CLI: make read-only SecretRef status flows degrade safely (#37023) thanks @joshavant.
|
||||||
|
- Docker/Podman extension dependency baking: add `OPENCLAW_EXTENSIONS` so container builds can preinstall selected bundled extension npm dependencies into the image for faster and more reproducible startup in container deployments. (#32223) Thanks @sallyom.
|
||||||
|
|
||||||
### Breaking
|
### Breaking
|
||||||
|
|
||||||
|
|||||||
21
Dockerfile
21
Dockerfile
@@ -1,3 +1,22 @@
|
|||||||
|
# Opt-in extension dependencies at build time (space-separated directory names).
|
||||||
|
# Example: docker build --build-arg OPENCLAW_EXTENSIONS="diagnostics-otel matrix" .
|
||||||
|
#
|
||||||
|
# A multi-stage build is used instead of `RUN --mount=type=bind` because
|
||||||
|
# bind mounts require BuildKit, which is not available in plain Docker.
|
||||||
|
# This stage extracts only the package.json files we need from extensions/,
|
||||||
|
# so the main build layer is not invalidated by unrelated extension source changes.
|
||||||
|
ARG OPENCLAW_EXTENSIONS=""
|
||||||
|
FROM node:22-bookworm@sha256:cd7bcd2e7a1e6f72052feb023c7f6b722205d3fcab7bbcbd2d1bfdab10b1e935 AS ext-deps
|
||||||
|
ARG OPENCLAW_EXTENSIONS
|
||||||
|
COPY extensions /tmp/extensions
|
||||||
|
RUN mkdir -p /out && \
|
||||||
|
for ext in $OPENCLAW_EXTENSIONS; do \
|
||||||
|
if [ -f "/tmp/extensions/$ext/package.json" ]; then \
|
||||||
|
mkdir -p "/out/$ext" && \
|
||||||
|
cp "/tmp/extensions/$ext/package.json" "/out/$ext/package.json"; \
|
||||||
|
fi; \
|
||||||
|
done
|
||||||
|
|
||||||
FROM node:22-bookworm@sha256:cd7bcd2e7a1e6f72052feb023c7f6b722205d3fcab7bbcbd2d1bfdab10b1e935
|
FROM node:22-bookworm@sha256:cd7bcd2e7a1e6f72052feb023c7f6b722205d3fcab7bbcbd2d1bfdab10b1e935
|
||||||
|
|
||||||
# OCI base-image metadata for downstream image consumers.
|
# OCI base-image metadata for downstream image consumers.
|
||||||
@@ -35,6 +54,8 @@ COPY --chown=node:node ui/package.json ./ui/package.json
|
|||||||
COPY --chown=node:node patches ./patches
|
COPY --chown=node:node patches ./patches
|
||||||
COPY --chown=node:node scripts ./scripts
|
COPY --chown=node:node scripts ./scripts
|
||||||
|
|
||||||
|
COPY --from=ext-deps --chown=node:node /out/ ./extensions/
|
||||||
|
|
||||||
USER node
|
USER node
|
||||||
# Reduce OOM risk on low-memory hosts during dependency installation.
|
# Reduce OOM risk on low-memory hosts during dependency installation.
|
||||||
# Docker builds on small VMs may otherwise fail with "Killed" (exit 137).
|
# Docker builds on small VMs may otherwise fail with "Killed" (exit 137).
|
||||||
|
|||||||
@@ -200,6 +200,7 @@ export OPENCLAW_BRIDGE_PORT="${OPENCLAW_BRIDGE_PORT:-18790}"
|
|||||||
export OPENCLAW_GATEWAY_BIND="${OPENCLAW_GATEWAY_BIND:-lan}"
|
export OPENCLAW_GATEWAY_BIND="${OPENCLAW_GATEWAY_BIND:-lan}"
|
||||||
export OPENCLAW_IMAGE="$IMAGE_NAME"
|
export OPENCLAW_IMAGE="$IMAGE_NAME"
|
||||||
export OPENCLAW_DOCKER_APT_PACKAGES="${OPENCLAW_DOCKER_APT_PACKAGES:-}"
|
export OPENCLAW_DOCKER_APT_PACKAGES="${OPENCLAW_DOCKER_APT_PACKAGES:-}"
|
||||||
|
export OPENCLAW_EXTENSIONS="${OPENCLAW_EXTENSIONS:-}"
|
||||||
export OPENCLAW_EXTRA_MOUNTS="$EXTRA_MOUNTS"
|
export OPENCLAW_EXTRA_MOUNTS="$EXTRA_MOUNTS"
|
||||||
export OPENCLAW_HOME_VOLUME="$HOME_VOLUME_NAME"
|
export OPENCLAW_HOME_VOLUME="$HOME_VOLUME_NAME"
|
||||||
export OPENCLAW_ALLOW_INSECURE_PRIVATE_WS="${OPENCLAW_ALLOW_INSECURE_PRIVATE_WS:-}"
|
export OPENCLAW_ALLOW_INSECURE_PRIVATE_WS="${OPENCLAW_ALLOW_INSECURE_PRIVATE_WS:-}"
|
||||||
@@ -378,6 +379,7 @@ upsert_env "$ENV_FILE" \
|
|||||||
OPENCLAW_EXTRA_MOUNTS \
|
OPENCLAW_EXTRA_MOUNTS \
|
||||||
OPENCLAW_HOME_VOLUME \
|
OPENCLAW_HOME_VOLUME \
|
||||||
OPENCLAW_DOCKER_APT_PACKAGES \
|
OPENCLAW_DOCKER_APT_PACKAGES \
|
||||||
|
OPENCLAW_EXTENSIONS \
|
||||||
OPENCLAW_SANDBOX \
|
OPENCLAW_SANDBOX \
|
||||||
OPENCLAW_DOCKER_SOCKET \
|
OPENCLAW_DOCKER_SOCKET \
|
||||||
DOCKER_GID \
|
DOCKER_GID \
|
||||||
@@ -388,6 +390,7 @@ if [[ "$IMAGE_NAME" == "openclaw:local" ]]; then
|
|||||||
echo "==> Building Docker image: $IMAGE_NAME"
|
echo "==> Building Docker image: $IMAGE_NAME"
|
||||||
docker build \
|
docker build \
|
||||||
--build-arg "OPENCLAW_DOCKER_APT_PACKAGES=${OPENCLAW_DOCKER_APT_PACKAGES}" \
|
--build-arg "OPENCLAW_DOCKER_APT_PACKAGES=${OPENCLAW_DOCKER_APT_PACKAGES}" \
|
||||||
|
--build-arg "OPENCLAW_EXTENSIONS=${OPENCLAW_EXTENSIONS}" \
|
||||||
--build-arg "OPENCLAW_INSTALL_DOCKER_CLI=${OPENCLAW_INSTALL_DOCKER_CLI:-}" \
|
--build-arg "OPENCLAW_INSTALL_DOCKER_CLI=${OPENCLAW_INSTALL_DOCKER_CLI:-}" \
|
||||||
-t "$IMAGE_NAME" \
|
-t "$IMAGE_NAME" \
|
||||||
-f "$ROOT_DIR/Dockerfile" \
|
-f "$ROOT_DIR/Dockerfile" \
|
||||||
|
|||||||
@@ -60,6 +60,7 @@ Optional env vars:
|
|||||||
|
|
||||||
- `OPENCLAW_IMAGE` — use a remote image instead of building locally (e.g. `ghcr.io/openclaw/openclaw:latest`)
|
- `OPENCLAW_IMAGE` — use a remote image instead of building locally (e.g. `ghcr.io/openclaw/openclaw:latest`)
|
||||||
- `OPENCLAW_DOCKER_APT_PACKAGES` — install extra apt packages during build
|
- `OPENCLAW_DOCKER_APT_PACKAGES` — install extra apt packages during build
|
||||||
|
- `OPENCLAW_EXTENSIONS` — pre-install extension dependencies at build time (space-separated extension names, e.g. `diagnostics-otel matrix`)
|
||||||
- `OPENCLAW_EXTRA_MOUNTS` — add extra host bind mounts
|
- `OPENCLAW_EXTRA_MOUNTS` — add extra host bind mounts
|
||||||
- `OPENCLAW_HOME_VOLUME` — persist `/home/node` in a named volume
|
- `OPENCLAW_HOME_VOLUME` — persist `/home/node` in a named volume
|
||||||
- `OPENCLAW_SANDBOX` — opt in to Docker gateway sandbox bootstrap. Only explicit truthy values enable it: `1`, `true`, `yes`, `on`
|
- `OPENCLAW_SANDBOX` — opt in to Docker gateway sandbox bootstrap. Only explicit truthy values enable it: `1`, `true`, `yes`, `on`
|
||||||
@@ -320,6 +321,31 @@ Notes:
|
|||||||
- If you change `OPENCLAW_DOCKER_APT_PACKAGES`, rerun `docker-setup.sh` to rebuild
|
- If you change `OPENCLAW_DOCKER_APT_PACKAGES`, rerun `docker-setup.sh` to rebuild
|
||||||
the image.
|
the image.
|
||||||
|
|
||||||
|
### Pre-install extension dependencies (optional)
|
||||||
|
|
||||||
|
Extensions with their own `package.json` (e.g. `diagnostics-otel`, `matrix`,
|
||||||
|
`msteams`) install their npm dependencies on first load. To bake those
|
||||||
|
dependencies into the image instead, set `OPENCLAW_EXTENSIONS` before
|
||||||
|
running `docker-setup.sh`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export OPENCLAW_EXTENSIONS="diagnostics-otel matrix"
|
||||||
|
./docker-setup.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
Or when building directly:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker build --build-arg OPENCLAW_EXTENSIONS="diagnostics-otel matrix" .
|
||||||
|
```
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
|
||||||
|
- This accepts a space-separated list of extension directory names (under `extensions/`).
|
||||||
|
- Only extensions with a `package.json` are affected; lightweight plugins without one are ignored.
|
||||||
|
- If you change `OPENCLAW_EXTENSIONS`, rerun `docker-setup.sh` to rebuild
|
||||||
|
the image.
|
||||||
|
|
||||||
### Power-user / full-featured container (opt-in)
|
### Power-user / full-featured container (opt-in)
|
||||||
|
|
||||||
The default Docker image is **security-first** and runs as the non-root `node`
|
The default Docker image is **security-first** and runs as the non-root `node`
|
||||||
|
|||||||
@@ -32,6 +32,11 @@ By default the container is **not** installed as a systemd service, you start it
|
|||||||
|
|
||||||
(Or set `OPENCLAW_PODMAN_QUADLET=1`; use `--container` to install only the container and launch script.)
|
(Or set `OPENCLAW_PODMAN_QUADLET=1`; use `--container` to install only the container and launch script.)
|
||||||
|
|
||||||
|
Optional build-time env vars (set before running `setup-podman.sh`):
|
||||||
|
|
||||||
|
- `OPENCLAW_DOCKER_APT_PACKAGES` — install extra apt packages during image build
|
||||||
|
- `OPENCLAW_EXTENSIONS` — pre-install extension dependencies (space-separated extension names, e.g. `diagnostics-otel matrix`)
|
||||||
|
|
||||||
**2. Start gateway** (manual, for quick smoke testing):
|
**2. Start gateway** (manual, for quick smoke testing):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|||||||
@@ -209,7 +209,10 @@ if ! run_as_openclaw test -f "$OPENCLAW_JSON"; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
echo "Building image from $REPO_PATH..."
|
echo "Building image from $REPO_PATH..."
|
||||||
podman build -t openclaw:local -f "$REPO_PATH/Dockerfile" "$REPO_PATH"
|
BUILD_ARGS=()
|
||||||
|
[[ -n "${OPENCLAW_DOCKER_APT_PACKAGES:-}" ]] && BUILD_ARGS+=(--build-arg "OPENCLAW_DOCKER_APT_PACKAGES=${OPENCLAW_DOCKER_APT_PACKAGES}")
|
||||||
|
[[ -n "${OPENCLAW_EXTENSIONS:-}" ]] && BUILD_ARGS+=(--build-arg "OPENCLAW_EXTENSIONS=${OPENCLAW_EXTENSIONS}")
|
||||||
|
podman build ${BUILD_ARGS[@]+"${BUILD_ARGS[@]}"} -t openclaw:local -f "$REPO_PATH/Dockerfile" "$REPO_PATH"
|
||||||
|
|
||||||
echo "Loading image into $OPENCLAW_USER's Podman store..."
|
echo "Loading image into $OPENCLAW_USER's Podman store..."
|
||||||
TMP_IMAGE="$(mktemp -p /tmp openclaw-image.XXXXXX.tar)"
|
TMP_IMAGE="$(mktemp -p /tmp openclaw-image.XXXXXX.tar)"
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ describe("docker base image pinning", () => {
|
|||||||
.find((line) => line.trimStart().startsWith("FROM "));
|
.find((line) => line.trimStart().startsWith("FROM "));
|
||||||
expect(fromLine, `${dockerfilePath} should define a FROM line`).toBeDefined();
|
expect(fromLine, `${dockerfilePath} should define a FROM line`).toBeDefined();
|
||||||
expect(fromLine, `${dockerfilePath} FROM must be digest-pinned`).toMatch(
|
expect(fromLine, `${dockerfilePath} FROM must be digest-pinned`).toMatch(
|
||||||
/^FROM\s+\S+@sha256:[a-f0-9]{64}$/,
|
/^FROM\s+\S+@sha256:[a-f0-9]{64}(?:\s+AS\s+\S+)?$/,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user