# ============================================================================= # Profilarr Dockerfile # ============================================================================= # Multi-stage build for minimal final image size # # Build: docker build -t profilarr . # Run: docker run -v ./config:/config -p 6868:6868 profilarr # ----------------------------------------------------------------------------- # Stage 1: Build # ----------------------------------------------------------------------------- ARG NODE_VERSION=24.22.1 ARG PNPM_VERSION=11.28.9 ARG BUN_VERSION=1.4.4 FROM node:${NODE_VERSION}-bookworm-slim AS builder ARG PNPM_VERSION # pnpm installs dependencies and runs Vite; Bun only runs the built server RUN npm install -g pnpm@${PNPM_VERSION} WORKDIR /build # Copy dependency files first (cache key) COPY package.json pnpm-lock.yaml pnpm-workspace.yaml .npmrc ./ # Install dependencies, keeping the pnpm store in a build cache mount RUN --mount=type=cache,id=pnpm,target=/pnpm/store \ pnpm install --frozen-lockfile --store-dir /pnpm/store # Vite bundles every runtime package into the server, so the image has no # node_modules for `trivy image` to read. Trivy reads SBOM files instead, so this # writes a CycloneDX SBOM of the production packages. Trivy 0.70 silently skips # CycloneDX 1.7, pnpm's default, so pin 1.6. Reading package details from the # store keeps platform packages that weren't installed out of the list. RUN --mount=type=cache,id=pnpm,target=/pnpm/store \ pnpm sbom --sbom-format cyclonedx --sbom-spec-version 1.6 \ --sbom-type application --prod --store-dir /pnpm/store \ --out /build/sbom.cdx.json # Copy everything else COPY . . # Build the application: Vite builds SvelteKit to dist/build/ # Build-time variables for version card # TARGETARCH is automatically set by Docker buildx (amd64 or arm64) ARG TARGETARCH ARG VITE_CHANNEL=stable ENV VITE_PLATFORM=docker-${TARGETARCH} ENV VITE_CHANNEL=${VITE_CHANNEL} # Build metadata stamped into src/lib/shared/build.ts so the version, channel, # and commit are inlined into both server and client bundles at vite build time. # Consumers import this file directly; no runtime config lookup. ARG PROFILARR_VERSION=dev ARG PROFILARR_COMMIT=unknown ARG PROFILARR_BUILT_AT=unknown RUN cat > src/lib/shared/build.ts </dev/null || true && \ delgroup bun 2>/dev/null || true && \ addgroup -g 1000 profilarr && \ adduser -u 1000 -G profilarr -D -h /config -s /sbin/nologin profilarr # Create config directory RUN mkdir -p /config # Environment variables ENV PORT=6868 ENV HOST=0.0.0.0 ENV APP_BASE_PATH=/config ENV TZ=UTC # Expose port EXPOSE 6868 # Health check HEALTHCHECK --interval=30s --timeout=10s --start-period=10s --retries=3 \ CMD curl -sf http://localhost:${PORT}/api/v1/health || exit 1 # Volume for persistent data VOLUME /config # Tini runs as PID 1 to forward signals and reap orphaned child processes. # The entrypoint handles PUID/PGID/UMASK, then drops privileges and runs the app. # nosemgrep: dockerfile.security.missing-user-entrypoint.missing-user-entrypoint ENTRYPOINT ["/sbin/tini", "--", "/entrypoint.sh"]