Dockerfile.single
Rama main · réplica de lectura del repositorio original.
# The single image: Peekaping in single mode (ADR 0002). One process, peekaping, serves the
# web app, the API and the websocket on port 8383. Its database is SQLite in /app/data, or a
# PostgreSQL server in a container of its own (DB_TYPE=postgres).
# Stage 1: Build peekaping and the migration tool
FROM golang:1.27.3-alpine AS go-builder
RUN apk add --no-cache git
WORKDIR /app/server
COPY apps/server/go.mod apps/server/go.sum ./
RUN go mod download
COPY apps/server/ .
RUN CGO_ENABLED=0 GOFLAGS="-trimpath" go build -o peekaping -ldflags="-s -w" ./cmd/peekaping
RUN CGO_ENABLED=0 GOFLAGS="-trimpath" go build -o bun -ldflags="-s -w" ./cmd/bun
# Stage 2: Build the web app
FROM node:22-alpine AS web-builder
WORKDIR /app
# Bun, at the version pinned in package.json
COPY --from=oven/bun:1.4.4-alpine /usr/local/bin/bun /usr/local/bin/bun
COPY package.json bun.lock ./
COPY apps/web/package.json ./apps/web/package.json
RUN bun install --frozen-lockfile --filter web
COPY apps/web/ ./apps/web/
WORKDIR /app/apps/web
RUN bun run build
# Stage 3: Runtime
FROM alpine:3.24
# tini runs as PID 1 and passes docker stop's SIGTERM to peekaping. su-exec drops root for
# peekaping, and shadow's groupmod and usermod apply PUID and PGID (startup.user.sh). Ping
# monitors fall back to busybox's ping where they can't open an ICMP socket themselves.
RUN apk add --no-cache ca-certificates tini su-exec shadow
# PUID and PGID can't take an ID a user or group in the image has, and Alpine gives out IDs
# that Debian, which earlier bundles ran on, leaves free: groups 82 (www-data) and 999 (ping)
# and user 405 (guest), among others. So the users and groups no file in the image belongs to
# are dropped.
# root, nobody and nogroup stay, and so do tty, disk and kmem, the groups Linux gives the
# device files Docker can bring into the container.
RUN for user in $(cut -d: -f1 /etc/passwd); do \
case "$user" in root | nobody) continue ;; esac; \
if [ -z "$(find / -xdev -user "$user" | head -n 1)" ]; then userdel "$user" || exit 1; fi; \
done \
&& for group in $(cut -d: -f1 /etc/group); do \
case "$group" in root | tty | disk | kmem | nogroup | nobody) continue ;; esac; \
if [ -z "$(find / -xdev -group "$group" | head -n 1)" ]; then groupdel "$group" || exit 1; fi; \
done
# The user peekaping runs as; startup.user.sh applies PUID/PGID
RUN addgroup -g 1000 peekaping \
&& adduser -u 1000 -G peekaping -H -D -h /nonexistent -s /sbin/nologin peekaping \
&& mkdir -p /app/data \
&& chown peekaping:peekaping /app/data
COPY --from=go-builder --chmod=755 /app/server/peekaping /app/server/bun /app/server/
COPY --from=go-builder --chmod=755 /app/server/scripts/run-migrations.sh /app/server/run-migrations.sh
COPY --from=web-builder /app/apps/web/dist /app/web
COPY --chmod=755 startup.single.sh /app/startup.sh
COPY startup.single.user.sh /app/startup.user.sh
# peekaping serves the web build itself and generates /env.js. The startup script checks DB_TYPE
# and forces SERVER_PORT to 8383, whatever the .env says.
ENV WEB_DIR=/app/web \
TZ=UTC
WORKDIR /app/server
EXPOSE 8383
# peekaping takes up to 30 seconds to shut down; give docker stop -t 35 or
# stop_grace_period: 35s so it isn't killed halfway
STOPSIGNAL SIGTERM
HEALTHCHECK --interval=30s --timeout=5s --start-period=60s --start-interval=2s --retries=3 \
CMD wget -q -O /dev/null http://127.0.0.1:8383/api/v1/health || exit 1
ENTRYPOINT ["/sbin/tini", "--"]
CMD ["/app/startup.sh"]