-
Notifications
You must be signed in to change notification settings - Fork 22
Expand file tree
/
Copy pathDockerfile
More file actions
94 lines (86 loc) · 4 KB
/
Copy pathDockerfile
File metadata and controls
94 lines (86 loc) · 4 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
# 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
FROM golang:1.27-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
# 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"]