Repository navigation
Expand file tree
/
Copy pathDockerfile.base
More file actions
144 lines (129 loc) · 6.48 KB
/
Copy pathDockerfile.base
File metadata and controls
144 lines (129 loc) · 6.48 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
# syntax=docker/dockerfile:1.7
# Environment image: OS toolchain, language runtimes, global CLIs, npm deps.
# Rebuild with `scripts/build-image.sh --base` when this file, package-lock, or CLI pins change.
FROM node:22-trixie-slim
ARG GO_VERSION=1.27.0
ARG TARGETARCH
# build-essential + python3: community dsh plugins (e.g. dsh-better-sidebar →
# node-pty) have no linux prebuilds and compile via node-gyp at profile install
# time. The cluster forbids root pods, so the toolchain must live in the image
# and the boot-time `npm install` runs as uid 1000 with build scripts enabled.
# ffmpeg/ffprobe: on PATH for agent skills and ffmpeg_encode (Debian package).
#
# The rest is what a coding agent reaches for and node:22-trixie-slim does not
# have: inspection, archives, transfer, and in-cluster network diagnosis (the
# olares-doctor skill asks whether a service answers, from inside the pod).
# Debian's `yq` is the jq wrapper, not mikefarah's Go tool -- the filter syntax
# is jq's and YAML output needs -y.
# pkg-config / libssl-dev: native crates and node-gyp modules that link OpenSSL.
RUN apt-get update \
&& DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
ca-certificates curl fd-find ffmpeg git jq ripgrep tini \
build-essential pkg-config libssl-dev \
file less procps tree \
unzip zip xz-utils zstd \
openssh-client patch rsync \
dnsutils iproute2 iputils-ping netcat-openbsd socat \
shellcheck tzdata yq \
&& ln -sf "$(command -v fdfind)" /usr/local/bin/fd \
&& rm -rf /var/lib/apt/lists/*
# gh has no Debian package; upstream's apt repo is the only maintained route.
# The arch in the sources line comes from dpkg so both build legs resolve.
RUN install -m 0755 -d /etc/apt/keyrings \
&& curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg \
-o /etc/apt/keyrings/githubcli-archive-keyring.gpg \
&& chmod go+r /etc/apt/keyrings/githubcli-archive-keyring.gpg \
&& echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/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 --no-install-recommends gh \
&& rm -rf /var/lib/apt/lists/* \
&& gh --version
# Debian marks the system Python externally managed (PEP 668), so pip refuses
# to install anything without this -- at build time below, and at runtime for
# whoever is holding the shell.
ENV PIP_BREAK_SYSTEM_PACKAGES=1
# Python development environment on the distro interpreter (Trixie: 3.13) --
# the same one node-gyp uses, so there is no second runtime to keep current.
#
# The science stack is here because two olares-publish skill scripts need it:
# generate_icon.py imports PIL, numpy and scipy (cairosvg too, for SVG input)
# and render_promo.py loads a font by absolute path. fonts-dejavu-core is that
# font -- a slim node image carries none at all, so without it the script fails.
# ffmpeg_encode burns translated subtitles, so it also needs a CJK font rather
# than silently rendering Chinese as tofu.
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
python3-dev python3-pip python3-venv python-is-python3 \
python3-numpy python3-scipy python3-pil python3-cairosvg \
fonts-dejavu-core fonts-noto-cjk \
&& ln -sf "$(command -v pip3)" /usr/local/bin/pip \
&& rm -rf /var/lib/apt/lists/* \
&& pip install --no-cache-dir uv==0.12.5 \
&& uv --version \
&& python3 -c "import PIL, numpy, scipy, cairosvg"
# Go/Rust under /usr/local. HOME=/data/home is a volume at runtime, so a
# user-local rustup/gopath from the build would disappear after mount.
RUN curl -fsSL "https://go.dev/dl/go${GO_VERSION}.linux-${TARGETARCH}.tar.gz" \
| tar -C /usr/local -xzf -
ENV RUSTUP_HOME=/usr/local/rustup \
CARGO_HOME=/usr/local/cargo
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs \
| sh -s -- -y --no-modify-path --profile default --default-toolchain stable \
&& chmod -R a+rwX "$RUSTUP_HOME" "$CARGO_HOME"
WORKDIR /app
# PATH: the pod runs as uid 1000 and cannot write /usr, so `pip install --user`
# is the only route open to it. That lands in $HOME/.local, which is on the PVC
# and therefore survives a restart -- but its bin directory is not on PATH by
# default, so an installed command would be unreachable.
# GOPATH stays on the PVC; the toolchain itself is in the image.
ENV PORT=8080 \
HOSTNAME=0.0.0.0 \
PATH=/data/home/.local/bin:/usr/local/go/bin:/usr/local/cargo/bin:$PATH \
HOME=/data/home \
LARES_WORKSPACE=/data/workspace \
LARES_DATA_DIR=/data/lares \
LLM_GATEWAY_URL=http://router-svc.router-shared/v1 \
OLARES_APP_ID=lares \
GOPATH=/data/home/go \
GOTOOLCHAIN=local
# Debian's /etc/profile assigns PATH outright rather than appending to it, so a
# login shell throws away the entry above -- and whether an agent's shell is a
# login shell is not ours to decide. profile.d runs after that assignment.
RUN printf 'PATH=/data/home/.local/bin:/usr/local/go/bin:/usr/local/cargo/bin:$PATH\n' \
> /etc/profile.d/10-lares-user-bin.sh
USER root
# The olares-* agent skills come out of this binary (app image runs
# `olares-cli skills export`), so a pin without the suite has to fail here
# rather than one layer later.
RUN npm install -g @olares/cli@1.12.7-cli.12 \
&& ln -sf "$(npm root -g)/@olares/cli/bin/olares-cli.js" /usr/local/bin/olares-cli \
&& olares-cli -v \
&& olares-cli skills list -q \
&& python3 --version \
&& pip --version \
&& go version \
&& rustc --version \
&& cargo --version
COPY package.json package-lock.json ./
# Ownership is settled here, while /app holds two json files, rather than after
# npm ci. A recursive chown over a finished node_modules makes overlayfs copy up
# every file in it to change one metadata field, and that cost 450 MB.
RUN mkdir -p /data/home /data/lares /data/workspace \
&& chown -R node:node /data /app
USER node
# Keep devDependencies (tsc / esbuild) so the app image can compile without a
# second npm ci. Installing as node is also what actually happens at boot, when
# a community dsh plugin compiles against this same tree as uid 1000.
#
# The cache has to go in the same layer it was made in: npm puts it under $HOME,
# which is /data, and VOLUME below does not retroactively discard what a build
# wrote there -- it was 74 MB of the image.
RUN npm ci --include=dev \
&& npm cache clean --force \
&& rm -rf "$HOME/.cache"
ENV NODE_ENV=production
VOLUME ["/data"]
EXPOSE 8080
ENTRYPOINT ["/usr/bin/tini", "--"]
CMD ["npm", "run", "start"]