-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathDockerfile
More file actions
79 lines (61 loc) · 3.26 KB
/
Dockerfile
File metadata and controls
79 lines (61 loc) · 3.26 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
# Stage 1 — Install dependencies (including native addons)
FROM node:24-bookworm AS deps
RUN corepack enable pnpm
WORKDIR /app
# Build tools for native addons (better-sqlite3, ssh2, cbor-extract, cpu-features)
RUN apt-get update && apt-get install -y --no-install-recommends \
python3 make g++ libssl-dev \
&& rm -rf /var/lib/apt/lists/*
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./
RUN pnpm install --frozen-lockfile
# Stage 2 — Build (tsc + SvelteKit static adapter)
FROM deps AS build
# Build identity — supplied by CI via --build-arg. Defaults are dev fallbacks
# so a manual `docker build` without args still produces a working image.
# Note: tag is intentionally NOT baked here — it's stamped onto release images
# at retag time via `crane mutate --set-env GIT_TAG=...` (see release.yml).
ARG GIT_SHA=dev
ARG GIT_REF=local
COPY tsconfig.json ./
COPY src/ src/
COPY client/ client/
COPY drizzle/ drizzle/
COPY scripts/ scripts/
# Bake build identity into the runtime — read by src/server/buildInfo.ts.
# ARGs are passed through explicitly via shell substitution so this works
# under both BuildKit and the classic builder (BuildKit auto-exposes ARGs to
# RUN env, classic does not — silent fallback to "dev" otherwise).
RUN GIT_SHA="$GIT_SHA" GIT_REF="$GIT_REF" \
node -e "const fs=require('fs');fs.writeFileSync('buildInfo.generated.json',JSON.stringify({sha:process.env.GIT_SHA||'dev',ref:process.env.GIT_REF||'local',builtAt:new Date().toISOString()}));"
RUN pnpm build
# Generate THIRD_PARTY_LICENSES for inclusion in the runtime image.
RUN pnpm licenses:bundle
RUN pnpm prune --prod --ignore-scripts
# Stage 3 — Runtime
FROM node:24-bookworm-slim AS runtime
LABEL org.opencontainers.image.source="https://github.com/rado0x54/ShellWatch"
LABEL org.opencontainers.image.description="SSH session broker with browser UI and MCP interface"
LABEL org.opencontainers.image.licenses="LicenseRef-FSL-1.1-Apache-2.0"
# Replace the baked-in `node` user with a named `shellwatch` user pinned to
# UID/GID 1000 so bind-mounted volumes owned by the typical host user (1000)
# work out of the box. Override at runtime with `--user UID:GID` / compose
# `user:` if your host uses a different UID.
RUN userdel node 2>/dev/null || true; \
groupdel node 2>/dev/null || true; \
groupadd --gid 1000 shellwatch && \
useradd --uid 1000 --gid shellwatch --no-create-home shellwatch
WORKDIR /app
COPY --from=build --chown=shellwatch:shellwatch /app/dist/ dist/
COPY --from=build --chown=shellwatch:shellwatch /app/node_modules/ node_modules/
COPY --from=build --chown=shellwatch:shellwatch /app/drizzle/ drizzle/
COPY --from=build --chown=shellwatch:shellwatch /app/package.json package.json
COPY --from=build --chown=shellwatch:shellwatch /app/buildInfo.generated.json buildInfo.generated.json
COPY --chown=shellwatch:shellwatch LICENSE LICENSE
COPY --from=build --chown=shellwatch:shellwatch /app/THIRD_PARTY_LICENSES THIRD_PARTY_LICENSES
RUN install -d -o shellwatch -g shellwatch /app/data /app/keys
VOLUME ["/app/data", "/app/keys"]
EXPOSE 3000
USER shellwatch
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
CMD node -e "fetch('http://localhost:3000/health').then(r=>{if(!r.ok)throw r.status}).catch(()=>process.exit(1))"
CMD ["node", "dist/index.js"]