-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathDockerfile
More file actions
140 lines (120 loc) · 7.19 KB
/
Copy pathDockerfile
File metadata and controls
140 lines (120 loc) · 7.19 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
# ─── Build stage ─────────────────────────────────────────────────────────────
FROM node:22-slim AS builder
WORKDIR /app
# ── Build/release metadata + optional Sentry source-map upload ───────────────
# All optional — a plain `docker build` with none of these still succeeds.
# GIT_SHA generic build identifier. Baked into the runtime image (prod
# stage below) and used as the Sentry release, so uploaded
# source maps line up with runtime events.
# SENTRY_ORG / only used to upload source maps. `npm run build` self-skips
# SENTRY_PROJECT the upload unless ORG + PROJECT + the sentry_auth_token
# secret are all present (see scripts/upload-server-sourcemaps.mjs
# and vite.server.config.ts). None of this is Buildkite- or
# Sentry-specific to the Dockerfile — the CI that supplies the
# values lives outside it (.buildkite/pipeline.yml here).
ARG GIT_SHA=""
ARG SENTRY_ORG=""
ARG SENTRY_PROJECT=""
# AWS RDS CA bundle for IAM-auth Postgres connections.
# https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/UsingWithRDS.SSL.html
# Fetched here so the certificate becomes part of the build cache, and so the
# build fails cleanly if AWS ever takes the URL down — rather than failing at
# pod startup. Validated on first DB connect via tls.rejectUnauthorized.
# --chmod=644: ADD'ed URLs default to 600 root:root, which survives the cp +
# COPY --from=builder into the prod stage and is unreadable by `USER node`.
ADD --chmod=644 https://truststore.pki.rds.amazonaws.com/global/global-bundle.pem /tmp/global-bundle.pem
COPY package*.json ./
RUN npm ci
COPY . .
# Build renderer (out/renderer/) + server JS (out/server/). The Sentry auth
# token is mounted as a BuildKit secret (never baked into an image layer) and is
# optional — absent ⇒ empty ⇒ the source-map upload is skipped. (This Dockerfile
# already requires BuildKit for `ADD --chmod` above.)
RUN --mount=type=secret,id=sentry_auth_token \
GIT_SHA="$GIT_SHA" SENTRY_ORG="$SENTRY_ORG" SENTRY_PROJECT="$SENTRY_PROJECT" \
SENTRY_AUTH_TOKEN="$(cat /run/secrets/sentry_auth_token 2>/dev/null || true)" \
npm run build
# Place the RDS CA bundle next to the compiled DB module so `path.join(
# __dirname, 'global-bundle.pem')` finds it in `out/main/db/`.
RUN cp /tmp/global-bundle.pem out/main/db/global-bundle.pem
# ─── Production stage ────────────────────────────────────────────────────────
FROM node:22-slim
WORKDIR /app
# git is required at runtime for repository sync (clone/fetch/worktree); curl
# is needed to fetch the Cursor CLI installer below. git-lfs is required for
# repos that store assets in git-LFS (e.g. a Yarn zero-install `.yarn/cache`):
# without it, worktree checkouts contain LFS *pointer* files instead of content,
# which breaks the agent's `yarn install`/build and stalls the run. `git lfs
# install --system` registers the smudge/clean filters for every later git op.
# No PID-1 wrapper (tini/dumb-init): Node 22 receives signals correctly when
# used as PID 1 with an exec-form ENTRYPOINT, and the server installs its
# own SIGTERM/SIGINT handlers for graceful shutdown.
RUN apt-get update && \
apt-get install -y --no-install-recommends git git-lfs ca-certificates curl && \
git lfs install --system && \
rm -rf /var/lib/apt/lists/*
# GitHub CLI (`gh`), installed from GitHub's official apt repo (needs the `curl`
# installed above to fetch the signing key). Agents use it for API/PR operations
# (`gh pr create`, `gh api`); at run time the runner injects the repo's git
# credential as GH_TOKEN so `gh` authenticates as the same identity `git push`
# uses. Install steps per https://github.com/cli/cli/blob/trunk/docs/install_linux.md
RUN mkdir -p -m 755 /etc/apt/keyrings && \
curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg \
-o /etc/apt/keyrings/githubcli-archive-keyring.gpg && \
chmod go+r /etc/apt/keyrings/githubcli-archive-keyring.gpg && \
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" \
> /etc/apt/sources.list.d/github-cli.list && \
apt-get update && \
apt-get install -y --no-install-recommends gh && \
rm -rf /var/lib/apt/lists/* && \
# Fail the build loudly if `gh` ever fails to land on PATH.
gh --version
# Agent CLIs the runner shells out to. Without these the deployed host reports
# every runner as "not installed" on the agent select screen and runs fail.
# - claude → @anthropic-ai/claude-code (npm, bin: claude)
# - amp → @sourcegraph/amp (npm, bin: amp)
# - cursor-agent → cursor.com/install script (not on npm)
# npm globals land in /usr/local/bin (on PATH for every user, incl. `node`).
RUN npm install -g @anthropic-ai/claude-code @sourcegraph/amp && \
npm cache clean --force
# Cursor's installer chooses its install dir purely from $HOME: it drops a
# versioned build under $HOME/.local/share/cursor-agent and a `cursor-agent`
# launcher into $HOME/.local/bin. Point $HOME at a shared, world-readable dir
# (not /root, which is mode 700 and unreadable by `node`), then symlink the
# launcher onto PATH.
#
# The env assignment MUST sit on `bash`, not `curl`: in a pipeline
# `VAR=v curl ... | bash` the prefix binds only to `curl`, so the installer
# (bash) would inherit the build's HOME=/root and write there instead — leaving
# the symlink below dangling and `which cursor-agent` (the runtime install
# check) failing on the deployed host.
ENV CURSOR_HOME=/opt/cursor
RUN mkdir -p "$CURSOR_HOME" && \
curl -fsS https://cursor.com/install | HOME="$CURSOR_HOME" bash && \
ln -sf "$CURSOR_HOME/.local/bin/cursor-agent" /usr/local/bin/cursor-agent && \
chmod -R a+rX "$CURSOR_HOME" && \
# Fail the build loudly if the install layout ever drifts again: -x follows
# the whole symlink chain and confirms the agent is actually executable,
# mirroring the runtime `which cursor-agent` check.
test -x /usr/local/bin/cursor-agent
COPY package*.json ./
RUN npm ci --omit=dev && npm cache clean --force
# Compiled JS only — no TS source / tsx runtime in the image.
COPY --from=builder /app/out ./out
# /data is the persistent volume for run logs and bare git clones.
# The Postgres database lives in RDS. In production / staging the pod uses
# IAM auth (DATABASE_USE_RDS_IAM=true + DATABASE_HOST/PORT/NAME/USER); local
# dev uses DATABASE_URL against the docker-compose Postgres.
VOLUME /data
ENV CONDUIT_DATA_DIR=/data
ENV PORT=7456
ENV NODE_ENV=production
# Baked-in build identifier — used as the Sentry release at runtime so error
# events match the source maps uploaded for this commit. Empty in local/dev
# builds (harmless; Sentry release just goes untagged).
ARG GIT_SHA=""
ENV GIT_SHA=${GIT_SHA}
EXPOSE 7456
# Run an unprivileged user. The `node` user is provided by the official image.
USER node
ENTRYPOINT ["node", "out/server/index.js"]