-
Notifications
You must be signed in to change notification settings - Fork 153
Expand file tree
/
Copy pathDockerfile.lite
More file actions
158 lines (133 loc) · 7.2 KB
/
Copy pathDockerfile.lite
File metadata and controls
158 lines (133 loc) · 7.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
# ──────────────────────────────────────────────
# Marinara Engine — Wolfi Lite Docker Build
# ──────────────────────────────────────────────
# Three-stage build using Chainguard Wolfi images:
# 1. Builder — full dev toolchain, compiles TS and bundles client
# 2. Deps — production-only node_modules (stripped of heavy native deps)
# 3. Runtime — minimal Wolfi base + Node.js 24 binary only
#
# Disabled features (MARINARA_LITE):
# • Local sidecar model (llama-server / Gemma)
# • Local embedding model (onnxruntime + all-MiniLM-L6-v2)
# • Memory recall (semantic search)
# • Local Whisper voice input (Conversation call speech-to-text)
# ──────────────────────────────────────────────
# ── Stage 1: Build ──
# Uses wolfi-base + nodejs-24 + npm so Node ABI matches across all stages.
FROM cgr.dev/chainguard/wolfi-base:latest@sha256:07e60ff6586b56f03c625e27b604f9f7d29498fef32f099f6560f0d207b4a056 AS builder
ARG BUILD_COMMIT
ARG BUILD_BRANCH
WORKDIR /app
RUN apk add --no-cache nodejs-24 npm && \
npm install -g pnpm@10.34.5
ENV MARINARA_LITE=true
ENV VITE_MARINARA_LITE=true
# Copy workspace config first (layer cache for deps)
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml .npmrc ./
COPY packages/shared/package.json packages/shared/
COPY packages/server/package.json packages/server/
COPY packages/client/package.json packages/client/
COPY scripts/clean-stale-client-artifacts.mjs scripts/clean-stale-client-artifacts.mjs
# Install all build dependencies. --ignore-scripts is what prevents
# onnxruntime-node's GPU postinstall (the Lite-mode requirement); optional
# deps are kept so Rollup and esbuild's platform-native binaries land in
# node_modules. We then rebuild native packages used in Lite mode to register
# their bindings, which --ignore-scripts skipped at install time.
RUN --mount=type=cache,target=/app/.pnpm-store \
pnpm install --frozen-lockfile --ignore-scripts && \
pnpm rebuild esbuild rollup sharp
# Copy source code
COPY tsconfig.base.json ./
COPY packages/shared/ packages/shared/
COPY packages/server/ packages/server/
COPY packages/client/ packages/client/
# Build everything: shared → server + client in parallel
ENV NODE_OPTIONS="--max-old-space-size=4096"
RUN pnpm build
# Bake the git ref into build-meta.json because the runtime image has no .git directory.
RUN BUILD_COMMIT="$BUILD_COMMIT" BUILD_BRANCH="$BUILD_BRANCH" node -e 'const fs = require("node:fs"); const meta = {}; if (process.env.BUILD_COMMIT) meta.commit = process.env.BUILD_COMMIT; if (process.env.BUILD_BRANCH) meta.branch = process.env.BUILD_BRANCH; if (Object.keys(meta).length > 0) fs.writeFileSync("packages/server/dist/config/build-meta.json", JSON.stringify(meta));'
# ── Stage 2: Production dependencies ──
# Separate stage keeps builder cache warm while allowing aggressive dep stripping.
FROM cgr.dev/chainguard/wolfi-base:latest@sha256:07e60ff6586b56f03c625e27b604f9f7d29498fef32f099f6560f0d207b4a056 AS deps
WORKDIR /app
RUN apk add --no-cache nodejs-24 npm && \
npm install -g pnpm@10.34.5
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml .npmrc ./
COPY packages/shared/package.json packages/shared/
COPY packages/server/package.json packages/server/
COPY packages/client/package.json packages/client/
# Install production deps. We can't pass --no-optional here because sharp's
# native binary ships through transitive optional packages (@img/sharp-*),
# which --no-optional would drop alongside the truly-optional onnxruntime
# packages. Instead, install everything, rebuild sharp so its native binding
# is registered (--ignore-scripts skipped it at install time), and then
# explicitly prune onnxruntime to keep the lite image small.
RUN --mount=type=cache,target=/app/.pnpm-store \
pnpm install --frozen-lockfile --prod --ignore-scripts && \
pnpm rebuild sharp && \
rm -rf .pnpm/onnxruntime-node@* \
.pnpm/onnxruntime-web@* \
node_modules/.pnpm/onnxruntime-node@* \
node_modules/.pnpm/onnxruntime-web@* \
packages/server/node_modules/onnxruntime-node \
packages/server/node_modules/onnxruntime-web
# Archive node_modules preserving pnpm symlinks — Docker COPY
# dereferences them which would break module resolution.
RUN tar cf /tmp/node_modules.tar \
.pnpm \
node_modules \
packages/shared/node_modules \
packages/server/node_modules 2>/dev/null; true
# ── Stage 3: Minimal Wolfi runtime ──
# Only the Node.js 24 binary — no npm, no build tools, no shell bloat.
FROM cgr.dev/chainguard/wolfi-base:latest@sha256:07e60ff6586b56f03c625e27b604f9f7d29498fef32f099f6560f0d207b4a056 AS production
WORKDIR /app
RUN apk add --no-cache nodejs-24-minimal && \
# Drop the package manager cache to save space
rm -rf /var/cache/apk/*
# Extract prod node_modules (preserves pnpm symlinks).
# Use a bind mount from the deps stage to avoid copying the tar into the image
RUN --mount=type=bind,from=deps,source=/tmp/node_modules.tar,target=/tmp/node_modules.tar \
tar xf /tmp/node_modules.tar -C /app
# Copy workspace manifests (pnpm resolution needs them)
COPY package.json pnpm-workspace.yaml ./
COPY packages/shared/package.json packages/shared/
COPY packages/server/package.json packages/server/
COPY packages/client/package.json packages/client/
# Fail the image build if pnpm's symlinked virtual store is incomplete.
RUN node -e "for (const dep of ['fastify', '@fastify/cors', 'sharp']) require.resolve(dep, { paths: ['/app/packages/server'] })"
# Copy built artifacts from builder
COPY --from=builder /app/packages/shared/dist packages/shared/dist
COPY --from=builder /app/packages/server/dist packages/server/dist
COPY --from=builder /app/packages/client/dist packages/client/dist
COPY scripts/docker-entrypoint.mjs /usr/local/bin/marinara-docker-entrypoint.mjs
COPY scripts/install-backgroundremover.mjs scripts/install-backgroundremover.mjs
# The storage downgrade escape hatch (#4708) — docs/TROUBLESHOOTING.md tells
# Docker users to run it in a one-off container, so it must ship in the image.
COPY scripts/protect-launcher-data.mjs scripts/protect-launcher-data.mjs
# User guides served by the in-app documentation viewer (/api/docs)
COPY README.md README.md
COPY docs/ docs/
# Ensure data directory exists
RUN mkdir -p /app/data && \
chown nonroot:nonroot /app/data
# Runtime feature flag
ENV MARINARA_LITE=true
ENV DATA_DIR=/app/data
ENV FILE_STORAGE_DIR=/app/data/storage
# Pin the Claude Agent SDK + synthetic-session writer to a path under the
# already-chowned data volume. Avoids the post-setuid HOME=/root trap and
# makes the future "mount your host ~/.claude here" workflow a single
# -v flag for the user.
ENV CLAUDE_CONFIG_DIR=/app/data/claude-config
ENV PORT=7860
ENV HOST=0.0.0.0
ENV NODE_ENV=production
ENV MARINARA_DOCKER=true
ENV MARINARA_DOCKER_USER=nonroot
ENV MARINARA_DOCKER_GROUP=nonroot
VOLUME /app/data
EXPOSE 7860
USER root
ENTRYPOINT ["node", "/usr/local/bin/marinara-docker-entrypoint.mjs"]
CMD ["node", "packages/server/dist/index.js"]