-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathDockerfile
More file actions
131 lines (108 loc) · 5.09 KB
/
Copy pathDockerfile
File metadata and controls
131 lines (108 loc) · 5.09 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
# syntax=docker/dockerfile:1.7
# -----------------------------------------------------------------------------
# Stage 1: install production dependencies
# -----------------------------------------------------------------------------
FROM node:22-slim AS deps
WORKDIR /app
# Copy lockfile + manifest first for better layer caching
COPY package.json package-lock.json* ./
# Install production deps only. npm ci if a lockfile exists, npm install otherwise.
RUN if [ -f package-lock.json ]; then \
npm ci --omit=dev; \
else \
npm install --omit=dev; \
fi
# -----------------------------------------------------------------------------
# Stage 2: runtime image
# -----------------------------------------------------------------------------
FROM node:22-slim
# OCI metadata
LABEL org.opencontainers.image.title="Klebb" \
org.opencontainers.image.description="Manifest-driven self-hosted personal health dashboard" \
org.opencontainers.image.licenses="AGPL-3.0-only" \
org.opencontainers.image.source="https://github.com/Aristocles/klebb"
# Runtime system dependencies:
# ca-certificates : HTTPS trust anchors for outbound calls (chat gateway, Fish Audio)
# tini : proper PID 1 so SIGTERM reaches Node cleanly
# gosu : privilege drop from root -> klebb in the entrypoint
# ffmpeg : transcode browser voice-note audio to 16 kHz mono WAV for Fish ASR
# (also reused by the audio report extractor)
# poppler-utils : pdftotext for a PDF's text layer, plus pdftoppm and
# pdfinfo, which rasterise scanned (image-only) PDFs so
# tesseract can read them
# tesseract-ocr (+
# tesseract-ocr-eng) : OCR for uploaded images and for scanned-PDF pages
RUN apt-get update && apt-get install -y --no-install-recommends \
ca-certificates \
ffmpeg \
gosu \
poppler-utils \
tesseract-ocr \
tesseract-ocr-eng \
tini \
&& rm -rf /var/lib/apt/lists/*
# Non-root runtime user
RUN groupadd --system --gid 1001 klebb \
&& useradd --system --uid 1001 --gid klebb --home-dir /app --shell /usr/sbin/nologin klebb
WORKDIR /app
# Production node_modules from the deps stage
COPY --from=deps --chown=klebb:klebb /app/node_modules ./node_modules
# Application source — only what the server actually needs at runtime
COPY --chown=klebb:klebb package.json ./
COPY --chown=klebb:klebb server.js ./
COPY --chown=klebb:klebb auth ./auth
COPY --chown=klebb:klebb chat ./chat
COPY --chown=klebb:klebb config ./config
COPY --chown=klebb:klebb ingest ./ingest
COPY --chown=klebb:klebb lib ./lib
COPY --chown=klebb:klebb manifests ./manifests
COPY --chown=klebb:klebb meta ./meta
COPY --chown=klebb:klebb routes ./routes
COPY --chown=klebb:klebb public ./public
COPY --chown=klebb:klebb scripts ./scripts
COPY --chown=klebb:klebb voice ./voice
COPY --chown=klebb:klebb health-auto-export ./health-auto-export
COPY --chown=klebb:klebb server ./server
COPY --chown=klebb:klebb templates ./templates
COPY --chown=klebb:klebb prompts ./prompts
# Demo fixtures + reset script. Needed inside the image so the public
# demo's hourly reset cron can `docker exec klebb-demo node
# /app/scripts/reset-demo.js`. Both directories are public-safe; the
# reset script refuses to run unless KLEBB_DEMO=1 so it can never be
# invoked against a real instance.
COPY --chown=klebb:klebb demo ./demo
# Doc files served by the read_doc chat tool. The allowlist in
# chat/docs.js enumerates exactly these paths; an absent file would
# surface to the agent as ENOENT.
COPY --chown=klebb:klebb docs ./docs
COPY --chown=klebb:klebb README.md MANIFEST-SCHEMA.md CHANGELOG.md \
CONTRIBUTING.md CONTRIBUTING-PROMPTS.md CONTRIBUTING-TEMPLATES.md \
SECURITY.md ./
# Data dir — mount a volume here in production. The entrypoint chowns
# this to the runtime user at container start so bind-mounts owned by
# the host user Just Work on first boot.
RUN mkdir -p /data && chown klebb:klebb /data
# Entrypoint script — runs as root, fixes /data ownership, drops to klebb.
COPY docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh
RUN chmod 0755 /usr/local/bin/docker-entrypoint.sh
# The source revision this build was cut from (publish workflow passes the
# sha). Served by /api/admin/info; empty on a local build.
ARG SOURCE_COMMIT=
ENV SOURCE_COMMIT=${SOURCE_COMMIT}
# Runtime config
ENV NODE_ENV=production \
PORT=10002 \
HOST=0.0.0.0 \
HEALTH_HOME=/data \
TZ=UTC
EXPOSE 10002
# Operator contract — bind-mount persistent data here
VOLUME ["/data"]
STOPSIGNAL SIGTERM
# Dependency-free liveness probe
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
CMD node -e "fetch('http://127.0.0.1:' + (process.env.PORT || 10002) + '/healthz').then(r => { if (!r.ok) process.exit(1); }).catch(() => process.exit(1))"
# NOTE: no `USER klebb` directive here — the entrypoint runs as root so
# it can chown /data, then drops to klebb via gosu before exec'ing node.
ENTRYPOINT ["/usr/bin/tini", "--", "/usr/local/bin/docker-entrypoint.sh"]
CMD ["node", "server.js"]