# ── Stage 1: Build frontend ─────────────────────────────────────────────────── FROM node:20-alpine AS frontend-builder WORKDIR /app/frontend COPY frontend/package*.json ./ RUN npm install COPY frontend/ ./ RUN npm run build # ── Stage 2: Build backend ──────────────────────────────────────────────────── FROM golang:1.22-alpine AS backend-builder WORKDIR /app/backend # Copy go.mod first for layer caching. go.sum is written by go mod download # when it doesn't exist yet, or verified when it does. COPY backend/go.mod ./ COPY backend/go.su[m] ./ RUN go mod download -x COPY backend/ ./ # CGO disabled — pure Go SQLite (modernc.org/sqlite). RUN CGO_ENABLED=0 go build -ldflags="-s -w" -o /archivum ./cmd/server # ── Stage 3: Runtime image ──────────────────────────────────────────────────── FROM alpine:3.20 # su-exec: lightweight tool to drop privileges (replaces gosu / setpriv). # git: needed for wiki commit operations. RUN apk add --no-cache su-exec git # Create the app group and user that the entrypoint will run as. # PUID/PGID can be overridden at runtime via environment variables; # the entrypoint script uses them rather than these build-time values. RUN addgroup -g 1000 archivum \ && adduser -u 1000 -G archivum -s /sbin/nologin -D archivum COPY --from=frontend-builder /app/frontend/dist /srv/archivum/ui COPY --from=backend-builder /archivum /usr/local/bin/archivum COPY docker/entrypoint.sh /usr/local/bin/entrypoint.sh RUN chmod +x /usr/local/bin/entrypoint.sh # Declare the volume mount points. The entrypoint creates subdirectories # and fixes ownership before the application starts. VOLUME ["/config", "/data"] ENV DOCKER_PATH=/config \ UI_DIR=/srv/archivum/ui \ PUID=1000 \ PGID=1000 # Entrypoint runs as root so it can chown the volumes, then drops to PUID:PGID. EXPOSE 4000 ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]