Dockerfile
Rama develop · réplica de lectura del repositorio original.
# =============================================================================
# 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 <<EOF
// Generated by Dockerfile at image build time. Do not hand-edit.
export type Channel = 'stable' | 'develop' | 'dev';
export interface BuildInfo {
readonly version: string;
readonly channel: Channel;
readonly commit: string | null;
readonly builtAt: string | null;
}
export const build: BuildInfo = {
version: '${PROFILARR_VERSION}',
channel: '${VITE_CHANNEL}',
commit: '${PROFILARR_COMMIT}',
builtAt: '${PROFILARR_BUILT_AT}'
};
EOF
ENV APP_BASE_PATH=/build/dist/build
RUN pnpm exec vite build
RUN cp src/runtime/server.js src/runtime/origins.js dist/build/
# -----------------------------------------------------------------------------
# Stage 2: Runtime
# -----------------------------------------------------------------------------
FROM oven/bun:${BUN_VERSION}-alpine
# Labels for container metadata
LABEL org.opencontainers.image.title="Profilarr"
LABEL org.opencontainers.image.description="Configuration management for Radarr and Sonarr"
LABEL org.opencontainers.image.source="https://github.com/Dictionarry-Hub/profilarr"
LABEL org.opencontainers.image.licenses="AGPL-3.0"
# Install runtime dependencies
# - git: PCD repository operations (clone, pull, push)
# - tar: GNU tar for backup compatibility (not busybox tar)
# - curl: Health checks
# - su-exec: Drop privileges to non-root user (Alpine equivalent of gosu)
# - ca-certificates: HTTPS connections
# - bash: Entrypoint uses bash
# - shadow: Provides usermod/groupmod for PUID/PGID runtime changes
# - tini: PID 1 init for signal forwarding and orphaned process reaping
RUN apk add --no-cache \
git \
tar \
curl \
su-exec \
ca-certificates \
bash \
shadow \
tini \
&& apk upgrade --no-cache
# Create application directory
WORKDIR /app
# Copy the built server, client assets and runtime entry from the builder stage.
# Vite bundled every npm package into these files; the image has no node_modules.
COPY --from=builder /build/dist/build /app
# SBOM of the bundled npm packages, so `trivy image` can report them
COPY --from=builder /build/sbom.cdx.json /app/sbom.cdx.json
# Copy entrypoint script
COPY scripts/docker/entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
# Create a fixed profilarr user/group (UID/GID 1000).
# - Root/PUID mode: entrypoint may reassign UID/GID at runtime via usermod.
# - Non-root mode: set runAsUser: 1000 (K8s) or --user 1000 (Docker) to skip
# all privilege operations and run directly as this user.
# The Bun image ships a bun user and group on 1000; remove them first.
RUN deluser bun 2>/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"]