perf(docker): бандл API, brotli-статика, wget-healthcheck, лимиты ресурсов, вендорные чанки web

This commit is contained in:
Denozordec committed 2026-10-23 17:01:02 +07:00
1 parent 1d7ea35a23
commit 8298454c63
10 files changed
+244 -20

No files matched your search

+25 -11
View File
@@ -22,27 +22,40 @@ COPY apps/api apps/api
COPY packages/ui packages/ui
COPY packages/shared packages/shared
COPY packages/db packages/db
# API собирается tsup в один файл (все зависимости внутри, кроме нативных).
RUN --mount=type=cache,target=/root/.local/share/pnpm/store,sharing=locked \
pnpm turbo build --filter=web --filter=@authportal/api \
&& pnpm --filter @authportal/api deploy --prod /out \
pnpm turbo build --filter=web --filter=@authportal/api
# Рантайм-слой: бандл + статика (только brotli) + нативные модули с теми же версиями, что в lockfile.
RUN mkdir -p /out/dist \
&& cp apps/api/dist/server.js /out/dist/server.js \
&& cp -r apps/web/dist /out/static \
&& find /out/static -type f \( -name '*.js' -o -name '*.css' \) -exec sh -c \
'gzip -k9 "$1" && brotli -f -q 11 -o "$1.br" "$1"' _ {} \; \
&& rm -rf /out/src /out/test /out/.turbo \
&& rm -rf /out/node_modules/@authportal/db/src /out/node_modules/@authportal/db/scripts /out/node_modules/@authportal/db/.turbo \
&& rm -rf /out/node_modules/@authportal/shared/src /out/node_modules/@authportal/shared/.turbo \
&& find /out/dist /out/node_modules/@authportal -type f \( -name '*.d.ts' -o -name '*.map' -o -name 'tsconfig*.json' -o -name 'vitest.config.ts' -o -name 'drizzle.config.ts' \) -delete
&& find /out/static -type f \( -name '*.js' -o -name '*.css' -o -name '*.html' -o -name '*.svg' -o -name '*.json' \) \
-exec sh -c 'brotli -f -q 11 -o "$1.br" "$1"' _ {} \; \
&& node -e "const v=(p)=>require(p).version;require('fs').writeFileSync('/out/package.json',JSON.stringify({name:'authportal-runtime',private:true,type:'module',dependencies:{'better-sqlite3':v('./packages/db/node_modules/better-sqlite3/package.json'),'@node-rs/argon2':v('./apps/api/node_modules/@node-rs/argon2/package.json')}}))"
RUN --mount=type=cache,target=/root/.npm,sharing=locked \
cd /out && npm install --omit=dev --no-audit --no-fund \
&& rm -rf node_modules/better-sqlite3/deps node_modules/better-sqlite3/src \
node_modules/better-sqlite3/build/Release/obj node_modules/better-sqlite3/build/Release/.deps \
node_modules/better-sqlite3/build/deps \
&& node -e "const D=require('better-sqlite3');new D(':memory:').prepare('select 1').get();import('@node-rs/argon2').then(m=>m.hash('x')).then(()=>console.log('native modules ok'))"
FROM ${BASE_NODE} AS runtime
WORKDIR /app
# Не-root runtime; /data — bind mount с хоста, на сервере нужен
# `chown -R 1000:1000 ./data` (см. deploy/docker-compose.traefik.yml).
RUN apk add --no-cache ca-certificates \
&& mkdir -p /data && chown node:node /data
RUN mkdir -p /data && chown node:node /data
ARG VERSION=dev
ARG GIT_SHA=unknown
ARG BUILD_TIME=
LABEL org.opencontainers.image.title="auth-portal" \
org.opencontainers.image.version="${VERSION}" \
org.opencontainers.image.revision="${GIT_SHA}" \
org.opencontainers.image.created="${BUILD_TIME}"
# Куча V8 ограничена под контейнер 256 МиБ (RSS = куча + нативная память + argon2);
# при другом mem_limit переопределите NODE_OPTIONS.
ENV NODE_ENV=production \
NODE_OPTIONS=--max-old-space-size=160 \
UV_THREADPOOL_SIZE=4 \
STATIC_DIR=/app/static \
DATABASE_URL=sqlite:/data/app.db \
SERVER_PORT=8080 \
@@ -54,6 +67,7 @@ USER node
EXPOSE 8080
STOPSIGNAL SIGTERM
VOLUME ["/data"]
# busybox wget вместо запуска второго процесса node каждые 30 с.
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
CMD ["node", "-e", "fetch('http://127.0.0.1:8080/health').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))"]
CMD ["wget", "-q", "-O", "/dev/null", "http://127.0.0.1:8080/health"]
CMD ["node", "dist/server.js"]