Reduce Docker build memory use on small VPS
Some checks failed
Deploy MineGeo to Dokploy / deploy (push) Failing after 7m39s
Some checks failed
Deploy MineGeo to Dokploy / deploy (push) Failing after 7m39s
This commit is contained in:
10
Dockerfile
10
Dockerfile
@@ -18,12 +18,12 @@ ENV NODE_ENV=production \
|
||||
RUN if [ -z "$NEXT_PUBLIC_SUPABASE_URL" ] || [ -z "$NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY" ]; then \
|
||||
echo "In Dokploy, set NEXT_PUBLIC_SUPABASE_URL and NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY under Application > Environment > Build Time Arguments." >&2; \
|
||||
exit 1; \
|
||||
fi && npm run build
|
||||
fi && npm run build:docker
|
||||
|
||||
FROM node:22-bookworm-slim AS production-dependencies
|
||||
WORKDIR /app
|
||||
COPY package.json package-lock.json ./
|
||||
RUN npm ci --omit=dev --no-audit --no-fund
|
||||
# Reuse the installation, and prune only after the build has completed.
|
||||
# A second independent npm ci stage could overlap with compilation in BuildKit.
|
||||
FROM builder AS production-dependencies
|
||||
RUN npm prune --omit=dev --no-audit --no-fund
|
||||
|
||||
FROM node:22-bookworm-slim AS runner
|
||||
WORKDIR /app
|
||||
|
||||
@@ -43,6 +43,54 @@ Also set the following in Dokploy's regular runtime environment:
|
||||
Add any optional production variables from `.env.example` in Dokploy's
|
||||
environment settings. Do not upload `.env.local` or add it to the repository.
|
||||
|
||||
## Building on a 2 GiB VPS
|
||||
|
||||
The Dockerfile uses `npm run build:docker`. It generates route types and runs
|
||||
the full TypeScript check in a separate process before compiling with Webpack.
|
||||
Next.js skips its duplicate TypeScript check only after the separate check
|
||||
succeeds. The Docker build enables Webpack's build worker and memory
|
||||
optimizations, and keeps page collection at one worker. Each build Node process
|
||||
has a 640 MiB JavaScript heap ceiling; this is not a limit on the total memory
|
||||
used by all processes or native libraries.
|
||||
|
||||
Dependencies are installed once, then pruned after compilation. The dependency
|
||||
installation for the final image no longer runs independently alongside the
|
||||
application build. Normal `npm run build` retains its usual type checking and
|
||||
bundler behavior. See Next.js's [memory usage
|
||||
guide](https://nextjs.org/docs/app/guides/memory-usage) for the build options.
|
||||
|
||||
In Dokploy, keep builds concurrency at **1** on this VPS. Dokploy, PostgreSQL,
|
||||
Redis, Traefik, the running application, and the new build share the host's RAM.
|
||||
Avoid starting a panorama render during a deployment. Application runtime
|
||||
resource limits do not necessarily limit the Docker image build.
|
||||
|
||||
If the host freezes, capture these read-only diagnostics from the VPS shell
|
||||
before retrying. They print resource figures and Dokploy container state without
|
||||
environment values, API keys, private addresses, or repository names:
|
||||
|
||||
```sh
|
||||
free -m
|
||||
df -h /
|
||||
vmstat 1 5
|
||||
journalctl -k --since '30 minutes ago' --no-pager -o cat |
|
||||
awk '/oom-kill|Out of memory|Killed process/ {n++} END {print "Kernel OOM messages:", n+0}'
|
||||
docker ps -aq --filter label=com.docker.swarm.service.name=dokploy |
|
||||
xargs -r docker inspect --format 'OOMKilled={{.State.OOMKilled}} ExitCode={{.State.ExitCode}} Started={{.State.StartedAt}} Finished={{.State.FinishedAt}}'
|
||||
```
|
||||
|
||||
A startup log containing `Cancelled 1 deployments` is Dokploy's startup cleanup,
|
||||
not proof of a deployment timeout. Correlate container start/finish times and
|
||||
kernel OOM messages with the failed build. `OOMKilled=false` for Dokploy does not
|
||||
rule out a different process being killed or host-wide memory pressure. Journal
|
||||
permissions or missing history can also prevent the OOM count from being useful.
|
||||
|
||||
If swap is absent and memory pressure is confirmed, adding swap can prevent
|
||||
abrupt memory exhaustion, but it consumes disk space and can make builds slower.
|
||||
Approve server configuration changes separately; the repository changes do not
|
||||
create swap, restart services, or change server resource limits. If builds still
|
||||
exceed available memory, move image compilation to the MacBook runner and use a
|
||||
private image-transfer or private-registry workflow.
|
||||
|
||||
## Gitea Actions secrets
|
||||
|
||||
Add the following repository Actions secrets in Gitea. Use the HTTPS origin of
|
||||
|
||||
@@ -1,12 +1,18 @@
|
||||
import type { NextConfig } from "next";
|
||||
import { withWorkflow } from "workflow/next";
|
||||
// Set only by build-docker.mjs after the separate type check has succeeded.
|
||||
const dockerTypechecked = process.env.MINEGEO_DOCKER_TYPECHECKED === "1";
|
||||
const config: NextConfig = {
|
||||
poweredByHeader: false,
|
||||
cacheMaxMemorySize: 16 * 1024 * 1024,
|
||||
enablePrerenderSourceMaps: false,
|
||||
typescript: { ignoreBuildErrors: dockerTypechecked },
|
||||
experimental: {
|
||||
cpus: 1,
|
||||
preloadEntriesOnStart: false,
|
||||
...(dockerTypechecked
|
||||
? { webpackBuildWorker: true, webpackMemoryOptimizations: true }
|
||||
: {}),
|
||||
},
|
||||
outputFileTracingIncludes: { "/downloads": ["./public/tools/**/*.jar"] },
|
||||
serverExternalPackages: ["pg"],
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
"dev:web": "next dev --hostname 127.0.0.1",
|
||||
"prebuild": "node scripts/clean-build.mjs",
|
||||
"build": "next build",
|
||||
"build:docker": "node scripts/build-docker.mjs",
|
||||
"start": "node scripts/supervise.mjs production",
|
||||
"setup": "node scripts/setup.mjs",
|
||||
"reprocess": "node scripts/reprocess.mjs",
|
||||
|
||||
43
scripts/build-docker.mjs
Normal file
43
scripts/build-docker.mjs
Normal file
@@ -0,0 +1,43 @@
|
||||
import { spawnSync } from "node:child_process";
|
||||
|
||||
// Release each child's memory before starting the next expensive build phase.
|
||||
// In particular, do not type-check while a compiled bundler is still resident.
|
||||
function run(label, args, typechecked = false) {
|
||||
console.log(`[build:docker] ${label}`);
|
||||
const started = Date.now();
|
||||
const child = spawnSync(process.execPath, args, {
|
||||
stdio: "inherit",
|
||||
env: {
|
||||
...process.env,
|
||||
NODE_OPTIONS: "--max-old-space-size=640",
|
||||
NEXT_TELEMETRY_DISABLED: "1",
|
||||
MINEGEO_DOCKER_TYPECHECKED: typechecked ? "1" : "0",
|
||||
},
|
||||
});
|
||||
if (child.error) throw child.error;
|
||||
if (child.status !== 0) {
|
||||
console.error(
|
||||
`[build:docker] ${label} failed (${child.signal || `exit ${child.status}`}).`,
|
||||
);
|
||||
process.exit(child.status || 1);
|
||||
}
|
||||
console.log(
|
||||
`[build:docker] ${label} finished in ${Math.round((Date.now() - started) / 1000)}s.`,
|
||||
);
|
||||
}
|
||||
|
||||
run("Clean build cache", ["scripts/clean-build.mjs"]);
|
||||
run("Generate route types", ["node_modules/next/dist/bin/next", "typegen"]);
|
||||
run("Check TypeScript", [
|
||||
"node_modules/typescript/bin/tsc",
|
||||
"--noEmit",
|
||||
"--incremental",
|
||||
"false",
|
||||
]);
|
||||
// Type errors fail the preceding step. Only this invocation skips the duplicate
|
||||
// check; regular npm run build still performs Next.js's normal type checking.
|
||||
run(
|
||||
"Compile production application",
|
||||
["node_modules/next/dist/bin/next", "build", "--webpack"],
|
||||
true,
|
||||
);
|
||||
Reference in New Issue
Block a user