1- FROM node:lts-trixie-slim AS base
1+ # ── Stage 1: base ─────────────────────────────────────────────
2+ # Pinned Node 22 on Debian Trixie slim for reproducibility.
3+ FROM node:22-trixie-slim AS base
4+
25RUN apt-get update \
3- && apt-get install -y --no-install-recommends ca-certificates curl git \
6+ && apt-get install -y --no-install-recommends \
7+ ca-certificates curl git tini \
48 && rm -rf /var/lib/apt/lists/*
9+
510RUN corepack enable
611
12+ # ── Stage 2: deps ─────────────────────────────────────────────
13+ # Install only production + build dependencies using lockfile.
714FROM base AS deps
815WORKDIR /app
16+
17+ # Copy workspace manifests first (layer caching)
918COPY package.json pnpm-workspace.yaml pnpm-lock.yaml .npmrc ./
1019COPY cli/package.json cli/
1120COPY server/package.json server/
@@ -20,23 +29,62 @@ COPY packages/adapters/gemini-local/package.json packages/adapters/gemini-local/
2029COPY packages/adapters/openclaw-gateway/package.json packages/adapters/openclaw-gateway/
2130COPY packages/adapters/opencode-local/package.json packages/adapters/opencode-local/
2231COPY packages/adapters/pi-local/package.json packages/adapters/pi-local/
32+ COPY packages/plugins/sdk/package.json packages/plugins/sdk/
33+ COPY packages/plugins/create-paperclip-plugin/package.json packages/plugins/create-paperclip-plugin/
2334
2435RUN pnpm install --frozen-lockfile
2536
37+ # ── Stage 3: build ────────────────────────────────────────────
38+ # Build UI and server from source.
2639FROM base AS build
2740WORKDIR /app
41+
2842COPY --from=deps /app /app
2943COPY . .
30- RUN pnpm --filter @paperclipai/ui build
31- RUN pnpm --filter @paperclipai/server build
32- RUN test -f server/dist/index.js || (echo "ERROR: server build output missing" && exit 1)
3344
34- FROM base AS production
45+ RUN pnpm --filter @paperclipai/ui build \
46+ && pnpm --filter @paperclipai/server build \
47+ && test -f server/dist/index.js || (echo "ERROR: server build output missing" && exit 1)
48+
49+ # Prune dev dependencies after build
50+ RUN pnpm prune --prod --no-optional
51+
52+ # ── Stage 4: production ───────────────────────────────────────
53+ # Minimal runtime image — no build tools, no source code bloat.
54+ FROM node:22-trixie-slim AS production
55+
56+ # Labels for container registries
57+ LABEL org.opencontainers.image.source="https://github.com/paperclipinc/paperclip"
58+ LABEL org.opencontainers.image.description="Paperclip — AI company orchestration platform"
59+ LABEL org.opencontainers.image.vendor="Paperclip Inc."
60+ LABEL org.opencontainers.image.licenses="MIT"
61+
62+ RUN apt-get update \
63+ && apt-get install -y --no-install-recommends \
64+ ca-certificates curl git tini \
65+ && rm -rf /var/lib/apt/lists/*
66+
67+ RUN corepack enable
68+
69+ # Install agent runtimes globally
70+ RUN npm install --global --omit=dev \
71+ @anthropic-ai/claude-code@latest \
72+ @openai/codex@latest \
73+ opencode-ai \
74+ && npm cache clean --force
75+
76+ # Create data directory
77+ RUN mkdir -p /paperclip && chown node:node /paperclip
78+
3579WORKDIR /app
36- COPY --chown=node:node --from=build /app /app
37- RUN npm install --global --omit=dev @anthropic-ai/claude-code@latest @openai/codex@latest opencode-ai \
38- && mkdir -p /paperclip \
39- && chown node:node /paperclip
80+
81+ # Copy only built artifacts and production dependencies
82+ COPY --chown=node:node --from=build /app/package.json /app/pnpm-workspace.yaml /app/.npmrc ./
83+ COPY --chown=node:node --from=build /app/node_modules ./node_modules
84+ COPY --chown=node:node --from=build /app/server ./server
85+ COPY --chown=node:node --from=build /app/ui/dist ./ui/dist
86+ COPY --chown=node:node --from=build /app/cli ./cli
87+ COPY --chown=node:node --from=build /app/packages ./packages
4088
4189ENV NODE_ENV=production \
4290 HOME=/paperclip \
@@ -52,5 +100,12 @@ ENV NODE_ENV=production \
52100VOLUME ["/paperclip" ]
53101EXPOSE 3100
54102
103+ # Run as non-root
55104USER node
105+
106+ # Use tini as PID 1 for proper signal handling
107+ HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
108+ CMD curl -fsS http://localhost:3100/api/health || exit 1
109+
110+ ENTRYPOINT ["tini" , "--" ]
56111CMD ["node" , "--import" , "./server/node_modules/tsx/dist/loader.mjs" , "server/dist/index.js" ]
0 commit comments