mirror of
https://github.com/fosrl/pangolin.git
synced 2026-10-15 00:59:04 +02:00
Reduce image size by pruning stuff and things
Ref https://github.com/orgs/fosrl/discussions/2069
This commit is contained in:
+15
-17
@@ -24,13 +24,19 @@ RUN if [ "$BUILD" = "oss" ]; then rm -rf server/private; fi && \
|
||||
npm run build:cli && \
|
||||
test -f dist/server.mjs
|
||||
|
||||
# Create placeholder files for MaxMind databases to avoid COPY errors
|
||||
# Real files should be present for saas builds, placeholders for oss builds
|
||||
RUN touch /app/GeoLite2-Country.mmdb /app/GeoLite2-ASN.mmdb
|
||||
# Instead of shipping the full production node_modules (most of which is UI-only
|
||||
# code already compiled into .next), copy only the files the server bundles can
|
||||
# load, merged with the node_modules Next.js traced for its standalone server.
|
||||
# Pruning first keeps dev dependencies out of the trace.
|
||||
RUN npm prune --omit=dev && \
|
||||
node trace-deps.mjs -o .next/standalone dist/server.mjs dist/migrations.mjs dist/cli.mjs
|
||||
|
||||
FROM base AS builder
|
||||
|
||||
RUN npm ci --omit=dev
|
||||
# MaxMind databases are only shipped in saas builds
|
||||
RUN mkdir -p maxmind && \
|
||||
if [ "$BUILD" = "saas" ]; then \
|
||||
touch GeoLite2-Country.mmdb GeoLite2-ASN.mmdb && \
|
||||
mv GeoLite2-Country.mmdb GeoLite2-ASN.mmdb maxmind/; \
|
||||
fi
|
||||
|
||||
# FROM node:24.18.1-slim AS runner
|
||||
FROM public.ecr.aws/docker/library/node:24.18.1-slim AS runner
|
||||
@@ -39,9 +45,6 @@ WORKDIR /app
|
||||
|
||||
RUN apt-get update && apt-get install -y curl tzdata && rm -rf /var/lib/apt/lists/*
|
||||
|
||||
COPY --from=builder /app/node_modules ./node_modules
|
||||
COPY --from=builder /app/package.json ./package.json
|
||||
|
||||
COPY --from=builder-dev /app/.next/standalone ./
|
||||
COPY --from=builder-dev /app/.next/static ./.next/static
|
||||
COPY --from=builder-dev /app/dist ./dist
|
||||
@@ -55,17 +58,12 @@ COPY server/db/ios_models.json ./dist/ios_models.json
|
||||
COPY server/db/mac_models.json ./dist/mac_models.json
|
||||
COPY public ./public
|
||||
|
||||
# Copy MaxMind databases for SaaS builds
|
||||
# Copy MaxMind databases (empty directory for non-saas builds)
|
||||
ARG BUILD=oss
|
||||
|
||||
RUN mkdir -p ./maxmind
|
||||
COPY --from=builder-dev /app/maxmind ./maxmind
|
||||
|
||||
# Copy MaxMind databases (placeholders exist for oss builds, real files for saas)
|
||||
COPY --from=builder-dev /app/GeoLite2-Country.mmdb ./maxmind/GeoLite2-Country.mmdb
|
||||
COPY --from=builder-dev /app/GeoLite2-ASN.mmdb ./maxmind/GeoLite2-ASN.mmdb
|
||||
|
||||
# Remove MaxMind databases for non-saas builds (keep only for saas)
|
||||
RUN if [ "$BUILD" != "saas" ]; then rm -rf ./maxmind; fi
|
||||
RUN if [ "$BUILD" != "saas" ]; then rmdir ./maxmind; fi
|
||||
|
||||
# OCI Image Labels - Build Args for dynamic values
|
||||
ARG VERSION="dev"
|
||||
|
||||
+22
-1
@@ -1,5 +1,7 @@
|
||||
import next from "next";
|
||||
import express from "express";
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import { parse } from "url";
|
||||
import logger from "@server/logger";
|
||||
import config from "@server/lib/config";
|
||||
@@ -8,9 +10,28 @@ import { stripDuplicateSesions } from "./middlewares/stripDuplicateSessions";
|
||||
const nextPort = config.getRawConfig().server.next_port;
|
||||
|
||||
export async function createNextServer() {
|
||||
const dev = process.env.ENVIRONMENT !== "prod";
|
||||
|
||||
// The image only ships the files Next traced for its standalone output,
|
||||
// which leaves out what is needed to load next.config at runtime. Hand
|
||||
// Next the config resolved at build time instead, the same way the
|
||||
// generated .next/standalone/server.js does.
|
||||
const requiredServerFilesPath = path.join(
|
||||
process.cwd(),
|
||||
".next",
|
||||
"required-server-files.json"
|
||||
);
|
||||
if (!dev && fs.existsSync(requiredServerFilesPath)) {
|
||||
const { config: nextConfig } = JSON.parse(
|
||||
fs.readFileSync(requiredServerFilesPath, "utf8")
|
||||
);
|
||||
process.env.__NEXT_PRIVATE_STANDALONE_CONFIG =
|
||||
JSON.stringify(nextConfig);
|
||||
}
|
||||
|
||||
// const app = next({ dev });
|
||||
const app = next({
|
||||
dev: process.env.ENVIRONMENT !== "prod",
|
||||
dev,
|
||||
turbopack: false
|
||||
});
|
||||
const handle = app.getRequestHandler();
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
// Copies only the node_modules files that the bundled server entry points can
|
||||
// actually load into an output directory, so the Docker image does not need
|
||||
// the full production node_modules tree (most of which is UI-only code that
|
||||
// Next.js has already compiled into .next).
|
||||
//
|
||||
// Usage: node trace-deps.mjs -o <outDir> <entry.mjs> [entry.mjs...]
|
||||
//
|
||||
// Uses the copy of @vercel/nft that ships with Next.js (the same tracer Next
|
||||
// uses for its standalone output), so it works after `npm prune --omit=dev`.
|
||||
|
||||
import nft from "next/dist/compiled/@vercel/nft/index.js";
|
||||
import yargs from "yargs";
|
||||
import { hideBin } from "yargs/helpers";
|
||||
import path from "path";
|
||||
import fs from "fs";
|
||||
|
||||
const argv = yargs(hideBin(process.argv))
|
||||
.usage("Usage: $0 -o [string] <entry...>")
|
||||
.option("out", {
|
||||
alias: "o",
|
||||
describe: "Directory to copy the traced node_modules files into",
|
||||
type: "string",
|
||||
demandOption: true
|
||||
})
|
||||
.demandCommand(1)
|
||||
.help()
|
||||
.alias("help", "h").argv;
|
||||
|
||||
const { nodeFileTrace } = nft;
|
||||
const base = process.cwd();
|
||||
const outDir = path.resolve(argv.out);
|
||||
|
||||
const { fileList, warnings } = await nodeFileTrace(
|
||||
argv._.map((entry) => path.resolve(String(entry))),
|
||||
{
|
||||
base,
|
||||
// Next.js is traced by `next build` for the standalone output (which
|
||||
// includes next/dist/server/next, the entry createNextServer uses).
|
||||
// Tracing it from here would pull in its dev server and bundlers.
|
||||
ignore: (file) => file.startsWith("node_modules/next/")
|
||||
}
|
||||
);
|
||||
|
||||
// Unresolvable optional requires are expected (native binaries for other
|
||||
// platforms, pg-native, bufferutil, ...), so only list them on request
|
||||
if (process.env.TRACE_VERBOSE) {
|
||||
for (const warning of warnings) {
|
||||
console.warn(`trace warning: ${warning.message}`);
|
||||
}
|
||||
} else if (warnings.size > 0) {
|
||||
console.log(
|
||||
`${warnings.size} trace warnings (set TRACE_VERBOSE=1 to list them)`
|
||||
);
|
||||
}
|
||||
|
||||
let count = 0;
|
||||
for (const file of fileList) {
|
||||
if (!file.startsWith("node_modules/")) continue;
|
||||
|
||||
const src = path.join(base, file);
|
||||
const dest = path.join(outDir, file);
|
||||
const stat = fs.lstatSync(src);
|
||||
if (stat.isDirectory() || fs.existsSync(dest)) continue;
|
||||
|
||||
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
||||
if (stat.isSymbolicLink()) {
|
||||
fs.symlinkSync(fs.readlinkSync(src), dest);
|
||||
} else {
|
||||
fs.copyFileSync(src, dest);
|
||||
fs.chmodSync(dest, stat.mode);
|
||||
}
|
||||
count++;
|
||||
}
|
||||
|
||||
console.log(`Traced ${count} node_modules files into ${outDir}`);
|
||||
Reference in New Issue
Block a user