fix: speedup docker creation by reducing steps and bunch up copy operations

This commit is contained in:
Markus Hartung
2026-05-27 12:15:05 +02:00
parent f8e726fd1c
commit e12cf77857
3 changed files with 132 additions and 26 deletions

View File

@@ -37,6 +37,8 @@ INITIAL_PASSWORD=CHANGEME
# Base directory for all persistent data (SQLite DB, logs, backups).
# Used by: src/lib/db/core.ts — resolves the SQLite database file path.
# Default: ~/.omniroute/ | Override for Docker or custom installations.
# Hint: When running in Docker, consider mounting a host directory here for data persistence across container restarts
# also if you want to share the same database as "npm run dev" use "./data"
# DATA_DIR=/var/lib/omniroute
# Encryption key for SQLite database encryption at rest.

View File

@@ -1,10 +1,22 @@
FROM node:24-trixie-slim AS builder
# ── Common base with runtime deps ──────────────────────────────────────────
FROM node:26-trixie-slim AS base
WORKDIR /app
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
--mount=type=cache,target=/var/lib/apt/lists,sharing=locked \
RUN --mount=type=cache,target=/var/cache/apt,sharing=shared \
--mount=type=cache,target=/var/lib/apt/lists,sharing=shared \
apt-get update \
&& apt-get install -y --no-install-recommends libsecret-1-0 ca-certificates python3 make g++ \
&& apt-get install -y --no-install-recommends libsecret-1-0 ca-certificates \
&& rm -rf /var/lib/apt/lists/*
# ── Builder ────────────────────────────────────────────────────────────────
FROM base AS builder
# Build tools for native module compilation
# apt-get update needed here because base's rm -rf clears the shared cache
RUN --mount=type=cache,target=/var/cache/apt,sharing=shared \
--mount=type=cache,target=/var/lib/apt/lists,sharing=shared \
apt-get update \
&& apt-get install -y --no-install-recommends python3 make g++ \
&& rm -rf /var/lib/apt/lists/*
COPY package*.json ./
@@ -26,12 +38,15 @@ RUN --mount=type=cache,target=/root/.npm \
&& npm rebuild better-sqlite3 \
&& node -e "require('better-sqlite3')(':memory:').close()"
# Use Turbopack for significant build speedup
ENV OMNIROUTE_USE_TURBOPACK=1
COPY . ./
RUN --mount=type=cache,target=/app/.next/cache \
mkdir -p /app/data && npm run build -- --webpack
mkdir -p /app/data && npm run build
FROM node:24-trixie-slim AS runner-base
WORKDIR /app
# ── Runner base ────────────────────────────────────────────────────────────
FROM base AS runner-base
LABEL org.opencontainers.image.title="omniroute" \
org.opencontainers.image.description="Unified AI proxy — route any LLM through one endpoint" \
@@ -46,15 +61,11 @@ ENV NODE_OPTIONS="--max-old-space-size=256"
# Data directory inside Docker — must match the volume mount in docker-compose.yml
ENV DATA_DIR=/app/data
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
--mount=type=cache,target=/var/lib/apt/lists,sharing=locked \
apt-get update \
&& apt-get install -y --no-install-recommends libsecret-1-0 ca-certificates \
&& rm -rf /var/lib/apt/lists/*
RUN mkdir -p /app/data
COPY --from=builder /app/public ./public
COPY --from=builder /app/.next/static ./.next/static
# The standalone build + syncStandaloneExtraModules bundles all runtime files
# (.next, node_modules, migrations, scripts, docs, etc.) into .next/standalone/.
# Explicit overrides below cover modules that NFT tracing may miss.
COPY --from=builder /app/.next/standalone ./
# Explicitly copy @swc/helpers — not always traced by standalone output but needed at runtime
COPY --from=builder /app/node_modules/@swc/helpers ./node_modules/@swc/helpers
@@ -70,18 +81,6 @@ COPY --from=builder /app/node_modules/split2 ./node_modules/split2
# traced by Next.js standalone output — copy them explicitly.
COPY --from=builder /app/src/lib/db/migrations ./migrations
ENV OMNIROUTE_MIGRATIONS_DIR=/app/migrations
# MITM server.cjs is spawned at runtime via child_process — not traced by nft
COPY --from=builder /app/src/mitm/server.cjs ./src/mitm/server.cjs
# Runtime docs are pruned by .dockerignore to English markdown + OpenAPI.
# Next.js standalone tracing does not include docs read via fs.
COPY --from=builder /app/.next/standalone/docs ./docs
COPY --from=builder /app/scripts/dev/run-standalone.mjs ./dev/run-standalone.mjs
COPY --from=builder /app/scripts/build/runtime-env.mjs ./build/runtime-env.mjs
COPY --from=builder /app/scripts/build/bootstrap-env.mjs ./build/bootstrap-env.mjs
COPY --from=builder /app/scripts/dev/healthcheck.mjs ./healthcheck.mjs
RUN node -e "require('better-sqlite3')(':memory:').close()"
# Hand /app over to the baked-in `node` non-root user (UID/GID 1000) so the
# runtime process never holds root privileges. The chown happens after all

View File

@@ -147,6 +147,18 @@ export async function syncStandaloneNativeAssets(
sourcePath: path.join(rootDir, "node_modules", "wreq-js", "rust"),
destinationPath: path.join(rootDir, ".next", "standalone", "node_modules", "wreq-js", "rust"),
},
{
label: "better-sqlite3 native binary",
sourcePath: path.join(rootDir, "node_modules", "better-sqlite3", "build"),
destinationPath: path.join(
rootDir,
".next",
"standalone",
"node_modules",
"better-sqlite3",
"build"
),
},
];
let changed = false;
@@ -171,6 +183,90 @@ export async function syncStandaloneNativeAssets(
return changed;
}
export async function syncStandaloneExtraModules(
rootDir = projectRoot,
fsImpl = fs,
log = console
) {
const entries = [
{
label: "@swc/helpers",
sourcePath: path.join(rootDir, "node_modules", "@swc", "helpers"),
destRelative: path.join("node_modules", "@swc", "helpers"),
},
{
label: "pino-abstract-transport",
sourcePath: path.join(rootDir, "node_modules", "pino-abstract-transport"),
destRelative: path.join("node_modules", "pino-abstract-transport"),
},
{
label: "pino-pretty",
sourcePath: path.join(rootDir, "node_modules", "pino-pretty"),
destRelative: path.join("node_modules", "pino-pretty"),
},
{
label: "split2",
sourcePath: path.join(rootDir, "node_modules", "split2"),
destRelative: path.join("node_modules", "split2"),
},
{
label: "migrations",
sourcePath: path.join(rootDir, "src", "lib", "db", "migrations"),
destRelative: "migrations",
},
{
label: "MITM server",
sourcePath: path.join(rootDir, "src", "mitm", "server.cjs"),
destRelative: path.join("src", "mitm", "server.cjs"),
},
{
label: "run-standalone script",
sourcePath: path.join(rootDir, "scripts", "dev", "run-standalone.mjs"),
destRelative: path.join("dev", "run-standalone.mjs"),
},
{
label: "runtime-env script",
sourcePath: path.join(rootDir, "scripts", "build", "runtime-env.mjs"),
destRelative: path.join("build", "runtime-env.mjs"),
},
{
label: "bootstrap-env script",
sourcePath: path.join(rootDir, "scripts", "build", "bootstrap-env.mjs"),
destRelative: path.join("build", "bootstrap-env.mjs"),
},
{
label: "healthcheck script",
sourcePath: path.join(rootDir, "scripts", "dev", "healthcheck.mjs"),
destRelative: "healthcheck.mjs",
},
{
label: "public directory",
sourcePath: path.join(rootDir, "public"),
destRelative: "public",
},
{
label: "playwright-core (dynamic import by gemini-web executor)",
sourcePath: path.join(rootDir, "node_modules", "playwright-core"),
destRelative: path.join("node_modules", "playwright-core"),
},
];
let changed = false;
const standaloneRoot = path.join(rootDir, ".next", "standalone");
for (const entry of entries) {
if (!(await exists(entry.sourcePath))) continue;
const destPath = path.join(standaloneRoot, entry.destRelative);
await fsImpl.mkdir(path.dirname(destPath), { recursive: true });
await fsImpl.cp(entry.sourcePath, destPath, { recursive: true, force: true });
log.log(`[build-next-isolated] Synced standalone module: ${entry.label}`);
changed = true;
}
return changed;
}
export async function main() {
const movedPaths = [];
const transientBuildPaths = getTransientBuildPaths();
@@ -225,6 +321,15 @@ export async function main() {
nativeAssetErr
);
}
try {
await syncStandaloneExtraModules(projectRoot);
} catch (extraModuleErr) {
console.warn(
"[build-next-isolated] Non-fatal error syncing extra modules:",
extraModuleErr
);
}
}
process.exitCode = result.code;
} catch (error) {