mirror of
https://github.com/moltbot/moltbot.git
synced 2026-03-07 22:44:16 +00:00
reduce image size, offer slim image (#38479)
Signed-off-by: sallyom <somalley@redhat.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
83
.github/workflows/docker-release.yml
vendored
83
.github/workflows/docker-release.yml
vendored
@@ -22,14 +22,15 @@ env:
|
||||
IMAGE_NAME: ${{ github.repository }}
|
||||
|
||||
jobs:
|
||||
# Build amd64 image
|
||||
# Build amd64 images (default + slim share the build stage cache)
|
||||
build-amd64:
|
||||
runs-on: blacksmith-16vcpu-ubuntu-2404
|
||||
permissions:
|
||||
packages: write
|
||||
contents: read
|
||||
outputs:
|
||||
image-digest: ${{ steps.build.outputs.digest }}
|
||||
digest: ${{ steps.build.outputs.digest }}
|
||||
slim-digest: ${{ steps.build-slim.outputs.digest }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
@@ -52,12 +53,15 @@ jobs:
|
||||
run: |
|
||||
set -euo pipefail
|
||||
tags=()
|
||||
slim_tags=()
|
||||
if [[ "${GITHUB_REF}" == "refs/heads/main" ]]; then
|
||||
tags+=("${IMAGE}:main-amd64")
|
||||
slim_tags+=("${IMAGE}:main-slim-amd64")
|
||||
fi
|
||||
if [[ "${GITHUB_REF}" == refs/tags/v* ]]; then
|
||||
version="${GITHUB_REF#refs/tags/v}"
|
||||
tags+=("${IMAGE}:${version}-amd64")
|
||||
slim_tags+=("${IMAGE}:${version}-slim-amd64")
|
||||
fi
|
||||
if [[ ${#tags[@]} -eq 0 ]]; then
|
||||
echo "::error::No amd64 tags resolved for ref ${GITHUB_REF}"
|
||||
@@ -68,6 +72,11 @@ jobs:
|
||||
printf "%s\n" "${tags[@]}"
|
||||
echo "EOF"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
{
|
||||
echo "slim<<EOF"
|
||||
printf "%s\n" "${slim_tags[@]}"
|
||||
echo "EOF"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Resolve OCI labels (amd64)
|
||||
id: labels
|
||||
@@ -101,14 +110,28 @@ jobs:
|
||||
provenance: false
|
||||
push: true
|
||||
|
||||
# Build arm64 image
|
||||
- name: Build and push amd64 slim image
|
||||
id: build-slim
|
||||
uses: useblacksmith/build-push-action@v2
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64
|
||||
build-args: |
|
||||
OPENCLAW_VARIANT=slim
|
||||
tags: ${{ steps.tags.outputs.slim }}
|
||||
labels: ${{ steps.labels.outputs.value }}
|
||||
provenance: false
|
||||
push: true
|
||||
|
||||
# Build arm64 images (default + slim share the build stage cache)
|
||||
build-arm64:
|
||||
runs-on: blacksmith-16vcpu-ubuntu-2404-arm
|
||||
permissions:
|
||||
packages: write
|
||||
contents: read
|
||||
outputs:
|
||||
image-digest: ${{ steps.build.outputs.digest }}
|
||||
digest: ${{ steps.build.outputs.digest }}
|
||||
slim-digest: ${{ steps.build-slim.outputs.digest }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
@@ -131,12 +154,15 @@ jobs:
|
||||
run: |
|
||||
set -euo pipefail
|
||||
tags=()
|
||||
slim_tags=()
|
||||
if [[ "${GITHUB_REF}" == "refs/heads/main" ]]; then
|
||||
tags+=("${IMAGE}:main-arm64")
|
||||
slim_tags+=("${IMAGE}:main-slim-arm64")
|
||||
fi
|
||||
if [[ "${GITHUB_REF}" == refs/tags/v* ]]; then
|
||||
version="${GITHUB_REF#refs/tags/v}"
|
||||
tags+=("${IMAGE}:${version}-arm64")
|
||||
slim_tags+=("${IMAGE}:${version}-slim-arm64")
|
||||
fi
|
||||
if [[ ${#tags[@]} -eq 0 ]]; then
|
||||
echo "::error::No arm64 tags resolved for ref ${GITHUB_REF}"
|
||||
@@ -147,6 +173,11 @@ jobs:
|
||||
printf "%s\n" "${tags[@]}"
|
||||
echo "EOF"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
{
|
||||
echo "slim<<EOF"
|
||||
printf "%s\n" "${slim_tags[@]}"
|
||||
echo "EOF"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Resolve OCI labels (arm64)
|
||||
id: labels
|
||||
@@ -180,7 +211,20 @@ jobs:
|
||||
provenance: false
|
||||
push: true
|
||||
|
||||
# Create multi-platform manifest
|
||||
- name: Build and push arm64 slim image
|
||||
id: build-slim
|
||||
uses: useblacksmith/build-push-action@v2
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/arm64
|
||||
build-args: |
|
||||
OPENCLAW_VARIANT=slim
|
||||
tags: ${{ steps.tags.outputs.slim }}
|
||||
labels: ${{ steps.labels.outputs.value }}
|
||||
provenance: false
|
||||
push: true
|
||||
|
||||
# Create multi-platform manifests
|
||||
create-manifest:
|
||||
runs-on: blacksmith-16vcpu-ubuntu-2404
|
||||
permissions:
|
||||
@@ -206,14 +250,18 @@ jobs:
|
||||
run: |
|
||||
set -euo pipefail
|
||||
tags=()
|
||||
slim_tags=()
|
||||
if [[ "${GITHUB_REF}" == "refs/heads/main" ]]; then
|
||||
tags+=("${IMAGE}:main")
|
||||
slim_tags+=("${IMAGE}:main-slim")
|
||||
fi
|
||||
if [[ "${GITHUB_REF}" == refs/tags/v* ]]; then
|
||||
version="${GITHUB_REF#refs/tags/v}"
|
||||
tags+=("${IMAGE}:${version}")
|
||||
slim_tags+=("${IMAGE}:${version}-slim")
|
||||
if [[ "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[0-9]+)?$ ]]; then
|
||||
tags+=("${IMAGE}:latest")
|
||||
slim_tags+=("${IMAGE}:slim")
|
||||
fi
|
||||
fi
|
||||
if [[ ${#tags[@]} -eq 0 ]]; then
|
||||
@@ -225,8 +273,13 @@ jobs:
|
||||
printf "%s\n" "${tags[@]}"
|
||||
echo "EOF"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
{
|
||||
echo "slim<<EOF"
|
||||
printf "%s\n" "${slim_tags[@]}"
|
||||
echo "EOF"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Create and push manifest
|
||||
- name: Create and push default manifest
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
@@ -237,5 +290,19 @@ jobs:
|
||||
args+=("-t" "$tag")
|
||||
done
|
||||
docker buildx imagetools create "${args[@]}" \
|
||||
${{ needs.build-amd64.outputs.image-digest }} \
|
||||
${{ needs.build-arm64.outputs.image-digest }}
|
||||
${{ needs.build-amd64.outputs.digest }} \
|
||||
${{ needs.build-arm64.outputs.digest }}
|
||||
|
||||
- name: Create and push slim manifest
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
mapfile -t tags <<< "${{ steps.tags.outputs.slim }}"
|
||||
args=()
|
||||
for tag in "${tags[@]}"; do
|
||||
[ -z "$tag" ] && continue
|
||||
args+=("-t" "$tag")
|
||||
done
|
||||
docker buildx imagetools create "${args[@]}" \
|
||||
${{ needs.build-amd64.outputs.slim-digest }} \
|
||||
${{ needs.build-arm64.outputs.slim-digest }}
|
||||
|
||||
@@ -31,6 +31,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Config/Compaction safeguard tuning: expose `agents.defaults.compaction.recentTurnsPreserve` and quality-guard retry knobs through the validated config surface and embedded-runner wiring, with regression coverage for real config loading and schema metadata. (#25557) thanks @rodrigouroz.
|
||||
- iOS/App Store Connect release prep: align iOS bundle identifiers under `ai.openclaw.client`, refresh Watch app icons, add Fastlane metadata/screenshot automation, and support Keychain-backed ASC auth for uploads. (#38936) Thanks @ngutman.
|
||||
- Mattermost/model picker: add Telegram-style interactive provider/model browsing for `/oc_model` and `/oc_models`, fix picker callback updates, and emit a normal confirmation reply when a model is selected. (#38767) thanks @mukhtharcm.
|
||||
- Docker/multi-stage build: restructure Dockerfile as a multi-stage build to produce a minimal runtime image without build tools, source code, or Bun; add `OPENCLAW_VARIANT=slim` build arg for a bookworm-slim variant. (#38479) Thanks @sallyom.
|
||||
|
||||
### Breaking
|
||||
|
||||
|
||||
135
Dockerfile
135
Dockerfile
@@ -1,14 +1,27 @@
|
||||
# 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.
|
||||
# Multi-stage build produces a minimal runtime image without build tools,
|
||||
# source code, or Bun. Works with Docker, Buildx, and Podman.
|
||||
# The ext-deps stage extracts only the package.json files we need from
|
||||
# extensions/, so the main build layer is not invalidated by unrelated
|
||||
# extension source changes.
|
||||
#
|
||||
# Two runtime variants:
|
||||
# Default (bookworm): docker build .
|
||||
# Slim (bookworm-slim): docker build --build-arg OPENCLAW_VARIANT=slim .
|
||||
ARG OPENCLAW_EXTENSIONS=""
|
||||
FROM node:22-bookworm@sha256:cd7bcd2e7a1e6f72052feb023c7f6b722205d3fcab7bbcbd2d1bfdab10b1e935 AS ext-deps
|
||||
ARG OPENCLAW_VARIANT=default
|
||||
|
||||
# Base images are pinned to SHA256 digests for reproducible builds.
|
||||
# Trade-off: digests must be updated manually when upstream tags move.
|
||||
# To update, run: docker manifest inspect node:22-bookworm (or podman)
|
||||
# and replace the digest below with the current amd64 entry.
|
||||
|
||||
FROM node:22-bookworm@sha256:6d735b4d33660225271fda0a412802746658c3a1b975507b2803ed299609760a AS ext-deps
|
||||
ARG OPENCLAW_EXTENSIONS
|
||||
COPY extensions /tmp/extensions
|
||||
# Copy package.json for opted-in extensions so pnpm resolves their deps.
|
||||
RUN mkdir -p /out && \
|
||||
for ext in $OPENCLAW_EXTENSIONS; do \
|
||||
if [ -f "/tmp/extensions/$ext/package.json" ]; then \
|
||||
@@ -17,20 +30,8 @@ RUN mkdir -p /out && \
|
||||
fi; \
|
||||
done
|
||||
|
||||
FROM node:22-bookworm@sha256:cd7bcd2e7a1e6f72052feb023c7f6b722205d3fcab7bbcbd2d1bfdab10b1e935
|
||||
|
||||
# OCI base-image metadata for downstream image consumers.
|
||||
# If you change these annotations, also update:
|
||||
# - docs/install/docker.md ("Base image metadata" section)
|
||||
# - https://docs.openclaw.ai/install/docker
|
||||
LABEL org.opencontainers.image.base.name="docker.io/library/node:22-bookworm" \
|
||||
org.opencontainers.image.base.digest="sha256:cd7bcd2e7a1e6f72052feb023c7f6b722205d3fcab7bbcbd2d1bfdab10b1e935" \
|
||||
org.opencontainers.image.source="https://github.com/openclaw/openclaw" \
|
||||
org.opencontainers.image.url="https://openclaw.ai" \
|
||||
org.opencontainers.image.documentation="https://docs.openclaw.ai/install/docker" \
|
||||
org.opencontainers.image.licenses="MIT" \
|
||||
org.opencontainers.image.title="OpenClaw" \
|
||||
org.opencontainers.image.description="OpenClaw gateway and CLI runtime container image"
|
||||
# ── Stage 2: Build ──────────────────────────────────────────────
|
||||
FROM node:22-bookworm@sha256:6d735b4d33660225271fda0a412802746658c3a1b975507b2803ed299609760a AS build
|
||||
|
||||
# Install Bun (required for build scripts)
|
||||
RUN curl -fsSL https://bun.sh/install | bash
|
||||
@@ -39,8 +40,80 @@ ENV PATH="/root/.bun/bin:${PATH}"
|
||||
RUN corepack enable
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml .npmrc ./
|
||||
COPY ui/package.json ./ui/package.json
|
||||
COPY patches ./patches
|
||||
COPY scripts ./scripts
|
||||
|
||||
COPY --from=ext-deps /out/ ./extensions/
|
||||
|
||||
# Reduce OOM risk on low-memory hosts during dependency installation.
|
||||
# Docker builds on small VMs may otherwise fail with "Killed" (exit 137).
|
||||
RUN NODE_OPTIONS=--max-old-space-size=2048 pnpm install --frozen-lockfile
|
||||
|
||||
COPY . .
|
||||
|
||||
# A2UI bundle may fail under QEMU cross-compilation (e.g. building amd64
|
||||
# on Apple Silicon). CI builds natively per-arch so this is a no-op there.
|
||||
# Stub it so local cross-arch builds still succeed.
|
||||
RUN pnpm canvas:a2ui:bundle || \
|
||||
(echo "A2UI bundle: creating stub (non-fatal)" && \
|
||||
mkdir -p src/canvas-host/a2ui && \
|
||||
echo "/* A2UI bundle unavailable in this build */" > src/canvas-host/a2ui/a2ui.bundle.js && \
|
||||
echo "stub" > src/canvas-host/a2ui/.bundle.hash && \
|
||||
rm -rf vendor/a2ui apps/shared/OpenClawKit/Tools/CanvasA2UI)
|
||||
RUN pnpm build
|
||||
# Force pnpm for UI build (Bun may fail on ARM/Synology architectures)
|
||||
ENV OPENCLAW_PREFER_PNPM=1
|
||||
RUN pnpm ui:build
|
||||
|
||||
# ── Runtime base images ─────────────────────────────────────────
|
||||
FROM node:22-bookworm@sha256:6d735b4d33660225271fda0a412802746658c3a1b975507b2803ed299609760a AS base-default
|
||||
LABEL org.opencontainers.image.base.name="docker.io/library/node:22-bookworm" \
|
||||
org.opencontainers.image.base.digest="sha256:6d735b4d33660225271fda0a412802746658c3a1b975507b2803ed299609760a"
|
||||
|
||||
FROM node:22-bookworm-slim@sha256:b41c15b715b5d6e3f305e9c6480a2396dd5f130b63add98d3d45760376f20823 AS base-slim
|
||||
LABEL org.opencontainers.image.base.name="docker.io/library/node:22-bookworm-slim" \
|
||||
org.opencontainers.image.base.digest="sha256:b41c15b715b5d6e3f305e9c6480a2396dd5f130b63add98d3d45760376f20823"
|
||||
|
||||
# ── Stage 3: Runtime ────────────────────────────────────────────
|
||||
FROM base-${OPENCLAW_VARIANT}
|
||||
ARG OPENCLAW_VARIANT
|
||||
|
||||
# OCI base-image metadata for downstream image consumers.
|
||||
# If you change these annotations, also update:
|
||||
# - docs/install/docker.md ("Base image metadata" section)
|
||||
# - https://docs.openclaw.ai/install/docker
|
||||
LABEL org.opencontainers.image.source="https://github.com/openclaw/openclaw" \
|
||||
org.opencontainers.image.url="https://openclaw.ai" \
|
||||
org.opencontainers.image.documentation="https://docs.openclaw.ai/install/docker" \
|
||||
org.opencontainers.image.licenses="MIT" \
|
||||
org.opencontainers.image.title="OpenClaw" \
|
||||
org.opencontainers.image.description="OpenClaw gateway and CLI runtime container image"
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Install system utilities present in bookworm but missing in bookworm-slim.
|
||||
# On the full bookworm image these are already installed (apt-get is a no-op).
|
||||
RUN apt-get update && \
|
||||
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
|
||||
procps hostname curl git openssl && \
|
||||
apt-get clean && \
|
||||
rm -rf /var/lib/apt/lists/* /var/cache/apt/archives/*
|
||||
|
||||
RUN chown node:node /app
|
||||
|
||||
COPY --from=build --chown=node:node /app/dist ./dist
|
||||
COPY --from=build --chown=node:node /app/node_modules ./node_modules
|
||||
COPY --from=build --chown=node:node /app/package.json .
|
||||
COPY --from=build --chown=node:node /app/openclaw.mjs .
|
||||
COPY --from=build --chown=node:node /app/extensions ./extensions
|
||||
COPY --from=build --chown=node:node /app/skills ./skills
|
||||
COPY --from=build --chown=node:node /app/docs ./docs
|
||||
|
||||
# Install additional system packages needed by your skills or extensions.
|
||||
# Example: docker build --build-arg OPENCLAW_DOCKER_APT_PACKAGES="python3 wget" .
|
||||
ARG OPENCLAW_DOCKER_APT_PACKAGES=""
|
||||
RUN if [ -n "$OPENCLAW_DOCKER_APT_PACKAGES" ]; then \
|
||||
apt-get update && \
|
||||
@@ -49,23 +122,10 @@ RUN if [ -n "$OPENCLAW_DOCKER_APT_PACKAGES" ]; then \
|
||||
rm -rf /var/lib/apt/lists/* /var/cache/apt/archives/*; \
|
||||
fi
|
||||
|
||||
COPY --chown=node:node package.json pnpm-lock.yaml pnpm-workspace.yaml .npmrc ./
|
||||
COPY --chown=node:node ui/package.json ./ui/package.json
|
||||
COPY --chown=node:node patches ./patches
|
||||
COPY --chown=node:node scripts ./scripts
|
||||
|
||||
COPY --from=ext-deps --chown=node:node /out/ ./extensions/
|
||||
|
||||
USER node
|
||||
# Reduce OOM risk on low-memory hosts during dependency installation.
|
||||
# Docker builds on small VMs may otherwise fail with "Killed" (exit 137).
|
||||
RUN NODE_OPTIONS=--max-old-space-size=2048 pnpm install --frozen-lockfile
|
||||
|
||||
# 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.
|
||||
# Must run after pnpm install so playwright-core is available in node_modules.
|
||||
USER root
|
||||
# Must run after node_modules COPY so playwright-core is available.
|
||||
ARG OPENCLAW_INSTALL_BROWSER=""
|
||||
RUN if [ -n "$OPENCLAW_INSTALL_BROWSER" ]; then \
|
||||
apt-get update && \
|
||||
@@ -110,9 +170,7 @@ RUN if [ -n "$OPENCLAW_INSTALL_DOCKER_CLI" ]; then \
|
||||
rm -rf /var/lib/apt/lists/* /var/cache/apt/archives/*; \
|
||||
fi
|
||||
|
||||
USER node
|
||||
COPY --chown=node:node . .
|
||||
# Normalize copied plugin/agent paths so plugin safety checks do not reject
|
||||
# Normalize extension paths so plugin safety checks do not reject
|
||||
# world-writable directories inherited from source file modes.
|
||||
RUN for dir in /app/extensions /app/.agent /app/.agents; do \
|
||||
if [ -d "$dir" ]; then \
|
||||
@@ -120,13 +178,8 @@ RUN for dir in /app/extensions /app/.agent /app/.agents; do \
|
||||
find "$dir" -type f -exec chmod 644 {} +; \
|
||||
fi; \
|
||||
done
|
||||
RUN pnpm build
|
||||
# Force pnpm for UI build (Bun may fail on ARM/Synology architectures)
|
||||
ENV OPENCLAW_PREFER_PNPM=1
|
||||
RUN pnpm ui:build
|
||||
|
||||
# Expose the CLI binary without requiring npm global writes as non-root.
|
||||
USER root
|
||||
RUN ln -sf /app/openclaw.mjs /usr/local/bin/openclaw \
|
||||
&& chmod 755 /app/openclaw.mjs
|
||||
|
||||
|
||||
@@ -170,7 +170,7 @@ The main Docker image currently uses:
|
||||
The docker image now publishes OCI base-image annotations (sha256 is an example):
|
||||
|
||||
- `org.opencontainers.image.base.name=docker.io/library/node:22-bookworm`
|
||||
- `org.opencontainers.image.base.digest=sha256:cd7bcd2e7a1e6f72052feb023c7f6b722205d3fcab7bbcbd2d1bfdab10b1e935`
|
||||
- `org.opencontainers.image.base.digest=sha256:6d735b4d33660225271fda0a412802746658c3a1b975507b2803ed299609760a`
|
||||
- `org.opencontainers.image.source=https://github.com/openclaw/openclaw`
|
||||
- `org.opencontainers.image.url=https://openclaw.ai`
|
||||
- `org.opencontainers.image.documentation=https://docs.openclaw.ai/install/docker`
|
||||
|
||||
Reference in New Issue
Block a user