Repository navigation
Expand file tree
/
Copy pathDockerfile
More file actions
51 lines (50 loc) · 2.62 KB
/
Copy pathDockerfile
File metadata and controls
51 lines (50 loc) · 2.62 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
# Build stage
# trixie (not bookworm): better-sqlite3 v13's linux prebuilds need glibc >= 2.38.
FROM node:24-trixie-slim AS build
WORKDIR /app
RUN corepack enable && apt-get update && apt-get install -y --no-install-recommends python3 make g++ && rm -rf /var/lib/apt/lists/*
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./
RUN pnpm install --frozen-lockfile
COPY . .
RUN pnpm build
# Runtime stage
FROM node:24-trixie-slim
WORKDIR /app
# The ob CLI is spawned as a child process (not bundled by the server build),
# so install it globally in the runtime image. Keep the version pinned in
# lockstep with package.json.
# git + git-lfs are for the git sync backend. LFS matters even though notemesh
# never writes binaries: cloning an LFS-backed vault without it yields ~130-byte
# pointer files in place of every attachment, which contain no NUL bytes and so
# read as text — the server would hand those to a model as if they were images.
RUN apt-get update \
&& apt-get install -y --no-install-recommends git git-lfs ca-certificates tini \
&& rm -rf /var/lib/apt/lists/* \
&& git lfs install --system
RUN npm install -g obsidian-headless@0.0.14
COPY --from=build /app/.output ./.output
COPY docker-entrypoint.sh /app/docker-entrypoint.sh
# The server runs as the image's unprivileged `node` user (see the
# entrypoint), so a compromise of the process — the class of thing the
# frontmatter parser fix in 1.2.0 closed — does not hand out root in the
# container. /app stays owned by root and read-only to node; only DATA_DIR
# is writable. HOME is node's own, not root's: git reads ~/.gitconfig and
# would refuse to run against a home it cannot open.
ENV NODE_ENV=production \
PORT=3000 \
DATA_DIR=/data \
HOME=/home/node
EXPOSE 3000
# tini as PID 1, node as its child. PID 1 is expected to reap orphaned
# processes; node only ever waits on its own direct children. This server
# spawns a lot of children — the ob sync daemon, every git call under a 120 s
# timeout — and kills them individually, so their grandchildren (git-remote-https,
# LFS filters, the sh credential helper) are orphaned onto PID 1 whenever a
# parent is killed mid-flight. Under node they were never reaped: one zombie per
# timeout, accumulating for the life of the container until PID exhaustion
# stopped spawn() and sync with it. Docker's --init does the same thing but is
# a runtime flag Railway does not expose, so it is baked in.
# The entrypoint runs as root under tini only long enough to make DATA_DIR
# writable by node, then drops privileges for the command.
ENTRYPOINT ["tini", "--", "/app/docker-entrypoint.sh"]
CMD ["node", ".output/server/index.mjs"]