|
| 1 | +FROM debian:bookworm-slim |
| 2 | + |
| 3 | +# Install Node.js 22 (required by OpenClaw) |
| 4 | +ENV NODE_VERSION=22.22.1 |
| 5 | +RUN apt-get update \ |
| 6 | + && apt-get install -y --no-install-recommends \ |
| 7 | + ca-certificates curl gnupg git xz-utils unzip jq ripgrep rsync zstd \ |
| 8 | + build-essential python3 ffmpeg tmux chromium \ |
| 9 | + && ARCH="$(dpkg --print-architecture)" \ |
| 10 | + && case "${ARCH}" in \ |
| 11 | + amd64) NODE_ARCH="x64" ;; \ |
| 12 | + arm64) NODE_ARCH="arm64" ;; \ |
| 13 | + *) echo "Unsupported architecture: ${ARCH}" >&2; exit 1 ;; \ |
| 14 | + esac \ |
| 15 | + && curl -fsSL https://nodejs.org/dist/v${NODE_VERSION}/node-v${NODE_VERSION}-linux-${NODE_ARCH}.tar.xz -o /tmp/node.tar.xz \ |
| 16 | + && tar -xJf /tmp/node.tar.xz -C /usr/local --strip-components=1 \ |
| 17 | + && rm /tmp/node.tar.xz \ |
| 18 | + && curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg \ |
| 19 | + -o /usr/share/keyrings/githubcli-archive-keyring.gpg \ |
| 20 | + && echo "deb [arch=${ARCH} signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" \ |
| 21 | + > /etc/apt/sources.list.d/github-cli.list \ |
| 22 | + && curl -fsSL https://downloads.1password.com/linux/keys/1password.asc \ |
| 23 | + | gpg --dearmor --output /usr/share/keyrings/1password-archive-keyring.gpg \ |
| 24 | + && echo "deb [arch=${ARCH} signed-by=/usr/share/keyrings/1password-archive-keyring.gpg] https://downloads.1password.com/linux/debian/${ARCH} stable main" \ |
| 25 | + > /etc/apt/sources.list.d/1password.list \ |
| 26 | + && mkdir -p /etc/debsig/policies/AC2D62742012EA22/ \ |
| 27 | + && curl -fsSL https://downloads.1password.com/linux/debian/debsig/1password.pol \ |
| 28 | + | tee /etc/debsig/policies/AC2D62742012EA22/1password.pol > /dev/null \ |
| 29 | + && mkdir -p /usr/share/debsig/keyrings/AC2D62742012EA22 \ |
| 30 | + && curl -fsSL https://downloads.1password.com/linux/keys/1password.asc \ |
| 31 | + | gpg --dearmor --output /usr/share/debsig/keyrings/AC2D62742012EA22/debsig.gpg \ |
| 32 | + && apt-get update \ |
| 33 | + && apt-get install -y --no-install-recommends gh 1password-cli=2.32.1-1 \ |
| 34 | + && apt-get purge -y xz-utils \ |
| 35 | + && apt-get autoremove -y \ |
| 36 | + && rm -rf /var/lib/apt/lists/* \ |
| 37 | + && node --version \ |
| 38 | + && npm --version |
| 39 | + |
| 40 | +# Install pnpm globally |
| 41 | +RUN npm install -g pnpm |
| 42 | + |
| 43 | +# Install OpenClaw from local tarball (see openclaw-build/README) |
| 44 | +COPY openclaw-build/openclaw-*.tgz /tmp/openclaw.tgz |
| 45 | +RUN npm install -g /tmp/openclaw.tgz \ |
| 46 | + && rm /tmp/openclaw.tgz \ |
| 47 | + && openclaw --version |
| 48 | + |
| 49 | +# Install ClawHub CLI |
| 50 | +RUN npm install -g clawhub |
| 51 | + |
| 52 | +# Install QMD |
| 53 | +RUN npm install -g @tobilu/qmd |
| 54 | + |
| 55 | +# Install mcporter (MCP server tooling) |
| 56 | +RUN npm install -g mcporter@0.7.3 |
| 57 | + |
| 58 | +# Install summarize (web page summarization CLI) |
| 59 | +RUN npm install -g @steipete/summarize@0.11.1 |
| 60 | + |
| 61 | +# Install Go (available at runtime for users to `go install` additional tools) |
| 62 | +ENV GO_VERSION=1.26.0 |
| 63 | +RUN ARCH="$(dpkg --print-architecture)" \ |
| 64 | + && curl -fsSL https://dl.google.com/go/go${GO_VERSION}.linux-${ARCH}.tar.gz -o /tmp/go.tar.gz \ |
| 65 | + && EXPECTED_SHA256="$(curl -fsSL https://dl.google.com/go/go${GO_VERSION}.linux-${ARCH}.tar.gz.sha256)" \ |
| 66 | + && echo "${EXPECTED_SHA256} /tmp/go.tar.gz" | sha256sum -c - \ |
| 67 | + && tar -xzf /tmp/go.tar.gz -C /usr/local \ |
| 68 | + && rm /tmp/go.tar.gz |
| 69 | +# IMPORTANT: A Fly Volume is mounted at /root at runtime, which shadows |
| 70 | +# everything the image layer writes under /root. |
| 71 | +# - Pre-installed tools use GOBIN=/usr/local/bin so they survive the mount. |
| 72 | +# - At runtime, Go defaults to GOBIN=/root/go/bin (on the persistent volume), |
| 73 | +# so user-installed tools persist across restarts and are included in snapshots. |
| 74 | +# - npm/Node (installed to /usr/local) and apt packages (/usr/bin) are unaffected. |
| 75 | +ENV PATH="/usr/local/go/bin:/root/go/bin:$PATH" |
| 76 | +RUN GOBIN=/usr/local/bin go install github.com/steipete/gogcli/cmd/gog@v0.11.0 \ |
| 77 | + && GOBIN=/usr/local/bin go install github.com/steipete/goplaces/cmd/goplaces@v0.3.0 \ |
| 78 | + && GOBIN=/usr/local/bin go install github.com/Hyaxia/blogwatcher/cmd/blogwatcher@v0.0.2 \ |
| 79 | + && GOBIN=/usr/local/bin go install github.com/xdevplatform/xurl@v1.0.3 \ |
| 80 | + && GOBIN=/usr/local/bin go install github.com/steipete/gifgrep/cmd/gifgrep@v0.2.3 \ |
| 81 | + && go clean -cache -modcache |
| 82 | + |
| 83 | +# Install uv (Python package manager, available at runtime). |
| 84 | +# Install to /usr/local/bin so the binary survives the Fly Volume mount at /root. |
| 85 | +RUN curl -LsSf https://astral.sh/uv/install.sh | env UV_INSTALL_DIR=/usr/local/bin sh \ |
| 86 | + && uv --version |
| 87 | + |
| 88 | +# Install pinned Bun (build-time only) and compile the controller bundle. |
| 89 | +ARG BUN_VERSION=1.2.4 |
| 90 | +RUN curl -fsSL https://bun.sh/install | bash -s "bun-v${BUN_VERSION}" |
| 91 | +ENV PATH="/root/.bun/bin:$PATH" |
| 92 | + |
| 93 | +ARG CONTROLLER_COMMIT=unknown |
| 94 | +# Changing this ARG value invalidates the COPY + RUN layers below it, |
| 95 | +# forcing a controller rebuild even when Docker thinks the source is unchanged. |
| 96 | +ARG CONTROLLER_CACHE_BUST=1 |
| 97 | +COPY controller/ /tmp/controller-build/ |
| 98 | +RUN cd /tmp/controller-build \ |
| 99 | + && bun install --frozen-lockfile \ |
| 100 | + && CONTROLLER_VERSION="$(date -u +'%Y.%-m.%-d')" \ |
| 101 | + && bun build src/index.ts --outfile=/usr/local/bin/kiloclaw-controller.js --target=node --minify \ |
| 102 | + --define "KILOCLAW_CONTROLLER_VERSION=\"${CONTROLLER_VERSION}\"" \ |
| 103 | + --define "KILOCLAW_CONTROLLER_COMMIT=\"${CONTROLLER_COMMIT}\"" \ |
| 104 | + && rm -rf /tmp/controller-build |
| 105 | + |
| 106 | +# Create OpenClaw directories |
| 107 | +# When a Fly Volume is mounted at /root, these dirs persist across restarts. |
| 108 | +RUN mkdir -p /root/.openclaw \ |
| 109 | + && mkdir -p /root/clawd \ |
| 110 | + && mkdir -p /root/clawd/skills |
| 111 | + |
| 112 | +# Copy startup script |
| 113 | +# Build cache bust: 2026-03-11-v60-tools-md |
| 114 | +RUN echo "9" |
| 115 | +COPY start-openclaw.sh /usr/local/bin/start-openclaw.sh |
| 116 | +COPY openclaw-pairing-list.js /usr/local/bin/openclaw-pairing-list.js |
| 117 | +COPY openclaw-device-pairing-list.js /usr/local/bin/openclaw-device-pairing-list.js |
| 118 | +RUN chmod +x /usr/local/bin/start-openclaw.sh |
| 119 | +RUN ls -l /usr/local/bin/start-openclaw.sh \ |
| 120 | + && head -n 1 /usr/local/bin/start-openclaw.sh | cat -v \ |
| 121 | + && od -An -t x1 -N 4 /usr/local/bin/start-openclaw.sh \ |
| 122 | + && ls -l /bin/bash |
| 123 | + |
| 124 | +# Copy custom skills |
| 125 | +COPY skills/ /root/clawd/skills/ |
| 126 | + |
| 127 | +# Stage TOOLS.md outside /root so it survives the Fly Volume mount. |
| 128 | +# start-openclaw.sh copies it to /root/.openclaw/workspace/TOOLS.md on first boot. |
| 129 | +COPY container/TOOLS.md /usr/local/share/kiloclaw/TOOLS.md |
| 130 | + |
| 131 | +# Expose the gateway port |
| 132 | +EXPOSE 18789 |
| 133 | + |
| 134 | +# Entrypoint: start the KiloClaw controller daemon |
| 135 | +CMD ["/usr/local/bin/start-openclaw.sh"] |
0 commit comments