Repository navigation
Expand file tree
/
Copy pathDockerfile
More file actions
226 lines (215 loc) · 13.3 KB
/
Copy pathDockerfile
File metadata and controls
226 lines (215 loc) · 13.3 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
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
# Minsky MCP Server image — serves the MCP tool registry over HTTP.
#
# Built from repo root because the MCP server needs the full Minsky source
# tree. Runs `minsky mcp start --http ...` by default; override CMD in Railway
# or docker-run to change the invocation.
#
# See docs/deploy-minsky-railway.md for deployment specifics (env vars, auth).
# Base-image digest pin (mt#1726). The mutable `oven/bun:1.4-slim` tag is
# pinned to a content-addressed digest so the build is reproducible and so
# image-tag drift cannot silently alter the runtime bun version. The pin
# itself is the safety measure — without it, a `docker pull` weeks from now
# could surface a different bun version against the same lockfile.
#
# To rotate the digest (only when intentionally adopting a newer bun): run
# `docker buildx imagetools inspect oven/bun:1.4-slim` (or `docker pull` and
# copy its `Digest:` line), replace the `@sha256:...` suffix below, verify
# locally that `bun install --frozen-lockfile --production --ignore-scripts`
# still succeeds against the committed lockfile, commit, let Railway rebuild.
# The same digest must go into `services/cockpit/Dockerfile` (both stages)
# and `services/reviewer/Dockerfile` — mt#3835 aligned all four.
#
# Current pin: bun 1.4.2 (mt#3835, 2026-09-10). Moved from 1.3.14 because
# oven-sh/bun#34861 (streaming tarball extraction losing its format selection
# on a tiny first HTTP chunk, the cause of mt#3623's install flake) shipped in
# 1.4.0, retiring the install retry loop and the disable-streaming-extraction
# env-flag hedge at every install site.
FROM oven/bun:1.4-slim@sha256:cb3bbbb08e13a4a2ff400f24c7a2a1d5efa83f6ef8544d52d95a519631e2fc61 AS base
WORKDIR /app
# Dependency layer — cached unless package.json/bun.lock changes.
COPY package.json bun.lock ./
# Workspace package manifests (mt#1681 / mt#1722 / mt#1727 / mt#1977 /
# mt#2621). The root declares `workspaces: ["packages/*", "services/*"]`,
# and bun's `--frozen-lockfile` install rejects any divergence between the
# workspace topology in the build context and the committed lockfile:
# every directory matched by the glob AND containing a `package.json` MUST
# be COPYed below before the `RUN bun install --frozen-lockfile` step, or
# bun aborts with `error: lockfile had changes, but lockfile is frozen`.
# The local `bun install` succeeds because the full repo is mounted; the
# Railway build fails because COPY is selective.
#
# The block below is GENERATED (mt#2621) — do not hand-edit the COPY lines.
# `bun run generate:dockerfile-workspace-copies`
# (scripts/generate-dockerfile-workspace-copies.ts) resolves the workspaces
# glob and regenerates the block between the markers; `src/hooks/pre-commit.ts`
# runs it automatically on every commit and re-stages this file if it
# changed, so the invariant can no longer drift out of sync by hand (the
# mt#1977 75-minute outage and mt#1991 4-hour reviewer-Dockerfile outage were
# both caused by exactly that kind of drift). Add or remove a workspace by
# editing its `package.json` under `packages/` or `services/` — the
# generator picks up the change on the next commit.
#
# Not a workspace despite matching the glob:
# - `services/minsky-mcp/` — directory exists but contains no `package.json`,
# so the workspaces glob skips it. The MCP server's source lives under
# root `src/` and is bundled directly by the `bun build` step below.
# === BEGIN GENERATED: workspace package.json COPY (mt#2621) ===
# Generated by `bun run generate:dockerfile-workspace-copies`
# (scripts/generate-dockerfile-workspace-copies.ts) from root package.json's
# `workspaces` glob. Do not hand-edit the COPY lines below — add/remove a
# workspace's package.json instead and rerun the generator. Pre-commit does
# this automatically and re-stages the result (mt#2621).
COPY packages/domain/package.json ./packages/domain/package.json
COPY packages/shared/package.json ./packages/shared/package.json
COPY services/cockpit/package.json ./services/cockpit/package.json
COPY services/reviewer/package.json ./services/reviewer/package.json
COPY services/site/package.json ./services/site/package.json
# === END GENERATED: workspace package.json COPY ===
# Hoist deps via the root workspace install (mt#1726).
#
# - `--frozen-lockfile`: enforces lockfile fidelity. Build fails on drift.
# - `--production`: skips dev deps. Verified by grep that no runtime import
# from `src/cli.ts mcp start --http` reaches commander, ts-morph, eslint,
# prettier, vite/tailwind, husky, lint-staged, testcontainers, or the
# other 22 devDependencies. Each was checked explicitly.
# - `--ignore-scripts`: skips package.json's `prepare: husky` install
# hook, which installs git-hook stubs. No .git in the image
# (`.git` is .dockerignore'd), so husky would no-op-with-warning at
# best, error at worst. (A `postinstall: npx skills experimental_install`
# hook was also previously skipped here; retired in mt#1902.)
#
# Mirrors `services/reviewer/Dockerfile:24` which uses the same flag set
# and ships to production without issue.
RUN bun install --frozen-lockfile --production --ignore-scripts
# Source layer — selective COPY (mt#1726). Replaces the prior blanket
# `COPY . .` which pulled in tests, docs, scripts, eslint configuration,
# and unrelated service trees. Order: stable files (tsconfig, shared
# sources) first, main src/ tree last because it changes most often.
#
# Invariants on what is INTENTIONALLY omitted (verified at PR-creation):
# - `bunfig.toml` (root) contains only `[test]` config — irrelevant to
# `bun run src/cli.ts mcp start --http`. Bun does not read it at runtime
# for a `bun run <file>` invocation.
# - `packages/shared/` contains only `package.json`, `tsconfig.json`, and
# TypeScript sources under `src/` (verified by `find packages/shared
# -type f`). No build artifacts, JSON data, or non-TS assets — so the
# `package.json` (in the deps layer) + `tsconfig.json` + `src/` copy
# here is the complete runtime dependency set for `@minsky/shared`.
# - `.git/` is excluded by `.dockerignore`. The MCP server reads its repo
# backend from `.minsky/config.yaml` (copied below), not from a git
# remote — without that file the container exits with
# `git remote get-url: command not found` at boot.
COPY tsconfig.json ./tsconfig.json
COPY packages/shared/tsconfig.json ./packages/shared/tsconfig.json
COPY packages/shared/src ./packages/shared/src
COPY packages/domain/tsconfig.json ./packages/domain/tsconfig.json
COPY packages/domain/src ./packages/domain/src
COPY .minsky/config.yaml ./.minsky/config.yaml
COPY src ./src
# Default HTTP port. Railway injects $PORT at runtime; MCP server reads it
# via the --port CLI flag (see CMD below).
ENV PORT=3000
EXPOSE 3000
# Profile B (Railway HTTP): build the bundle at image-build time.
# This bypasses the bin entry entirely — the bin entry is for source installs
# (Profile A/C) only. Direct bundle exec avoids the freshness-check overhead
# and the need for git at runtime.
#
# mt#3023 — flag parity with the other two `bun build` sites: the `build`
# script in package.json and `scripts/cli-entry.ts`'s source-install
# self-rebuild. All three build `--minify --sourcemap=external`. mt#3091
# made this parity mechanical instead of hand-maintained: the RUN line below
# is GENERATED from `scripts/cli-entry.ts`'s canonical `bunBuildArgs()` (see
# the marker block below the flag-property notes). Three properties of this
# line are load-bearing:
#
# - `--outdir` + `--entry-naming`, NOT `--outfile`: bun rejects an external
# source map written through `--outfile` outright ("error: cannot use an
# external source map without --outdir"). `--entry-naming minsky.js` keeps
# the output path at `dist/minsky.js`, which the CMD below and the mt#1767
# migrations COPY both depend on.
# - `--sourcemap=external` is REQUIRED alongside `--minify`, not optional.
# Bun symbolicates a minified bundle's stack traces back to the original
# `src/**.ts` file:line through the adjacent `.map`; without it, traces are
# minified single-line offsets and bun logs `note: missing sourcemaps`.
# Since this image previously emitted no source map at all, keeping the
# `.map` next to the bundle is a strict improvement in the diagnosability of
# a production crash (the failure mode of mt#1763 / mt#1785 / mt#2345).
# - The `.map` is deliberately KEPT in the image. It costs more than `--minify`
# saves — measured on this tree: 37.5 MB unminified/no-map vs 24.8 MB
# minified + 54.7 MB map, i.e. a net +42 MB layer — so this change is NOT an
# image-size win, it is a production-diagnosability win bought with layer
# size. Stripping the `.map` to reclaim the size would leave traces strictly
# worse than before this change.
#
# The artifact shape produced here is the same one `.github/workflows/
# bundle-boot-smoke.yml` builds (`bun run build`) and boots on every PR, so the
# minified bundle's ability to start the MCP server is already gated in CI.
#
# The block below is GENERATED (mt#3091) — do not hand-edit the RUN line.
# `bun run generate:dockerfile-bun-build` (scripts/generate-dockerfile-bun-build.ts)
# renders it from the single canonical `bunBuildArgs()` in
# `scripts/cli-entry.ts` — the same source package.json's `build` script is
# mechanically checked against (`bun run check:bun-build-sync`).
# `src/hooks/pre-commit.ts` runs the generator automatically on every commit
# and re-stages this file if it changed, so this line can no longer drift
# out of sync by hand (mirrors the mt#2621 workspace-COPY-block pattern).
# === BEGIN GENERATED: bun build invocation (mt#3091) ===
# Generated by `bun run generate:dockerfile-bun-build`
# (scripts/generate-dockerfile-bun-build.ts) from the canonical
# `bunBuildArgs()` in scripts/cli-entry.ts (the same source
# package.json's `build` script is checked against). Do not hand-edit
# the RUN line below — change bunBuildArgs() and rerun the generator;
# pre-commit does this automatically and re-stages the result (mt#3091,
# mirroring the mt#2621 workspace-COPY-block pattern).
RUN bun build --target=bun --outdir=dist --entry-naming minsky.js --sourcemap=external --minify --external tiktoken --external @pnpm/tabtab --external typescript --external vite src/cli.ts
# === END GENERATED: bun build invocation ===
# mt#5063 — the container build calls `bun build` DIRECTLY, so it never runs
# `package.json`'s `build` script and never picked up the portability guard
# wired in there. That is exactly the gap PR #3706 R1 flagged: the image is a
# published artifact too, and the defect this guards (a `__dirname` baked to the
# BUILD host, breaking a runtime asset lookup for everyone) would ship here
# unchecked while the npm path was covered.
#
# Fails the image build rather than warning: a bundle pinned to the builder's
# filesystem is not a degraded image, it is a broken one.
#
# Copying the ONE file rather than `scripts/` wholesale: this stage deliberately
# copies a minimal set (manifests, tsconfigs, packages/*/src, src), and pulling
# the whole directory in would widen the build context and invalidate the layer
# on every unrelated script edit. The first version of this omitted the COPY
# entirely and failed `docker-build-smoke` with `Module not found` — the check
# is only worth having if the image build can actually run it.
COPY scripts/verify-bundle-portability.ts ./scripts/verify-bundle-portability.ts
RUN bun scripts/verify-bundle-portability.ts
# mt#1767 — copy Drizzle migrations next to the bundle so the bundled
# `postgres-provider.ts`'s `resolveMigrationsFolder()` can find them via the
# `./storage/migrations/pg` candidate (relative to `import.meta.url` of
# `dist/minsky.js` = `/app/dist/storage/migrations/pg/`).
#
# Without this COPY, mt#1763's auto-migrate path crashed Railway in a boot
# loop on 2026-05-12 because the source-relative path `../../storage/...`
# resolved outside `/app`. mt#1787's bundle-boot-smoke CI gate now catches
# any future regression of this exact class at PR time before merge.
RUN mkdir -p dist/storage/migrations && cp -r packages/domain/src/storage/migrations/pg dist/storage/migrations/pg
# Default: start MCP over HTTP with auth required. The MINSKY_MCP_AUTH_TOKEN
# env var must be set for this to succeed. $PORT expands at container start
# via the shell form.
#
# IN-IMAGE SOURCE-MAP POLICY (mt#3023): `dist/minsky.js.map` (~57 MB) sits next
# to the bundle and is shipped BY DESIGN. Bun reads it to symbolicate this
# process's stack traces back to original `src/**.ts` file:line. Do NOT strip it
# as an image-size optimization — doing so while `--minify` stays on leaves
# production traces strictly worse than they were before this policy existed.
# Rationale + the size tradeoff: docs/deploy-minsky-railway.md §What ships in
# the image.
#
# No `--preload reflect-metadata` here, deliberately (mt#3680). This CMD carried
# it from mt#3561 through mt#3680 because bun's bundler emitted the polyfill's
# CommonJS require AFTER the init calls that reach tsyringe, so the bundle could
# not boot on its own. `src/reflect-polyfill.ts` fixes that ordering inside the
# bundle, which makes the flag redundant here — and a redundant flag is worse
# than none, because it would leave production the one place that never
# exercises the self-sufficient path. `bundle-boot-smoke.yml` boots this exact
# bare form, so a regression fails at PR time rather than at container start.
CMD bun run dist/minsky.js mcp start --http --host 0.0.0.0 --port $PORT --require-auth