The container-only harness was measuring the wrong thing. Real usage — several tabs, several boards, clicking into widgets — reaches ~880 MB server and a 650 MB *browser* heap, and the browser was never measured at all. Profiling build: `docker build --build-arg HOMARR_PROFILING=true . -t homarr:performance`. React's production bundle strips the hooks DevTools needs, hence "Profiling not supported". Note reactProductionProfiling in next.config is read only by the webpack path; under Turbopack it comes from `next build --profile`, and turbo strips any env var not declared in the task's `env`, so all three had to line up before the flag actually took effect — verified by markers present only in React's profiling bundle, and static chunks going 20 -> 84 MB with source maps. inspect-container.mts attaches to an already-running container (SIGUSR1 opens Node's inspector — no rebuild, no restart), reports cgroup/per-process/V8 memory and can pull a heap snapshot out over CDP. The inspected context has no require and no dynamic import, so it reads globals and uses CDP domains instead. analyze-heapsnapshot / analyze-react-fibers / analyze-heap-retainers summarise a snapshot too large for DevTools to open. Harness now opens STRESS_BOARDS across STRESS_TABS_PER_BOARD tabs, clicks into widgets, and records browser heap, DOM nodes and listener count via CDP. Performance.getMetrics silently returns nothing unless the domain is enabled, which is why the first run reported a 0 MiB client heap.
104 lines
4.4 KiB
Docker
104 lines
4.4 KiB
Docker
# syntax=docker/dockerfile:1.25
|
|
|
|
FROM node:24.18.0-alpine AS base
|
|
|
|
FROM base AS builder
|
|
ARG TARGETPLATFORM
|
|
WORKDIR /app
|
|
# Native dependencies normally use prebuilds, but the build must remain
|
|
# reproducible when a prebuild download times out and compilation is required.
|
|
RUN apk add --no-cache libc6-compat curl bash python3 make g++
|
|
|
|
RUN corepack enable pnpm
|
|
COPY .npmrc pnpm-lock.yaml pnpm-workspace.yaml package.json ./
|
|
COPY patches ./patches
|
|
COPY --parents ./apps/*/package.json ./packages/*/package.json ./tooling/*/package.json ./
|
|
# @homarr/definitions generates documentation types during install.
|
|
COPY --parents ./packages/definitions/src ./
|
|
# Workaround for pnpm/pnpm#5268: pnpm fetch crashes when patchedDependencies
|
|
# are configured with nodeLinker: hoisted. The applyPatchToDir function tries
|
|
# to chdir into node_modules/<pkg> which doesn't exist during fetch (only the
|
|
# content-addressable store is populated). By temporarily switching to the
|
|
# isolated linker, patches apply inside the virtual store (node_modules/.pnpm/...)
|
|
# which IS created by pnpm fetch. The original hoisted linker is restored
|
|
# before the install step so the final node_modules layout stays flat.
|
|
RUN --mount=type=cache,id=pnpm-store,target=/pnpm/store \
|
|
pnpm config set store-dir /pnpm/store && \
|
|
sed -i 's/nodeLinker: hoisted/nodeLinker: isolated/' pnpm-workspace.yaml && \
|
|
pnpm fetch --ignore-scripts && \
|
|
sed -i 's/nodeLinker: isolated/nodeLinker: hoisted/' pnpm-workspace.yaml
|
|
|
|
# Install only from the fetched, committed lockfile so local and Docker builds
|
|
# resolve the same dependency graph. Serial lifecycle builds avoid esbuild's
|
|
# atomic binary replacement racing across the hoisted workspace (pnpm/pnpm#8200).
|
|
RUN --mount=type=cache,id=pnpm-store,target=/pnpm/store \
|
|
npm_config_nodedir=/usr/local pnpm install --recursive --offline --frozen-lockfile --child-concurrency=1
|
|
|
|
COPY . .
|
|
|
|
ARG SKIP_ENV_VALIDATION='true'
|
|
ARG CI='true'
|
|
ARG DISABLE_REDIS_LOGS='true'
|
|
ARG TARGETPLATFORM
|
|
# Opt-in React profiling build + browser source maps, for React DevTools' Profiler
|
|
# and for readable names in heap snapshots. See next.config.ts.
|
|
ARG HOMARR_PROFILING='false'
|
|
ENV HOMARR_PROFILING=${HOMARR_PROFILING}
|
|
|
|
RUN --mount=type=secret,id=TURBO_API,env=TURBO_API \
|
|
--mount=type=secret,id=TURBO_TEAM,env=TURBO_TEAM \
|
|
--mount=type=secret,id=TURBO_TOKEN,env=TURBO_TOKEN \
|
|
--mount=type=secret,id=TURBO_REMOTE_CACHE_SIGNATURE_KEY,env=TURBO_REMOTE_CACHE_SIGNATURE_KEY \
|
|
--mount=type=cache,id=homarr-next-build-${TARGETPLATFORM},target=/app/apps/nextjs/.next/cache,sharing=locked \
|
|
TURBO_PLATFORM="${TARGETPLATFORM:-linux/amd64}" \
|
|
pnpm turbo build --filter=@homarr/nextjs... --filter=@homarr/cli
|
|
|
|
FROM base AS runner
|
|
WORKDIR /app
|
|
|
|
# gettext is required for envsubst, openssl for generating AUTH_SECRET, su-exec for running application as non-root
|
|
RUN apk add --no-cache redis nginx bash gettext su-exec openssl
|
|
RUN mkdir /appdata
|
|
VOLUME /appdata
|
|
|
|
# Enable homarr cli
|
|
COPY --from=builder /app/packages/cli/cli.cjs /app/apps/cli/cli.cjs
|
|
RUN echo $'#!/bin/bash\ncd /app/apps/cli && node ./cli.cjs "$@"' > /usr/bin/homarr
|
|
RUN chmod +x /usr/bin/homarr
|
|
|
|
# Don't run production as root
|
|
RUN mkdir -p /var/cache/nginx && \
|
|
mkdir -p /var/log/nginx && \
|
|
mkdir -p /var/lib/nginx && \
|
|
touch /run/nginx/nginx.pid && \
|
|
mkdir -p /etc/nginx/templates /etc/nginx/ssl/certs
|
|
|
|
COPY --from=builder /app/apps/nextjs/next.config.ts .
|
|
COPY --from=builder /app/apps/nextjs/package.json .
|
|
COPY --from=builder /app/pnpm-workspace.yaml ./pnpm-workspace.yaml
|
|
|
|
COPY --from=builder /app/node_modules/better-sqlite3/build/Release/better_sqlite3.node /app/build/better_sqlite3.node
|
|
|
|
COPY --from=builder /app/packages/db/migrations ./db/migrations
|
|
|
|
# Automatically leverage output traces to reduce image size
|
|
# https://nextjs.org/docs/advanced-features/output-file-tracing
|
|
COPY --from=builder /app/apps/nextjs/.next/standalone ./
|
|
COPY --from=builder /app/apps/nextjs/.next/static ./apps/nextjs/.next/static
|
|
COPY --from=builder /app/apps/nextjs/public ./apps/nextjs/public
|
|
COPY scripts/run.sh ./run.sh
|
|
COPY --chmod=755 scripts/entrypoint.sh ./entrypoint.sh
|
|
COPY packages/redis/redis.conf /app/redis.conf
|
|
COPY nginx.conf /etc/nginx/templates/nginx.conf
|
|
|
|
|
|
ENV DB_URL='/appdata/db/db.sqlite'
|
|
ENV DB_DIALECT='sqlite'
|
|
ENV DB_DRIVER='better-sqlite3'
|
|
ENV AUTH_PROVIDERS='credentials'
|
|
ENV REDIS_IS_EXTERNAL='false'
|
|
ENV NODE_ENV='production'
|
|
|
|
ENTRYPOINT [ "/app/entrypoint.sh" ]
|
|
CMD ["sh", "run.sh"]
|