Reduce the self-host runtime image by splitting production deps into a parallel stage, using a distroless cc runtime with a copied Bun binary, moving web-only host dependencies to devDependencies, and copying/pruning only runtime-needed files. Verification: DOCKER_BUILDKIT=1 docker build --no-cache --progress=plain -f apps/host-selfhost/Dockerfile -t executor-selfhost-pr:latest . docker run smoke: /api/health returned OK
Unified · Split
| @@ -12,25 +12,122 @@ · 11 unmodified lines | ||
| 12 | 12 | # SQLite (libSQL, file:/data/...) lives in /data, QuickJS + MCP run in-process — |
| 13 | 13 | # so there's no postgres/worker/proxy to orchestrate. |
| 14 | 14 | |
| 15 | +# ── Production deps: installs only what the runtime image needs ───────────── | |
| 16 | +FROM oven/bun:1 AS prod-deps | |
| 17 | +WORKDIR /app | |
| 18 | +COPY . . | |
| 19 | +# This stage is independent from the SPA build stage, so BuildKit can run the | |
| 20 | +# slow production install in parallel with the dev install and Vite build. | |
| 21 | +# --ignore-scripts: the root `prepare` hook runs a dev-only tool | |
| 22 | +# (effect-language-service); runtime deps are prebuilt JS with no postinstall. | |
| 23 | +RUN bun install --frozen-lockfile --production --ignore-scripts --filter @executor-js/host-selfhost \ | |
| 24 | + && find node_modules/.bun -maxdepth 1 -name '*linux-x64-musl*' -exec rm -rf {} + \ | |
| 25 | + && rm -rf \ | |
| 26 | + node_modules/.bun/@ant-design+* \ | |
| 27 | + node_modules/.bun/@base-ui+* \ | |
| 28 | + node_modules/.bun/@cloudflare+workers-types@* \ | |
| 29 | + node_modules/.bun/@electric-sql+pglite@* \ | |
| 30 | + node_modules/.bun/@emotion+react@* \ | |
| 31 | + node_modules/.bun/@emoji-mart+* \ | |
| 32 | + node_modules/.bun/@esbuild+* \ | |
| 33 | + node_modules/.bun/@floating-ui+react@* \ | |
| 34 | + node_modules/.bun/@dnd-kit+* \ | |
| 35 | + node_modules/.bun/@hookform+* \ | |
| 36 | + node_modules/.bun/@lobehub+* \ | |
| 37 | + node_modules/.bun/@mermaid-js+* \ | |
| 38 | + node_modules/.bun/@pierre+diffs@* \ | |
| 39 | + node_modules/.bun/@pierre+theme@* \ | |
| 40 | + node_modules/.bun/@primer+octicons@* \ | |
| 41 | + node_modules/.bun/@rc-component+* \ | |
| 42 | + node_modules/.bun/@rolldown+binding-* \ | |
| 43 | + node_modules/.bun/@reduxjs+* \ | |
| 44 | + node_modules/.bun/@shikijs+* \ | |
| 45 | + node_modules/.bun/@splinetool+* \ | |
| 46 | + node_modules/.bun/@tanstack+start-plugin-core@* \ | |
| 47 | + node_modules/.bun/@tanstack+react-router@* \ | |
| 48 | + node_modules/.bun/@tanstack+router-core@* \ | |
| 49 | + node_modules/.bun/@types+* \ | |
| 50 | + node_modules/.bun/ahooks@* \ | |
| 51 | + node_modules/.bun/antd@* \ | |
| 52 | + node_modules/.bun/better-sqlite3@* \ | |
| 53 | + node_modules/.bun/bun-types@* \ | |
| 54 | + node_modules/.bun/caniuse-lite@* \ | |
| 55 | + node_modules/.bun/chroma-js@* \ | |
| 56 | + node_modules/.bun/cytoscape*@* \ | |
| 57 | + node_modules/.bun/d3@* \ | |
| 58 | + node_modules/.bun/date-fns*@* \ | |
| 59 | + node_modules/.bun/drizzle-kit@* \ | |
| 60 | + node_modules/.bun/dompurify@* \ | |
| 61 | + node_modules/.bun/emoji-mart@* \ | |
| 62 | + node_modules/.bun/framer-motion@* \ | |
| 63 | + node_modules/.bun/jiti@* \ | |
| 64 | + node_modules/.bun/katex@* \ | |
| 65 | + node_modules/.bun/layout-base@* \ | |
| 66 | + node_modules/.bun/leva@* \ | |
| 67 | + node_modules/.bun/lightningcss-* \ | |
| 68 | + node_modules/.bun/lit-html@* \ | |
| 69 | + node_modules/.bun/lucide-react@* \ | |
| 70 | + node_modules/.bun/mermaid@* \ | |
| 71 | + node_modules/.bun/motion@* \ | |
| 72 | + node_modules/.bun/motion-* \ | |
| 73 | + node_modules/.bun/polished@* \ | |
| 74 | + node_modules/.bun/prettier@* \ | |
| 75 | + node_modules/.bun/react-day-picker@* \ | |
| 76 | + node_modules/.bun/react-hook-form@* \ | |
| 77 | + node_modules/.bun/react@* \ | |
| 78 | + node_modules/.bun/react-dom@* \ | |
| 79 | + node_modules/.bun/react-dropzone@* \ | |
| 80 | + node_modules/.bun/recharts@* \ | |
| 81 | + node_modules/.bun/rolldown@* \ | |
| 82 | + node_modules/.bun/shiki@* \ | |
| 83 | + node_modules/.bun/solid-js@* \ | |
| 84 | + node_modules/.bun/source-map@* \ | |
| 85 | + node_modules/.bun/tailwind-merge@* \ | |
| 86 | + node_modules/.bun/tsx@* \ | |
| 87 | + node_modules/.bun/@upsetjs+venn.js@* \ | |
| 88 | + node_modules/.bun/victory-vendor@* \ | |
| 89 | + node_modules/.bun/virtua@* \ | |
| 90 | + node_modules/.bun/typeorm@* \ | |
| 91 | + node_modules/.bun/vite@* \ | |
| 92 | + node_modules/.bun/vitest@* \ | |
| 93 | + && find node_modules -type f \( -name '*.map' -o -name '*.d.ts' -o -name '*.d.mts' -o -name '*.d.cts' -o -name '*.ts' -o -name '*.tsx' -o -name '*.md' -o -name '*.markdown' \) -delete \ | |
| 94 | + && find node_modules -type d \( -iname test -o -iname tests -o -iname __tests__ -o -iname fixtures -o -iname fixture -o -iname examples \) -prune -exec rm -rf {} + \ | |
| 95 | + && find packages apps/host-selfhost -type f \( -name '*.map' -o -name '*.d.ts' -o -name '*.test.ts' -o -name '*.test.tsx' -o -name '*.md' -o -name '*.markdown' \) -delete \ | |
| 96 | + && rm -rf \ | |
| 97 | + packages/app \ | |
| 98 | + packages/react \ | |
| 99 | + packages/plugins/*/fixtures \ | |
| 100 | + packages/plugins/*/src/react \ | |
| 101 | + packages/plugins/*/src/**/__snapshots__ \ | |
| 102 | + packages/plugins/*/test \ | |
| 103 | + packages/plugins/*/testing \ | |
| 104 | + packages/*/*/CHANGELOG.md \ | |
| 105 | + packages/*/*/tsconfig.json \ | |
| 106 | + packages/*/*/tsup.config.ts \ | |
| 107 | + packages/*/*/vitest.config.ts \ | |
| 108 | + packages/plugins/*/vitest.config.ts \ | |
| 109 | + apps/host-selfhost/CHANGELOG.md \ | |
| 110 | + apps/host-selfhost/Dockerfile \ | |
| 111 | + apps/host-selfhost/docker-compose.yml \ | |
| 112 | + apps/host-selfhost/scripts \ | |
| 113 | + apps/host-selfhost/vitest.config.ts \ | |
| 114 | + apps/host-selfhost/web \ | |
| 115 | + apps/host-selfhost/tsconfig.json \ | |
| 116 | + apps/host-selfhost/tsr.routes.ts \ | |
| 117 | + apps/host-selfhost/vite.config.ts | |
| 118 | + | |
| 15 | 119 | # ── Build stage: install the workspace + build the SPA ────────────────────── |
| 16 | 120 | FROM oven/bun:1 AS build |
| 17 | 121 | WORKDIR /app |
| 18 | 122 | COPY . . |
| 19 | −# Full install (dev deps included — vite/turbo/plugins are needed to build). | |
| 123 | +# Full install (dev deps included, vite/turbo/plugins are needed to build). | |
| 20 | 124 | RUN bun install --frozen-lockfile |
| 21 | 125 | # Builds @executor-js/vite-plugin (via turbo) then the self-host SPA into |
| 22 | 126 | # apps/host-selfhost/dist. |
| 23 | 127 | RUN cd apps/host-selfhost && bun run build |
| 24 | −# Reinstall PRODUCTION deps only, so the runtime image excludes the build/dev | |
| 25 | −# toolchain pulled by the full workspace install — vite, turbo, wrangler → | |
| 26 | −# miniflare → sharp/libvips (~800MB), astro, vitest, etc. The built SPA lives in | |
| 27 | −# apps/host-selfhost/dist (outside node_modules), so it survives the reinstall. | |
| 28 | −# --ignore-scripts: the root `prepare` hook runs a dev-only tool | |
| 29 | −# (effect-language-service); runtime deps are prebuilt JS with no postinstall. | |
| 30 | −RUN rm -rf node_modules && bun install --frozen-lockfile --production --ignore-scripts | |
| 31 | 128 | |
| 32 | 129 | # ── Runtime stage: serve the built app under Bun ──────────────────────────── |
| 33 | −FROM oven/bun:1 AS runtime | |
| 130 | +FROM gcr.io/distroless/cc-debian12 AS runtime | |
| 34 | 131 | WORKDIR /app |
| 35 | 132 | LABEL org.opencontainers.image.source="https://github.com/RhysSullivan/executor" \ |
| 36 | 133 | org.opencontainers.image.description="Single-container self-hosted Executor" \ |
| @@ -39,9 +136,25 @@ ENV NODE_ENV=production \ · 2 unmodified lines | ||
| 39 | 136 | EXECUTOR_HOST=0.0.0.0 \ |
| 40 | 137 | PORT=4788 \ |
| 41 | 138 | EXECUTOR_DATA_DIR=/data |
| 42 | −COPY --from=build /app /app | |
| 139 | +COPY --from=prod-deps /usr/local/bin/bun /usr/local/bin/bun | |
| 140 | +COPY --from=prod-deps /app/node_modules /app/node_modules | |
| 141 | +COPY --from=prod-deps /app/packages/core/api /app/packages/core/api | |
| 142 | +COPY --from=prod-deps /app/packages/core/config /app/packages/core/config | |
| 143 | +COPY --from=prod-deps /app/packages/core/execution /app/packages/core/execution | |
| 144 | +COPY --from=prod-deps /app/packages/core/fumadb /app/packages/core/fumadb | |
| 145 | +COPY --from=prod-deps /app/packages/core/sdk /app/packages/core/sdk | |
| 146 | +COPY --from=prod-deps /app/packages/hosts/mcp /app/packages/hosts/mcp | |
| 147 | +COPY --from=prod-deps /app/packages/kernel/core /app/packages/kernel/core | |
| 148 | +COPY --from=prod-deps /app/packages/kernel/runtime-quickjs /app/packages/kernel/runtime-quickjs | |
| 149 | +COPY --from=prod-deps /app/packages/plugins/encrypted-secrets /app/packages/plugins/encrypted-secrets | |
| 150 | +COPY --from=prod-deps /app/packages/plugins/google /app/packages/plugins/google | |
| 151 | +COPY --from=prod-deps /app/packages/plugins/graphql /app/packages/plugins/graphql | |
| 152 | +COPY --from=prod-deps /app/packages/plugins/mcp /app/packages/plugins/mcp | |
| 153 | +COPY --from=prod-deps /app/packages/plugins/microsoft /app/packages/plugins/microsoft | |
| 154 | +COPY --from=prod-deps /app/packages/plugins/openapi /app/packages/plugins/openapi | |
| 155 | +COPY --from=prod-deps /app/apps/host-selfhost /app/apps/host-selfhost | |
| 156 | +COPY --from=build /app/apps/host-selfhost/dist /app/apps/host-selfhost/dist | |
| 43 | 157 | WORKDIR /app/apps/host-selfhost |
| 44 | −RUN mkdir -p /data | |
| 45 | 158 | VOLUME ["/data"] |
| 46 | 159 | EXPOSE 4788 |
| 47 | 160 | # Readiness probe against the public /api/health endpoint (a trivial DB ping). |
| @@ -19,10 +19,8 @@ · 18 unmodified lines | ||
| 19 | 19 | }, |
| 20 | 20 | "dependencies": { |
| 21 | 21 | "@better-auth/api-key": "^1.6.11", |
| 22 | − "@effect/atom-react": "catalog:", | |
| 23 | 22 | "@effect/platform-bun": "catalog:", |
| 24 | 23 | "@executor-js/api": "workspace:*", |
| 25 | − "@executor-js/app": "workspace:*", | |
| 26 | 24 | "@executor-js/execution": "workspace:*", |
| 27 | 25 | "@executor-js/fumadb": "workspace:*", |
| 28 | 26 | "@executor-js/host-mcp": "workspace:*", |
| @@ -32,18 +30,14 @@ · 3 unmodified lines | ||
| 32 | 30 | "@executor-js/plugin-mcp": "workspace:*", |
| 33 | 31 | "@executor-js/plugin-microsoft": "workspace:*", |
| 34 | 32 | "@executor-js/plugin-openapi": "workspace:*", |
| 35 | − "@executor-js/react": "workspace:*", | |
| 36 | 33 | "@executor-js/runtime-quickjs": "workspace:*", |
| 37 | 34 | "@executor-js/sdk": "workspace:*", |
| 38 | 35 | "@libsql/client": "catalog:", |
| 39 | 36 | "@libsql/kysely-libsql": "catalog:", |
| 40 | 37 | "@modelcontextprotocol/sdk": "^1.29.0", |
| 41 | − "@tanstack/react-router": "catalog:", | |
| 42 | 38 | "better-auth": "^1.6.11", |
| 43 | 39 | "drizzle-orm": "catalog:", |
| 44 | − "effect": "catalog:", | |
| 45 | − "react": "catalog:", | |
| 46 | − "react-dom": "catalog:" | |
| 40 | + "effect": "catalog:" | |
| 47 | 41 | }, |
| 48 | 42 | "devDependencies": { |
| 49 | 43 | "@effect/vitest": "catalog:", |
| @@ -58,6 +52,12 @@ · 8 unmodified lines | ||
| 58 | 52 | "bun-types": "catalog:", |
| 59 | 53 | "typescript": "catalog:", |
| 60 | 54 | "vite": "catalog:", |
| 61 | − "vitest": "catalog:" | |
| 55 | + "vitest": "catalog:", | |
| 56 | + "@effect/atom-react": "catalog:", | |
| 57 | + "@executor-js/app": "workspace:*", | |
| 58 | + "@executor-js/react": "workspace:*", | |
| 59 | + "@tanstack/react-router": "catalog:", | |
| 60 | + "react": "catalog:", | |
| 61 | + "react-dom": "catalog:" | |
| 62 | 62 | } |
| 63 | 63 | } |