-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathDockerfile.orchestrator
More file actions
132 lines (116 loc) · 6.08 KB
/
Copy pathDockerfile.orchestrator
File metadata and controls
132 lines (116 loc) · 6.08 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
# syntax=docker/dockerfile:1
# hadolint global ignore=DL3008
# Orchestrator image: webhook server + WebSocket daemon registry + triage.
# Stays lean: no developer CLI toolchain, no docker CLI (orchestrator does not
# shell to docker). Daemon tooling lives in Dockerfile.daemon.
#
# The base/deps/development stages are byte-identical to Dockerfile.daemon
# between the SHARED-BASE-BEGIN/END markers; scripts/check-dockerfile-base-sync.ts
# enforces this in CI.
# --- SHARED-BASE-BEGIN ---
FROM oven/bun:1.4.1 AS base
WORKDIR /app
# Node.js required by Claude Code CLI; git required for repo checkout.
# GPG-based NodeSource installation (avoids curl-pipe-bash pattern).
# ca-certificates and gnupg are required for GPG key verification.
# curl is REQUIRED by the production HEALTHCHECK: do not remove.
RUN apt-get update && apt-get install -y --no-install-recommends \
curl git ca-certificates gnupg && \
mkdir -p /etc/apt/keyrings && \
curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key \
| gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg && \
echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_25.x nodistro main" \
> /etc/apt/sources.list.d/nodesource.list && \
apt-get update && \
apt-get install -y --no-install-recommends nodejs && \
rm -rf /var/lib/apt/lists/*
# Apply Debian security updates for OS packages baked into the oven/bun base
# image. The orchestrator stage never reinstalls them otherwise, so it shipped
# stale python3.13 / libsystemd0 / libnghttp2 / libcap2 / krb5 libs with HIGH CVEs flagged
# by the Trivy gate. This block lives in the shared base so both images carry
# the patched versions. Targeted --only-upgrade (not a blanket apt-get upgrade)
# keeps the build reproducible and avoids hadolint DL3005.
#
# The util-linux family (2.41-5 -> 2.41.5-0+deb13u1) covers CVE-2026-53612,
# CVE-2026-53613 and CVE-2026-53614. These nine are every util-linux binary
# package present in a debian:trixie rootfs, which oven/bun is built FROM (the
# source builds 33, the rest are not installed here). Keep the list complete:
# --only-upgrade silently IGNORES a name that is not installed and does not
# error on a name that is omitted, so dropping one leaves that package at the
# vulnerable version with no build-time signal and only Trivy notices later.
# `login` is here because trixie builds it from the util-linux source under a
# `+really` version suffix, keeping shadow's epoch so it still sorts above the
# old shadow-built package (1:4.16.0-2+really2.41.5-0+deb13u1).
RUN apt-get update && \
apt-get install --only-upgrade -y --no-install-recommends \
openssl libssl3 \
python3.13 python3.13-minimal libpython3.13-stdlib libpython3.13-minimal \
libsystemd0 libudev1 libnghttp2-14 libcap2 \
libgssapi-krb5-2 libk5crypto3 libkrb5-3 libkrb5support0 \
util-linux mount login bsdutils libblkid1 liblastlog2-2 libmount1 \
libsmartcols1 libuuid1 && \
rm -rf /var/lib/apt/lists/*
# Claude Code CLI required by @anthropic-ai/claude-agent-sdk. Declared as an
# ARG so renovate.json's custom regex manager keeps it current: the previous
# inline `@2.1.114` pin was invisible to every manager and drifted far enough
# behind to ship CVE-2026-55607 (arbitrary code execution via git directory
# confusion, fixed in 2.1.163).
#
# Value tracks the vendor's `stable` dist-tag, not `latest`/`next`. This CLI
# runs under bypassPermissions holding a GitHub installation token, so it takes
# the conservative channel. A hand-picked version must also clear the 7-day
# soak renovate.json enforces via minimumReleaseAge (that setting only governs
# Renovate-raised PRs, not a manual edit), and must be >= 2.1.163.
# renovate: datasource=npm depName=@anthropic-ai/claude-code
ARG CLAUDE_CODE_VERSION=2.1.236
# Tracks 11.x major (unlike the exact pins elsewhere).
# The version is validated before it reaches npm: `--build-arg
# CLAUDE_CODE_VERSION=` would otherwise expand to `...@`, which npm-package-arg
# resolves to the `*` range and silently installs whatever is newest, dropping
# the pin with no build failure.
RUN npm install -g npm@11 && \
printf '%s' "${CLAUDE_CODE_VERSION:?CLAUDE_CODE_VERSION must not be empty}" \
| grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+$' && \
npm install -g "@anthropic-ai/claude-code@${CLAUDE_CODE_VERSION}"
# Stage: Build: install all deps and bundle the main app.
FROM base AS development
ENV HUSKY=0
# Bun's bundler inlines `process.env.NODE_ENV` at build time and defaults to
# "development" when unset, which causes dist/app.js to require `pino-pretty`
# (a devDependency absent from production) via src/logger.ts.
ENV NODE_ENV=production
COPY package.json bun.lock ./
RUN bun install --frozen-lockfile
COPY . .
RUN bun run build
# Stage: Production deps only.
FROM base AS deps
COPY package.json bun.lock ./
# --ignore-scripts: husky is a devDependency: skip prepare/postinstall hooks.
RUN bun install --frozen-lockfile --production --ignore-scripts
# --- SHARED-BASE-END ---
# Stage: Orchestrator production image.
FROM base AS production
ARG PACKAGE_VERSION=untagged
ARG GIT_HASH=unspecified
# See Dockerfile.daemon for why CLAUDE_CODE_PATH is pinned. The orchestrator
# never executes the SDK pipeline, but keep parity to avoid surprise if a
# future code path imports the SDK in this image.
ENV CLAUDE_CODE_PATH=/usr/bin/claude
LABEL maintainer="Chris Lee"
LABEL com.chrisleekr.bot.package-version=${PACKAGE_VERSION}
LABEL com.chrisleekr.bot.git-hash=${GIT_HASH}
LABEL com.chrisleekr.bot.variant=orchestrator
# Bundled app and MCP stdio servers (dist/app.js, dist/mcp/servers/*.js)
COPY --from=development --chown=bun:bun /app/dist ./dist
COPY --from=development --chown=bun:bun /app/package.json ./
# SQL migration files: not bundled by Bun.build, copied as-is.
COPY --from=development --chown=bun:bun /app/src/db/migrations ./src/db/migrations
# Production node_modules (runtime dependencies only).
COPY --from=deps --chown=bun:bun /app/node_modules ./node_modules
USER bun
EXPOSE 3000/tcp
EXPOSE 3002/tcp
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD curl -f http://localhost:3000/healthz || exit 1
CMD ["bun", "run", "dist/app.js"]