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 | # SQLite (libSQL, file:/data/...) lives in /data, QuickJS + MCP run in-process — | 12 | # SQLite (libSQL, file:/data/...) lives in /data, QuickJS + MCP run in-process — |
| 13 | # so there's no postgres/worker/proxy to orchestrate. | 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 | # ── Build stage: install the workspace + build the SPA ────────────────────── | 119 | # ── Build stage: install the workspace + build the SPA ────────────────────── |
| 16 | FROM oven/bun:1 AS build | 120 | FROM oven/bun:1 AS build |
| 17 | WORKDIR /app | 121 | WORKDIR /app |
| 18 | COPY . . | 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 | RUN bun install --frozen-lockfile | 124 | RUN bun install --frozen-lockfile |
| 21 | # Builds @executor-js/vite-plugin (via turbo) then the self-host SPA into | 125 | # Builds @executor-js/vite-plugin (via turbo) then the self-host SPA into |
| 22 | # apps/host-selfhost/dist. | 126 | # apps/host-selfhost/dist. |
| 23 | RUN cd apps/host-selfhost && bun run build | 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 | # ── Runtime stage: serve the built app under Bun ──────────────────────────── | 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 | WORKDIR /app | 131 | WORKDIR /app |
| 35 | LABEL org.opencontainers.image.source="https://github.com/RhysSullivan/executor" \ | 132 | LABEL org.opencontainers.image.source="https://github.com/RhysSullivan/executor" \ |
| 36 | org.opencontainers.image.description="Single-container self-hosted Executor" \ | 133 | org.opencontainers.image.description="Single-container self-hosted Executor" \ |
| @@ -39,9 +136,25 @@ ENV NODE_ENV=production \ · 2 unmodified lines | |||
| 39 | EXECUTOR_HOST=0.0.0.0 \ | 136 | EXECUTOR_HOST=0.0.0.0 \ |
| 40 | PORT=4788 \ | 137 | PORT=4788 \ |
| 41 | EXECUTOR_DATA_DIR=/data | 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 | WORKDIR /app/apps/host-selfhost | 157 | WORKDIR /app/apps/host-selfhost |
| 44 | RUN mkdir -p /data | ||
| 45 | VOLUME ["/data"] | 158 | VOLUME ["/data"] |
| 46 | EXPOSE 4788 | 159 | EXPOSE 4788 |
| 47 | # Readiness probe against the public /api/health endpoint (a trivial DB ping). | 160 | # Readiness probe against the public /api/health endpoint (a trivial DB ping). |
| @@ -19,10 +19,8 @@ · 18 unmodified lines | |||
| 19 | }, | 19 | }, |
| 20 | "dependencies": { | 20 | "dependencies": { |
| 21 | "@better-auth/api-key": "^1.6.11", | 21 | "@better-auth/api-key": "^1.6.11", |
| 22 | "@effect/atom-react": "catalog:", | ||
| 23 | "@effect/platform-bun": "catalog:", | 22 | "@effect/platform-bun": "catalog:", |
| 24 | "@executor-js/api": "workspace:*", | 23 | "@executor-js/api": "workspace:*", |
| 25 | "@executor-js/app": "workspace:*", | ||
| 26 | "@executor-js/execution": "workspace:*", | 24 | "@executor-js/execution": "workspace:*", |
| 27 | "@executor-js/fumadb": "workspace:*", | 25 | "@executor-js/fumadb": "workspace:*", |
| 28 | "@executor-js/host-mcp": "workspace:*", | 26 | "@executor-js/host-mcp": "workspace:*", |
| @@ -32,18 +30,14 @@ · 3 unmodified lines | |||
| 32 | "@executor-js/plugin-mcp": "workspace:*", | 30 | "@executor-js/plugin-mcp": "workspace:*", |
| 33 | "@executor-js/plugin-microsoft": "workspace:*", | 31 | "@executor-js/plugin-microsoft": "workspace:*", |
| 34 | "@executor-js/plugin-openapi": "workspace:*", | 32 | "@executor-js/plugin-openapi": "workspace:*", |
| 35 | "@executor-js/react": "workspace:*", | ||
| 36 | "@executor-js/runtime-quickjs": "workspace:*", | 33 | "@executor-js/runtime-quickjs": "workspace:*", |
| 37 | "@executor-js/sdk": "workspace:*", | 34 | "@executor-js/sdk": "workspace:*", |
| 38 | "@libsql/client": "catalog:", | 35 | "@libsql/client": "catalog:", |
| 39 | "@libsql/kysely-libsql": "catalog:", | 36 | "@libsql/kysely-libsql": "catalog:", |
| 40 | "@modelcontextprotocol/sdk": "^1.29.0", | 37 | "@modelcontextprotocol/sdk": "^1.29.0", |
| 41 | "@tanstack/react-router": "catalog:", | ||
| 42 | "better-auth": "^1.6.11", | 38 | "better-auth": "^1.6.11", |
| 43 | "drizzle-orm": "catalog:", | 39 | "drizzle-orm": "catalog:", |
| 44 | "effect": "catalog:", | 40 | "effect": "catalog:" |
| 45 | "react": "catalog:", | ||
| 46 | "react-dom": "catalog:" | ||
| 47 | }, | 41 | }, |
| 48 | "devDependencies": { | 42 | "devDependencies": { |
| 49 | "@effect/vitest": "catalog:", | 43 | "@effect/vitest": "catalog:", |
| @@ -58,6 +52,12 @@ · 8 unmodified lines | |||
| 58 | "bun-types": "catalog:", | 52 | "bun-types": "catalog:", |
| 59 | "typescript": "catalog:", | 53 | "typescript": "catalog:", |
| 60 | "vite": "catalog:", | 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 | } |