Files
pad/Dockerfile
T
xarmian 7c9f29b2f3 fix(ci): Go toolchain 1.26.10 (GO-2026-6809) and markdown-it 14.3.3 (TASK-4007) (#2267)
* fix(ci): Go toolchain 1.26.10 for GO-2026-6809, markdown-it 14.3.3 for npm audit (TASK-4007)

Main CI is red on both gates.
- govulncheck: GO-2026-6809 (crypto/x509) is fixed in go1.26.10. The
  go.mod toolchain line and the Dockerfile's golang image move to
  1.26.10. The workflow comments that name the toolchain value follow
  it, and go.mod's comment records the raise.
- Nix: its artifact is built with nixos-26.05's go, which predates
  1.26.10, so GO-2026-6809 joins nix/accepted-advisories.txt the way the
  1.26.9 block did, with the same removal condition.
- npm audit: markdown-it up to 14.3.2 has two high quadratic-time DoS
  advisories (GHSA-vfgh-jcmw-g37q, GHSA-gfr2-rv44-r23c). The overrides
  pin goes ^14.3.0 -> ^14.3.3; the lock moves markdown-it 14.3.2 ->
  14.3.3 and nothing else.

Claude-Session: https://claude.ai/code/session_01JRXHP9ZVfEN3YEuzmucxP3

* ci(deps): allow markdown-it 14.3.3 under the 7-day age check, a security fix (TASK-4007)

Dependency age (advisory) flagged only markdown-it@14.3.3, published
2026-10-16: the fix for GHSA-vfgh-jcmw-g37q and GHSA-gfr2-rv44-r23c.
pad-merge-pr requires every check at the head to be SUCCESS, so the
advisory red blocked the merge. A security fix cannot wait out the
window. The allow line is the file's own remedy for this case; its
removal after the version ages past 7 days is a filed follow-up.

Claude-Session: https://claude.ai/code/session_01JRXHP9ZVfEN3YEuzmucxP3
2026-10-21 00:07:12 -04:00

98 lines
4.2 KiB
Docker

# Stage 1: Build web UI
FROM node:26-alpine AS web-builder
WORKDIR /app/web
COPY web/package.json web/package-lock.json ./
RUN npm ci
COPY web/ ./
RUN npm run build
# Stage 2: Build Go binary
# Pinned to the toolchain go.mod names, so a self-built image ships the same
# standard library as the release binaries (govulncheck, 2026-10-08).
FROM golang:1.26.10-alpine AS go-builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
COPY --from=web-builder /app/web/build ./web/build
COPY --from=web-builder /app/web/build-materializer ./web/build-materializer
# Build metadata. All three are caller-passed via --build-arg (see
# pad-cloud/scripts/build-pad.sh for the production wrapper that
# resolves them from the host's pad checkout).
#
# Why all three are passed in vs. computed inside the container:
#
# - .dockerignore intentionally excludes .git/, so an in-container
# `git rev-parse` substitution returns empty (with `2>/dev/null`
# swallowing the error) — the previous Dockerfile shipped "dev"
# forever because of this. We don't want to add .git/ to the
# context just for this; pre-computing on the host is the
# standard pattern.
# - `date` would work in-container but a fresh `date` value on
# every build invalidates layer caching for this RUN. Passing
# as ARG lets the caller decide cache semantics.
#
# Defaults are deliberately ugly-but-honest so a `docker build .`
# without args produces a binary whose pad_version makes the
# misconfiguration obvious ("dev (unknown)") rather than hiding it.
ARG VERSION=dev
ARG COMMIT=unknown
ARG BUILD_TIME=
RUN CGO_ENABLED=0 go build \
-ldflags="-s -w -X main.version=${VERSION} -X main.commit=${COMMIT} -X main.buildTime=${BUILD_TIME}" \
-o pad ./cmd/pad
# Stage 3: Runtime
FROM alpine:3.24
# ca-certificates: TLS roots for outbound HTTPS (e.g. Maileroo email).
# tzdata: timezone names for Go's time package.
# shadow: provides usermod / groupmod (BusyBox's adduser/addgroup don't
# ship modify equivalents — needed by docker-entrypoint.sh).
# su-exec: lightweight alpine equivalent of gosu — execs the target in
# the same process so SIGTERM propagates correctly. Used by the
# entrypoint shim AND by the conditional healthcheck below.
RUN apk add --no-cache ca-certificates tzdata shadow su-exec
COPY --from=go-builder /app/pad /usr/local/bin/pad
COPY docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh
RUN chmod +x /usr/local/bin/docker-entrypoint.sh
# In-image `pad` user/group at uid/gid 1000 — the entrypoint shim remaps
# these to match PUID/PGID at container start (defaults 99/100, the
# Unraid `nobody:users` convention). The actual numeric IDs can be
# anything; 1000 is just the historical default.
RUN addgroup -g 1000 pad \
&& adduser -D -u 1000 -G pad -h /home/pad pad \
&& mkdir -p /data \
&& chown -R pad:pad /data
ENV PAD_DATA_DIR=/data
ENV PAD_HOST=0.0.0.0
# IMPORTANT: NO `USER pad` directive — container starts as root so the
# entrypoint shim can chown /data + remap user/group ids before
# dropping privileges via `su-exec pad`. See TASK-1168 / PLAN-1166.
EXPOSE 7777
VOLUME /data
# Healthcheck adapts to the caller's chosen execution model:
#
# • Default invocation (container starts as root, entrypoint drops to pad):
# healthcheck runs as ROOT (Docker uses the image USER, which is root
# because we removed the USER directive). Wrap with su-exec so it
# matches the main process's unprivileged UID.
#
# • `docker run --user 1234` invocation: healthcheck ALSO runs as 1234.
# `su-exec pad ...` would fail because 1234 lacks the privilege to
# switch to user `pad`. Just run wget directly in that branch.
#
# start-period bumped 10s → 60s. The always-chown-R policy (D4) means a
# user restoring a backup with a large attachment store can legitimately
# spend tens of seconds in the entrypoint before pad starts listening;
# 60s covers ~600k files at 10k files/sec on local SSD.
HEALTHCHECK --interval=30s --timeout=5s --start-period=60s --retries=3 \
CMD sh -c 'if [ "$(id -u)" = "0" ]; then exec su-exec pad wget -q --spider http://localhost:7777/api/v1/health; fi; exec wget -q --spider http://localhost:7777/api/v1/health'
ENTRYPOINT ["docker-entrypoint.sh", "pad"]
CMD ["server", "start"]