History

Optimize self-host Docker image size

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

Rhys Sullivan committed 4 months ago

Commit 01a6d751 parent d5b7c462 files · +132 −19Browse files

Unified · Split

2 files
  1. apps/host-selfhost/Dockerfile +124 −11
  2. apps/host-selfhost/package.json +8 −8
apps/host-selfhost/Dockerfile+124−11
@@ -12,25 +12,122 @@ · 11 unmodified lines
1212 # SQLite (libSQL, file:/data/...) lives in /data, QuickJS + MCP run in-process —
1313 # so there's no postgres/worker/proxy to orchestrate.
1414
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+
15119 # ── Build stage: install the workspace + build the SPA ──────────────────────
16120 FROM oven/bun:1 AS build
17121 WORKDIR /app
18122 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).
20124 RUN bun install --frozen-lockfile
21125 # Builds @executor-js/vite-plugin (via turbo) then the self-host SPA into
22126 # apps/host-selfhost/dist.
23127 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
31128
32129 # ── Runtime stage: serve the built app under Bun ────────────────────────────
33−FROM oven/bun:1 AS runtime
130+FROM gcr.io/distroless/cc-debian12 AS runtime
34131 WORKDIR /app
35132 LABEL org.opencontainers.image.source="https://github.com/RhysSullivan/executor" \
36133 org.opencontainers.image.description="Single-container self-hosted Executor" \
@@ -39,9 +136,25 @@ ENV NODE_ENV=production \ · 2 unmodified lines
39136 EXECUTOR_HOST=0.0.0.0 \
40137 PORT=4788 \
41138 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
43157 WORKDIR /app/apps/host-selfhost
44−RUN mkdir -p /data
45158 VOLUME ["/data"]
46159 EXPOSE 4788
47160 # Readiness probe against the public /api/health endpoint (a trivial DB ping).
apps/host-selfhost/package.json+8−8
@@ -19,10 +19,8 @@ · 18 unmodified lines
1919 },
2020 "dependencies": {
2121 "@better-auth/api-key": "^1.6.11",
22− "@effect/atom-react": "catalog:",
2322 "@effect/platform-bun": "catalog:",
2423 "@executor-js/api": "workspace:*",
25− "@executor-js/app": "workspace:*",
2624 "@executor-js/execution": "workspace:*",
2725 "@executor-js/fumadb": "workspace:*",
2826 "@executor-js/host-mcp": "workspace:*",
@@ -32,18 +30,14 @@ · 3 unmodified lines
3230 "@executor-js/plugin-mcp": "workspace:*",
3331 "@executor-js/plugin-microsoft": "workspace:*",
3432 "@executor-js/plugin-openapi": "workspace:*",
35− "@executor-js/react": "workspace:*",
3633 "@executor-js/runtime-quickjs": "workspace:*",
3734 "@executor-js/sdk": "workspace:*",
3835 "@libsql/client": "catalog:",
3936 "@libsql/kysely-libsql": "catalog:",
4037 "@modelcontextprotocol/sdk": "^1.29.0",
41− "@tanstack/react-router": "catalog:",
4238 "better-auth": "^1.6.11",
4339 "drizzle-orm": "catalog:",
44− "effect": "catalog:",
45− "react": "catalog:",
46− "react-dom": "catalog:"
40+ "effect": "catalog:"
4741 },
4842 "devDependencies": {
4943 "@effect/vitest": "catalog:",
@@ -58,6 +52,12 @@ · 8 unmodified lines
5852 "bun-types": "catalog:",
5953 "typescript": "catalog:",
6054 "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:"
6262 }
6363 }