Repository navigation
Expand file tree
/
Copy pathDockerfile.fullstack
More file actions
139 lines (117 loc) · 6.24 KB
/
Copy pathDockerfile.fullstack
File metadata and controls
139 lines (117 loc) · 6.24 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
# Combined UI + server image: one exposed port, nginx out front.
#
# nginx serves the built static UI and reverse-proxies /api and /socket.io
# (WebSocket upgrade included) to the Node server on loopback:8787 — the same
# routing the Vite dev proxy provides (see vite.config.ts). The server still
# spawns the Pi coding agent (`pi`), which shells out to ripgrep/fd/jq/git, so
# those CLIs are installed alongside the published Pi package.
#
# Layered as three stages so each cache layer is invalidated independently:
# 1. base - Node + CLI tools + nginx + Pi (changes rarely)
# 2. builder - install deps, bundle the server (dist/) and build the UI (ui-dist/)
# 3. runtime - copy the compiled bundle + static UI + nginx config and run
# ---------------------------------------------------------------------------
# Stage 1: base — Node runtime, the CLI tools Pi needs, nginx, and Pi itself.
# ---------------------------------------------------------------------------
FROM node:24-bookworm-slim AS base
# CLI tools the Pi agent's built-in tools (bash/grep/find) rely on at runtime,
# plus nginx (reverse proxy) and gettext-base (envsubst, used to template the
# nginx config at startup). Debian ships `fd` as `fdfind`, so expose it under
# the expected `fd` name.
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
git \
ripgrep \
fd-find \
jq \
ca-certificates \
nginx \
gettext-base \
&& ln -s "$(command -v fdfind)" /usr/local/bin/fd \
&& rm -rf /var/lib/apt/lists/*
# Pin the Pi coding agent so the `pi` binary is on PATH.
RUN npm install -g @earendil-works/pi-coding-agent@0.78.1
WORKDIR /app
# ---------------------------------------------------------------------------
# Stage 2: builder — install dependencies, bundle the server, and build the UI.
# A full install (incl. devDependencies) is needed for esbuild and Vite.
# strictDepBuilds=false: esbuild's optional platform binary is installed as a
# regular dependency, so its (blocked) build script isn't required.
# ---------------------------------------------------------------------------
FROM base AS builder
ENV PNPM_HOME=/usr/local/share/pnpm
ENV PATH=$PNPM_HOME:$PATH
RUN corepack enable
# Workspace manifests first so the dependency install layer caches independently
# of source changes. The whole workspace is installed (server + UI both build
# here).
COPY pnpm-lock.yaml pnpm-workspace.yaml package.json ./
COPY apps/server/package.json apps/server/
COPY apps/web/package.json apps/web/
COPY packages/shared/package.json packages/shared/
COPY packages/build/package.json packages/build/
COPY packages/ui-primitives/package.json packages/ui-primitives/
COPY packages/ui-extensions-sdk/package.json packages/ui-extensions-sdk/
COPY packages/windows/package.json packages/windows/
COPY packages/analytics/package.json packages/analytics/
COPY packages/utils/package.json packages/utils/
COPY packages/embed-react/package.json packages/embed-react/
COPY packages/remote-subagent/package.json packages/remote-subagent/
RUN pnpm install --frozen-lockfile --config.strictDepBuilds=false
# Server + UI source plus the shared workspace packages they build against
# (incl. the .md prompt/agent files and the orchestrator .ts extension copied
# next to the server bundle).
COPY apps/server ./apps/server
COPY apps/web ./apps/web
COPY packages ./packages
# Bundle the server -> apps/server/dist/index.js plus its runtime assets
# (prompts, agents, extensions, migrations). Invoked via node directly to avoid
# pnpm's pre-run dependency status check.
RUN node apps/server/build.mjs
# Build the static UI -> apps/web/dist. Runs `vite build` directly (skips the
# tsc typecheck) to keep the image build resilient. The default session bundle
# id is patched by docker-entrypoint.sh at runtime so the same image can target
# different bundle defaults.
RUN VITE_DEFAULT_SESSION_BUNDLE_ID=__TANGENT_RUNTIME_DEFAULT_SESSION_BUNDLE_ID__ \
pnpm --filter @tangent/web exec vite build
# Build the embedded UI runtime -> apps/web/dist/embed/v1 (served by nginx at
# /embed/). Runs after the main build because its outDir lives under dist/; the
# main build's emptyOutDir would otherwise wipe it.
RUN pnpm --filter @tangent/web exec vite build --config vite.embed.config.ts
# Fail early if the embed bundle is missing.
RUN test -f apps/web/dist/embed/v1/tangent-elements.js \
|| { echo "missing apps/web/dist/embed/v1/tangent-elements.js" >&2; exit 1; }
# Defense in depth: fail the image build if any extension the server loads at
# runtime is missing from the bundle (the build script also asserts this).
RUN for f in orchestrator proxyProvider memory triggers; do \
test -f "apps/server/dist/extensions/$f.ts" || { echo "missing apps/server/dist/extensions/$f.ts" >&2; exit 1; }; \
done
# Fail early if the UI build produced no entry HTML.
RUN test -f apps/web/dist/index.html || { echo "missing apps/web/dist/index.html" >&2; exit 1; }
# ---------------------------------------------------------------------------
# Stage 3: runtime — assemble the final image from base (tools + nginx + Pi)
# plus the self-contained server bundle and the static UI. No node_modules /
# pnpm: the bundle inlines all deps.
# ---------------------------------------------------------------------------
FROM base AS runtime
ENV NODE_ENV=production
# Cloud Run injects PORT; default to 8000. nginx listens on this externally
# exposed port; the Node server runs internally on 8787.
ENV PORT=8000
# Writable locations on Cloud Run for session workspaces and Pi's lockfiles/config.
ENV SESSIONS_ROOT=/tmp/sessions
ENV HOME=/tmp
ENV PI_BIN=pi
# Compiled server bundle (+ runtime assets) and the built static UI.
COPY --from=builder /app/apps/server/dist ./dist
COPY --from=builder /app/apps/web/dist ./ui-dist
# nginx config template + process supervisor entrypoint.
COPY docker/nginx.conf.template /app/docker/nginx.conf.template
COPY docker/docker-entrypoint.sh /app/docker/docker-entrypoint.sh
RUN chmod +x /app/docker/docker-entrypoint.sh
# esbuild and better-sqlite3 are kept external by apps/server/build.mjs (native
# binaries, used at runtime). Install just them so node can resolve the bare
# imports from /app/dist/index.js.
RUN npm install --no-save --no-package-lock esbuild@0.28.0 better-sqlite3@12.10.0
EXPOSE 8000
ENTRYPOINT ["/app/docker/docker-entrypoint.sh"]