From 68232291363c1bedc0b645ed056898e8f2ef2076 Mon Sep 17 00:00:00 2001 From: John Ogle Date: Tue, 21 Apr 2026 17:13:07 -0700 Subject: [PATCH] refactor(openclaw): thin Docker image with Nix deps offloaded to Harmonia Strips runtime packages (nodejs_22, kubectl, jq, git, emacs, tsx, tea, pythonEnv, qmd) from the Docker image contents, reducing image size from ~2.7GB to ~1.5GB. Key changes: - Removed 9 runtime packages from contents (moved to openclaw-runtime-closure) - Removed pythonEnv let binding and qmd parameter (no longer needed in image) - Added OPENCLAW_RUNTIME_CLOSURE env var (bakes closure path for init container) - Added runtime closure bin dir to PATH (resolves after PVC population) - Added curl to contents (needed by init container for Harmonia health checks) - CI: added openclaw-runtime-closure to build-and-cache PACKAGES array - CI: added second sed command for CronJob image tag update - CI: removed inherit qmd from openclaw-image callPackage (qmd now in runtime closure) Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus --- .gitea/workflows/ci.yml | 5 ++++ packages/default.nix | 2 +- packages/openclaw-image/default.nix | 42 +++++++++++------------------ 3 files changed, 22 insertions(+), 27 deletions(-) diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index ea414bd..4ddb7bb 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -50,6 +50,7 @@ jobs: custom-rclone-torbox-setup custom-opencode custom-qmd + openclaw-runtime-closure custom-nextcloud-talk-desktop qt-pinned-jellyfin-media-player qt-pinned-stremio @@ -186,8 +187,12 @@ jobs: - name: Update HelmRelease image tag run: | cd k3s-cluster-config + # Update HelmRelease image tags (quoted format: tag: "version") sed -i 's|tag: ".*"|tag: "${{ needs.build-and-push-openclaw.outputs.image_tag }}"|' \ clusters/oglenet/apps/communication/openclaw.yaml + # Update CronJob image reference (registry:tag format) + sed -i 's|registry.johnogle.info/openclaw:.*|registry.johnogle.info/openclaw:${{ needs.build-and-push-openclaw.outputs.image_tag }}|' \ + clusters/oglenet/apps/communication/openclaw.yaml - name: Commit and push run: | diff --git a/packages/default.nix b/packages/default.nix index 0c76547..9ca060a 100644 --- a/packages/default.nix +++ b/packages/default.nix @@ -9,6 +9,6 @@ rec { nextcloud-talk-desktop = pkgs.callPackage ./nextcloud-talk-desktop { }; opencode = pkgs.callPackage ./opencode { }; qmd = pkgs.callPackage ./qmd { }; - openclaw-image = pkgs.callPackage ./openclaw-image { inherit qmd; }; + openclaw-image = pkgs.callPackage ./openclaw-image { }; openclaw-runtime-closure = pkgs.callPackage ./openclaw-image/runtime-closure.nix { inherit qmd; }; } diff --git a/packages/openclaw-image/default.nix b/packages/openclaw-image/default.nix index c6aaa49..77fb7d0 100644 --- a/packages/openclaw-image/default.nix +++ b/packages/openclaw-image/default.nix @@ -1,7 +1,6 @@ { pkgs, lib, - qmd, }: let @@ -82,9 +81,6 @@ let echo "Successfully extracted openclaw app" ''; - # Python environment with pymupdf - pythonEnv = pkgs.python3.withPackages (ps: [ ps.pymupdf ]); - # Custom NSS files that include the "node" user (UID 1000, GID 1000). # fakeNss only creates root/nobody, so we create our own with all three. openclawNss = pkgs.runCommand "openclaw-nss" { } '' @@ -164,30 +160,19 @@ pkgs.dockerTools.buildLayeredImage { entrypointPkg # Runtime package manager (agents can `nix run` arbitrary packages) + # Also needed by init container for `nix copy --from` to populate PVC from Harmonia pkgs.nix - # Tools baked into the image - pkgs.kubectl - pkgs.jq + # HTTP client (needed for init container Harmonia health checks and fallback) pkgs.curl - pkgs.git - pkgs.emacs - - # Node.js 22+ (for openclaw runtime, QMD, matrix-bot-sdk) - pkgs.nodejs_22 - - # TypeScript runtime (for running TypeScript files directly, e.g. via nix run) - pkgs.tsx - - # Python with pymupdf (PDF-to-image for Claude vision) - pythonEnv - - # Gitea CLI (PR workflow) - pkgs.tea - - # QMD — on-device hybrid search for markdown (built with Node.js 22, not Bun) - qmd ]; + # NOTE: Runtime packages (nodejs_22, kubectl, jq, git, emacs, tsx, tea, + # pythonEnv, qmd) are NOT in contents. They live in the + # `openclaw-runtime-closure` meta-package, which CI pushes to the + # Harmonia binary cache. The init container pulls them from Harmonia + # into the PVC at pod startup. This keeps the Docker image thin (~1.5GB + # vs the previous ~2.7GB) and makes CI pushes fast. + # # NOTE: openclawApp is NOT in contents. It would create /app as a symlink # to /nix/store/..., which breaks OpenClaw's symlink escape security check # (resolved paths "escape" /app/dist/extensions). Instead, extraCommands @@ -232,12 +217,17 @@ pkgs.dockerTools.buildLayeredImage { # Nix configuration "NIX_PATH=nixpkgs=flake:nixpkgs" - # PATH: standard dirs + Nix store bin dirs are appended by buildLayeredImage - "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" + # PATH: standard dirs + Nix store bin dirs appended by buildLayeredImage + # + runtime closure bin dir (populated from Harmonia by init container into PVC) + "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:${pkgs.custom.openclaw-runtime-closure}/bin" "NODE_ENV=production" # Home directory (Docker User directive doesn't set HOME from /etc/passwd) "HOME=/home/node" + # Runtime closure path — init container uses this to `nix copy --from` Harmonia + # This creates a build dependency (Nix resolves the path) but the closure + # is NOT in `contents`, so it won't be in the image layers. + "OPENCLAW_RUNTIME_CLOSURE=${pkgs.custom.openclaw-runtime-closure}" ]; }; }