-
-
Notifications
You must be signed in to change notification settings - Fork 224
Expand file tree
/
Copy pathDockerfile
More file actions
270 lines (233 loc) · 13.2 KB
/
Dockerfile
File metadata and controls
270 lines (233 loc) · 13.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
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
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
# ==============================================================================
# HolyClaude — Pre-configured Docker Environment for Claude Code CLI + CloudCLI
# https://github.com/coderluii/holyclaude
#
# Build variants:
# docker build -t holyclaude . # full (default)
# docker build --build-arg VARIANT=slim -t holyclaude:slim .
# ==============================================================================
FROM node:22-bookworm-slim
LABEL org.opencontainers.image.source=https://github.com/CoderLuii/HolyClaude
# ---------- Build args ----------
ARG S6_OVERLAY_VERSION=3.2.0.2
ARG TARGETARCH
ARG VARIANT=full
# ---------- Environment ----------
ENV DEBIAN_FRONTEND=noninteractive \
LANG=en_US.UTF-8 \
LC_ALL=en_US.UTF-8 \
DISPLAY=:99 \
DBUS_SESSION_BUS_ADDRESS=disabled: \
CHROMIUM_FLAGS="--no-sandbox --disable-gpu --disable-dev-shm-usage" \
CHROME_PATH=/usr/bin/chromium \
PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium
# ---------- s6-overlay v3 (multi-arch) ----------
RUN apt-get update && apt-get install -y --no-install-recommends xz-utils curl ca-certificates && rm -rf /var/lib/apt/lists/*
ADD https://github.com/just-containers/s6-overlay/releases/download/v${S6_OVERLAY_VERSION}/s6-overlay-noarch.tar.xz /tmp/
RUN S6_ARCH=$(case "$TARGETARCH" in arm64) echo "aarch64";; *) echo "x86_64";; esac) && \
curl -fsSL -o /tmp/s6-overlay-arch.tar.xz \
"https://github.com/just-containers/s6-overlay/releases/download/v${S6_OVERLAY_VERSION}/s6-overlay-${S6_ARCH}.tar.xz" && \
tar -C / -Jxpf /tmp/s6-overlay-noarch.tar.xz && \
tar -C / -Jxpf /tmp/s6-overlay-arch.tar.xz && \
rm /tmp/s6-overlay-*.tar.xz
# ---------- System packages (always installed) ----------
RUN apt-get update && apt-get install -y --no-install-recommends \
# Core utilities
git curl wget jq ripgrep fd-find unzip zip tree tmux fzf bat bubblewrap \
# Build tools
build-essential pkg-config python3 python3-pip python3-venv \
# Browser (Playwright/Puppeteer)
chromium \
# Fonts
fonts-liberation2 fonts-dejavu-core fonts-noto-core fonts-noto-color-emoji fonts-inter \
# Locale support
locales \
# Debugging tools
strace lsof iproute2 procps htop \
# Database CLI tools
postgresql-client redis-tools sqlite3 \
# SSH client (NOT server)
openssh-client \
# Xvfb for headless Chrome
xvfb \
# Image processing
imagemagick \
# Sudo
sudo \
&& rm -rf /var/lib/apt/lists/*
# ---------- bubblewrap setuid (Codex CLI sandbox on restricted kernels) ----------
RUN chmod u+s /usr/bin/bwrap
# ---------- Full-only system packages ----------
RUN if [ "$VARIANT" = "full" ]; then \
apt-get update && apt-get install -y --no-install-recommends \
pandoc ffmpeg libvips-dev \
&& rm -rf /var/lib/apt/lists/*; \
fi
# ---------- Azure CLI (full only) ----------
RUN if [ "$VARIANT" = "full" ]; then \
curl -sL https://aka.ms/InstallAzureCLIDeb | bash \
&& rm -rf /var/lib/apt/lists/*; \
fi
# ---------- GitHub CLI ----------
RUN curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg \
| dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg 2>/dev/null && \
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/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 gh && rm -rf /var/lib/apt/lists/*
# ---------- bat symlink (Debian names it batcat) ----------
RUN ln -sf /usr/bin/batcat /usr/local/bin/bat 2>/dev/null || true
# ---------- Locale configuration ----------
RUN sed -i '/en_US.UTF-8/s/^# //g' /etc/locale.gen && locale-gen
# ---------- Create claude user ----------
# node:22-bookworm-slim already has UID 1000 as 'node' — rename it to 'claude'
RUN usermod -l claude -d /home/claude -m node && \
groupmod -n claude node && \
echo "claude ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/claude && \
chmod 0440 /etc/sudoers.d/claude
# ---------- Claude Code CLI (native installer) ----------
# CRITICAL: WORKDIR must be non-root-owned or the installer hangs
WORKDIR /workspace
USER claude
RUN curl -fsSL https://claude.ai/install.sh | bash
USER root
ENV PATH="/home/claude/.local/bin:${PATH}"
# ---------- npm global packages (slim — always installed) ----------
RUN npm i -g \
typescript tsx \
pnpm \
vite esbuild \
eslint prettier \
serve nodemon concurrently \
dotenv-cli
# ---------- npm global packages (full only) ----------
RUN if [ "$VARIANT" = "full" ]; then \
npm i -g \
wrangler vercel netlify-cli \
pm2 \
prisma drizzle-kit \
eas-cli \
lighthouse @lhci/cli \
sharp-cli json-server http-server \
@marp-team/marp-cli @cloudflare/next-on-pages; \
fi
# ---------- Python packages (slim — always installed) ----------
RUN pip install --no-cache-dir --break-system-packages \
requests httpx beautifulsoup4 lxml \
Pillow \
pandas numpy \
openpyxl python-docx \
jinja2 pyyaml python-dotenv markdown \
rich click tqdm \
playwright \
apprise
# ---------- Python packages (full only) ----------
RUN if [ "$VARIANT" = "full" ]; then \
pip install --no-cache-dir --break-system-packages \
reportlab weasyprint cairosvg fpdf2 PyMuPDF pdfkit img2pdf \
xlsxwriter xlrd \
matplotlib seaborn \
python-pptx \
fastapi uvicorn \
httpie; \
fi
# ---------- AI CLI providers ----------
RUN npm i -g @google/gemini-cli @openai/codex task-master-ai
USER claude
RUN curl -fsSL https://cursor.com/install | bash
USER root
# ---------- Junie CLI (full only) ----------
USER claude
RUN if [ "$VARIANT" = "full" ]; then \
curl -fsSL https://junie.jetbrains.com/install.sh | bash; \
fi
USER root
# ---------- OpenCode CLI (full only) ----------
RUN if [ "$VARIANT" = "full" ]; then \
npm i -g opencode-ai; \
fi
COPY vendor/artifacts/siteboon-claude-code-ui-1.26.3.tgz /tmp/vendor/siteboon-claude-code-ui-1.26.3.tgz
# ---------- CloudCLI (web UI for Claude Code) ----------
RUN npm i -g /tmp/vendor/siteboon-claude-code-ui-1.26.3.tgz && rm -f /tmp/vendor/siteboon-claude-code-ui-1.26.3.tgz
RUN touch /usr/local/lib/node_modules/@siteboon/claude-code-ui/.env
# ---------- Patch: preserve WebSocket frame type in plugin proxy (Issue #11) ----------
RUN CLOUDCLI_INDEX="/usr/local/lib/node_modules/@siteboon/claude-code-ui/server/index.js" && \
grep -q "upstream.on('message', (data) =>" "$CLOUDCLI_INDEX" && \
sed -i "s/upstream.on('message', (data) => {/upstream.on('message', (data, isBinary) => {/" "$CLOUDCLI_INDEX" && \
sed -i "s/if (clientWs.readyState === WebSocket.OPEN) clientWs.send(data)/if (clientWs.readyState === WebSocket.OPEN) clientWs.send(data, { binary: isBinary })/" "$CLOUDCLI_INDEX" && \
sed -i "s/clientWs.on('message', (data) => {/clientWs.on('message', (data, isBinary) => {/" "$CLOUDCLI_INDEX" && \
sed -i "s/if (upstream.readyState === WebSocket.OPEN) upstream.send(data)/if (upstream.readyState === WebSocket.OPEN) upstream.send(data, { binary: isBinary })/" "$CLOUDCLI_INDEX" && \
echo "[patch] WebSocket frame type fix applied (both directions)" || \
echo "[patch] WARNING: WebSocket pattern not found in vendored CloudCLI install, skipping patch"
# patch: preserve Shell tab scroll position across periodic refresh (issue #35)
RUN CLOUDCLI_BUNDLE="/usr/local/lib/node_modules/@siteboon/claude-code-ui/dist/assets/index-X3ImjnMV.js" && \
grep -q 'const B=()=>{v.current?.focus()}' "$CLOUDCLI_BUNDLE" && \
perl -pi -e 's/const B=\(\)=>\{v\.current\?\.focus\(\)\}/const B=()=>{const _vp=v.current?.buffer?.active?.viewportY??0;v.current?.focus();v.current?.scrollToLine(_vp)}/g' "$CLOUDCLI_BUNDLE" && \
echo "[patch] Shell scroll position fix applied" || \
echo "[patch] WARNING: Shell scroll pattern not found in vendored CloudCLI bundle, skipping patch"
# patch v1.2.2-1: commands.js expose newModel in spawn args (issue #36)
RUN CLOUDCLI_COMMANDS="/usr/local/lib/node_modules/@siteboon/claude-code-ui/server/routes/commands.js" && \
grep -q 'message: args.length > 0' "$CLOUDCLI_COMMANDS" && \
perl -pi -e 's/^(\s+)(message: args\.length > 0)/$1newModel: args.length > 0 ? args[0] : null,\n$1$2/' "$CLOUDCLI_COMMANDS" && \
echo "[patch] commands.js newModel field added" || \
echo "[patch] WARNING: commands.js newModel pattern not found, skipping patch"
# patch v1.2.2-2: bundle expose setClaudeModel in claudeModel context spread (issue #36)
RUN CLOUDCLI_BUNDLE="/usr/local/lib/node_modules/@siteboon/claude-code-ui/dist/assets/index-X3ImjnMV.js" && \
grep -q 'claudeModel:W,codexModel:V' "$CLOUDCLI_BUNDLE" && \
perl -pi -e 's/\QclaudeModel:W,codexModel:V\E/claudeModel:W,setClaudeModel:L,codexModel:V/g' "$CLOUDCLI_BUNDLE" && \
echo "[patch] bundle setClaudeModel context spread applied" || \
echo "[patch] WARNING: bundle claudeModel:W pattern not found, skipping patch"
# patch v1.2.2-3: bundle wire setClaudeModel:lS2 into cursorModel destructure (issue #36)
RUN CLOUDCLI_BUNDLE="/usr/local/lib/node_modules/@siteboon/claude-code-ui/dist/assets/index-X3ImjnMV.js" && \
grep -q 'cursorModel:o,claudeModel:l,codexModel:c' "$CLOUDCLI_BUNDLE" && \
perl -pi -e 's/\QcursorModel:o,claudeModel:l,codexModel:c\E/cursorModel:o,claudeModel:l,setClaudeModel:lS2,codexModel:c/g' "$CLOUDCLI_BUNDLE" && \
echo "[patch] bundle setClaudeModel:lS2 destructure applied" || \
echo "[patch] WARNING: bundle cursorModel destructure pattern not found, skipping patch"
# patch v1.2.2-4: bundle apply newModel on SSE model event (issue #36)
RUN CLOUDCLI_BUNDLE="/usr/local/lib/node_modules/@siteboon/claude-code-ui/dist/assets/index-X3ImjnMV.js" && \
grep -q 'case"model":k({type:"assistant"' "$CLOUDCLI_BUNDLE" && \
perl -pi -e 's/\Qcase"model":k({type:"assistant"\E/case"model":me.newModel\&\&lS2\&\&(lS2(me.newModel),localStorage.setItem("claude-model",me.newModel));k({type:"assistant"/g' "$CLOUDCLI_BUNDLE" && \
echo "[patch] bundle SSE model event handler applied" || \
echo "[patch] WARNING: bundle case\"model\" pattern not found, skipping patch"
# patch v1.2.2-5: bundle add custom model option to select (issue #36)
RUN CLOUDCLI_BUNDLE="/usr/local/lib/node_modules/@siteboon/claude-code-ui/dist/assets/index-X3ImjnMV.js" && \
grep -q 'children:N.OPTIONS.map(({value:C,label:j})=>s.jsx("option",{value:C,children:j},C+j))}' "$CLOUDCLI_BUNDLE" && \
perl -pi -e 's/\Qchildren:N.OPTIONS.map(({value:C,label:j})=>s.jsx("option",{value:C,children:j},C+j))}\E/children:[...N.OPTIONS.map(({value:C,label:j})=>s.jsx("option",{value:C,children:j},C+j)),!N.OPTIONS.some(C=>C.value===k)\&\&k\&\&s.jsx("option",{value:k,children:k},k+"custom")].filter(Boolean)}/g' "$CLOUDCLI_BUNDLE" && \
echo "[patch] bundle custom model select option applied" || \
echo "[patch] WARNING: bundle custom model select pattern not found, skipping patch"
# ---------- CloudCLI plugins (baked into image) ----------
USER claude
RUN mkdir -p /home/claude/.claude-code-ui/plugins && \
git clone --depth 1 https://github.com/cloudcli-ai/cloudcli-plugin-starter.git /home/claude/.claude-code-ui/plugins/project-stats && \
cd /home/claude/.claude-code-ui/plugins/project-stats && npm install && npm run build && \
git clone --depth 1 https://github.com/cloudcli-ai/cloudcli-plugin-terminal.git /home/claude/.claude-code-ui/plugins/web-terminal && \
cd /home/claude/.claude-code-ui/plugins/web-terminal && npm install && npm run build && \
echo '{"project-stats":{"name":"project-stats","source":"https://github.com/cloudcli-ai/cloudcli-plugin-starter","enabled":true},"web-terminal":{"name":"web-terminal","source":"https://github.com/cloudcli-ai/cloudcli-plugin-terminal","enabled":true}}' > /home/claude/.claude-code-ui/plugins.json
USER root
# ---------- Store variant for bootstrap ----------
RUN echo "${VARIANT}" > /etc/holyclaude-variant
# ---------- Copy config files ----------
COPY scripts/entrypoint.sh /usr/local/bin/entrypoint.sh
COPY scripts/bootstrap.sh /usr/local/bin/bootstrap.sh
COPY scripts/notify.py /usr/local/bin/notify.py
COPY config/settings.json /usr/local/share/holyclaude/settings.json
COPY config/claude-memory-full.md /usr/local/share/holyclaude/claude-memory-full.md
COPY config/claude-memory-slim.md /usr/local/share/holyclaude/claude-memory-slim.md
RUN chmod +x /usr/local/bin/entrypoint.sh \
/usr/local/bin/bootstrap.sh \
/usr/local/bin/notify.py
# ---------- s6-overlay service definitions ----------
COPY s6-overlay/s6-rc.d/cloudcli/type /etc/s6-overlay/s6-rc.d/cloudcli/type
COPY s6-overlay/s6-rc.d/cloudcli/run /etc/s6-overlay/s6-rc.d/cloudcli/run
COPY s6-overlay/s6-rc.d/xvfb/type /etc/s6-overlay/s6-rc.d/xvfb/type
COPY s6-overlay/s6-rc.d/xvfb/run /etc/s6-overlay/s6-rc.d/xvfb/run
RUN chmod +x /etc/s6-overlay/s6-rc.d/cloudcli/run \
/etc/s6-overlay/s6-rc.d/xvfb/run && \
touch /etc/s6-overlay/s6-rc.d/user/contents.d/cloudcli && \
touch /etc/s6-overlay/s6-rc.d/user/contents.d/xvfb
# ---------- Working directory ----------
WORKDIR /workspace
# ---------- Health check ----------
HEALTHCHECK --interval=30s --timeout=5s --start-period=30s --retries=3 \
CMD curl -sf http://localhost:3001/ || exit 1
# ---------- s6-overlay as PID 1 ----------
ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]