1+ # syntax=docker/dockerfile:1
12# ═══════════════════════════════════════════════════════════════════════════
23# LOCALPIBOX DEVSTACK — MULTI-STAGE DOCKERFILE
34# ═══════════════════════════════════════════════════════════════════════════
@@ -29,6 +30,10 @@ ARG PI_FORK=https://github.com/localpibox/pi.git
2930ARG PI_REF=lpb
3031ARG PI_HEAD_SHA=unknown
3132
33+ # Provenance — set via --build-arg in CI, defaults to "unknown" for local builds
34+ ARG IMAGE_REVISION=unknown
35+ ARG IMAGE_BUILT=unknown
36+
3237# ═══════════════════════════════════════════════════════════════════════════
3338# BASE STAGE — Common setup for both cli and web images
3439# ═══════════════════════════════════════════════════════════════════════════
@@ -39,6 +44,8 @@ ARG VSCODIUM_VERSION
3944ARG PI_FORK
4045ARG PI_REF
4146ARG PI_HEAD_SHA
47+ ARG IMAGE_REVISION
48+ ARG IMAGE_BUILT
4249
4350ENV DEBIAN_FRONTEND=noninteractive
4451
@@ -48,6 +55,7 @@ ENV DEBIAN_FRONTEND=noninteractive
4855ENV LPB_MAX_TOKENS_CONTEXT_RATIO=0.06
4956ENV LPB_VERSION=
5057
58+ # ── System packages ─────────────────────────────────────────────────────────
5159RUN apt-get update && apt-get install -y --no-install-recommends \
5260 build-essential pkg-config \
5361 curl ca-certificates gnupg \
@@ -57,11 +65,13 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
5765 gh ripgrep fzf fd-find tmux \
5866 && rm -rf /var/lib/apt/lists/*
5967
68+ # ── Node.js ─────────────────────────────────────────────────────────────────
6069RUN curl -fsSL https://deb.nodesource.com/setup_${NODE_VERSION}.x | bash - \
6170 && apt-get install -y nodejs \
6271 && npm install -g npm@latest \
6372 && corepack enable
6473
74+ # ── User lpb (UID 1000) ────────────────────────────────────────────────────
6575RUN set -eux; \
6676 if getent passwd 1000 >/dev/null; then \
6777 oldname="$(getent passwd 1000 | cut -d: -f1)" ; \
@@ -79,7 +89,10 @@ RUN set -eux; \
7989
8090RUN echo '%sudo ALL=(ALL) NOPASSWD:ALL' > /etc/sudoers.d/nopasswd && chmod 440 /etc/sudoers.d/nopasswd
8191
82- RUN set -eux; \
92+ # ── Global npm packages ─────────────────────────────────────────────────────
93+ # Cache mounted — persists downloaded tarballs across builds.
94+ RUN --mount=type=cache,target=/home/lpb/.npm \
95+ set -eux; \
8396 npm config set prefix '/home/lpb/.npm-global' ; \
8497 npm config set fetch-retries 5; \
8598 npm config set fetch-retry-mintimeout 20000; \
@@ -88,12 +101,12 @@ RUN set -eux; \
88101 npm config set registry https://registry.npmjs.org/; \
89102 printf 'allow-scripts=better-sqlite3\n allow-scripts=agent-browser\n allow-scripts=esbuild\n allow-scripts=protobufjs\n allow-scripts=@google/genai\n ' > /home/lpb/.npmrc; \
90103 npm install -g zod@3 agent-browser exa-mcp-server; \
91- npm cache clean --force; \
92104 chown -R 1000:1000 /home/lpb/.npm-global
93105
94106# ── Pi monorepo build ───────────────────────────────────────────────────────
95- USER root
96- RUN set -eux; \
107+ # Cache mounted — persists node_modules across builds when Pi source is unchanged.
108+ RUN --mount=type=cache,target=/opt/pi-src/node_modules \
109+ set -eux; \
97110 export PATH="/home/lpb/.npm-global/bin:${PATH}" ; \
98111 mkdir -p /opt/pi-src && cd /opt/pi-src; \
99112 git clone --depth=1 --single-branch --branch ${PI_REF} ${PI_FORK} .; \
@@ -120,9 +133,11 @@ RUN set -eux; \
120133 ls -la /home/lpb/.npm-global/bin/; \
121134 rm -rf /opt/pi-src/.git /opt/pi-src/src /opt/pi-src/test /opt/pi-src/tests
122135
136+ # ── Switch to non-root user ─────────────────────────────────────────────────
137+ USER lpb
138+
123139# ── Extensions + Config ─────────────────────────────────────────────────────
124140# Config repo is cloned at container start by start.sh — the image stays lean.
125- USER lpb
126141
127142# ═══════════════════════════════════════════════════════════════════════════
128143# CLI IMAGE
@@ -147,37 +162,29 @@ COPY --chmod=755 support/start.sh /opt/devstack/start.sh
147162COPY lpb.conf.env /opt/devstack/lpb.conf.env
148163COPY --chmod=755 support/entrypoint-cli.sh /opt/devstack/entrypoint-cli.sh
149164
150- # Copy lpb-config to PATH (user-owned dir — no chmod issues)
165+ # ── Shell PATH helper ───────────────────────────────────────────────────────
151166RUN mkdir -p /home/lpb/.local/bin
152167COPY --chmod=755 support/lpb-config /home/lpb/.local/bin/lpb-config
153168
154- # ─── Ownership (must run as root — COPY creates files as root) ───────────
169+ # ── Root operations: ownership + gitconfig + shell PATH ────────── ───────────
155170USER root
156171RUN mkdir -p /home/lpb/.agent-browser/sessions \
157- && chown -R 1000:1000 /home/lpb /opt/devstack /opt/pi-support
158- USER lpb
172+ && chown -R 1000:1000 /home/lpb /opt/devstack /opt/pi-support \
173+ && printf '[credential "https://github.com"]\n helper = !gh auth git-credential\n [credential "https://gist.github.com"]\n helper = !gh auth git-credential\n ' > /home/lpb/.gitconfig \
174+ && chown 1000:1000 /home/lpb/.gitconfig \
175+ && printf '\n # LocalPibox: npm-global and local bin on PATH\n export PATH="/home/lpb/.npm-global/bin:/home/lpb/.local/bin:${PATH}"\n ' >> /home/lpb/.bashrc
159176
160- # ─── Git credential helper ────────────────────────────────────────────
161- # Points git to gh auth for GitHub HTTPS operations. No sensitive data
162- # baked in — the actual token is resolved at runtime from
163- # /home/lpb/.config/gh/hosts.yml (volume-mounted from host).
164- USER root
165- RUN printf '[credential "https://github.com"]\n helper = !gh auth git-credential\n [credential "https://gist.github.com"]\n helper = !gh auth git-credential\n ' > /home/lpb/.gitconfig && chown 1000:1000 /home/lpb/.gitconfig
166177USER lpb
167178
168- # ─── Shell PATH (npm global bin, local bin) ──────────────────────────
169- # Ensures agent-browser, pi, and other npm-global tools are on PATH
170- # for all interactive and non-interactive shells.
171- RUN printf '\n # LocalPibox: npm-global and local bin on PATH\n export PATH="/home/lpb/.npm-global/bin:/home/lpb/.local/bin:${PATH}"\n ' >> /home/lpb/.bashrc
172-
173- USER lpb
174179WORKDIR /home/lpb/workspace
175180
176181LABEL org.opencontainers.image.title="LocalPibox Devstack — CLI" \
177182 org.opencontainers.image.description="AI-powered dev environment with Pi CLI (interactive terminal)" \
178183 org.opencontainers.image.source="https://github.com/localpibox/devstack" \
179184 org.opencontainers.image.vendor="LocalPibox" \
180- org.opencontainers.image.licenses="MIT"
185+ org.opencontainers.image.licenses="MIT" \
186+ org.opencontainers.image.revision="${IMAGE_REVISION}" \
187+ org.opencontainers.image.created="${IMAGE_BUILT}"
181188
182189ENTRYPOINT ["/opt/devstack/entrypoint-cli.sh" ]
183190
@@ -195,7 +202,7 @@ RUN curl -fsSL \
195202 && tar -xzf /tmp/vscodium.tar.gz -C /opt/vscodium --strip-components=1 \
196203 && rm /tmp/vscodium.tar.gz
197204
198- USER root
205+ # ── VSCodium extensions ─────────────────────────────────────────────────────
199206RUN set -eux; \
200207 export PATH="/home/lpb/.npm-global/bin:${PATH}" ; \
201208 EXT_DIR="/home/lpb/.vscodium-server/extensions" ; \
@@ -224,6 +231,7 @@ COPY --chmod=755 support/entrypoint-web.sh /opt/devstack/entrypoint-web.sh
224231RUN chown -R 1000:1000 /home/lpb
225232
226233USER lpb
234+
227235WORKDIR /home/lpb/workspace
228236
229237ENV ED_PORT=3000
@@ -240,6 +248,8 @@ LABEL org.opencontainers.image.title="LocalPibox Devstack — Web" \
240248 org.opencontainers.image.description="AI-powered dev environment with VSCodium web editor" \
241249 org.opencontainers.image.source="https://github.com/localpibox/devstack" \
242250 org.opencontainers.image.vendor="LocalPibox" \
243- org.opencontainers.image.licenses="MIT"
251+ org.opencontainers.image.licenses="MIT" \
252+ org.opencontainers.image.revision="${IMAGE_REVISION}" \
253+ org.opencontainers.image.created="${IMAGE_BUILT}"
244254
245255ENTRYPOINT ["/opt/devstack/entrypoint-web.sh" ]
0 commit comments