diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml index 2410796..113c5e9 100644 --- a/.gitea/workflows/deploy.yml +++ b/.gitea/workflows/deploy.yml @@ -28,6 +28,20 @@ jobs: cd /opt/owicms - docker compose up -d --build + # Clean up afterwards whether or not the deploy succeeds; otherwise + # every failed build leaves its layers behind until the disk fills. + # Build cache used in the last day is kept, so routine deploys stay fast. + cleanup() { + docker image prune -f || true + docker builder prune -af --filter until=24h || true + } + trap cleanup EXIT - docker image prune -f \ No newline at end of file + # Start from a clean slate too, in case an earlier run was killed. + cleanup + + echo "Disk before build:" + df -h /opt || true + docker system df || true + + docker compose up -d --build \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 96a24c5..336ef67 100644 --- a/Dockerfile +++ b/Dockerfile @@ -36,17 +36,20 @@ WORKDIR /app # Install dumb-init for proper signal handling RUN apk add --no-cache dumb-init -# Copy from builder: node_modules and .next build output. +# Copy the standalone server (only the files it actually uses, including the +# traced parts of node_modules), plus static assets, which it does not include. # Files stay root-owned so the app user cannot rewrite its own code; only the # Next.js cache directory is writable. -COPY --from=builder /app/node_modules ./node_modules -COPY --from=builder /app/.next ./.next +COPY --from=builder /app/.next/standalone ./ +COPY --from=builder /app/.next/static ./.next/static COPY --from=builder /app/public ./public -COPY --from=builder /app/package.json ./package.json RUN mkdir -p .next/cache && chown node:node .next/cache -# Set production environment +# Set production environment. HOSTNAME makes the server listen on all +# interfaces rather than only the container's hostname. ENV NODE_ENV=production +ENV HOSTNAME=0.0.0.0 +ENV PORT=3000 # Run as the unprivileged "node" user (uid 1000) instead of root USER node @@ -57,5 +60,5 @@ EXPOSE 3000 # Use dumb-init to handle signals properly ENTRYPOINT ["dumb-init", "--"] -# Start Next.js production server (directly, so npm doesn't need a writable home) -CMD ["node", "node_modules/next/dist/bin/next", "start"] +# Start the standalone Next.js server +CMD ["node", "server.js"] diff --git a/app/api/transcoder/upload/[videoId]/route.ts b/app/api/transcoder/upload/[videoId]/route.ts index c6f4654..05bdc40 100644 --- a/app/api/transcoder/upload/[videoId]/route.ts +++ b/app/api/transcoder/upload/[videoId]/route.ts @@ -57,7 +57,7 @@ export async function POST( // Security: path traversal guard. for (const p of [tempZipPath, tempDir, finalDir]) { - if (!path.resolve(p).startsWith(path.resolve(HLS_ROOT))) { + if (!path.resolve(/*turbopackIgnore: true*/ p).startsWith(path.resolve(HLS_ROOT))) { return NextResponse.json({ error: "Forbidden" }, { status: 403 }); } } diff --git a/app/uploads/videos/[...path]/route.ts b/app/uploads/videos/[...path]/route.ts index 1114661..d057529 100644 --- a/app/uploads/videos/[...path]/route.ts +++ b/app/uploads/videos/[...path]/route.ts @@ -148,7 +148,10 @@ export async function GET( } if (range) { - const stream = fs.createReadStream(filePath, { start: range.start, end: range.end }); + const stream = fs.createReadStream(/*turbopackIgnore: true*/ filePath, { + start: range.start, + end: range.end, + }); return new NextResponse(Readable.toWeb(stream) as unknown as BodyInit, { status: 206, headers: { @@ -159,7 +162,7 @@ export async function GET( }); } - const stream = fs.createReadStream(filePath); + const stream = fs.createReadStream(/*turbopackIgnore: true*/ filePath); return new NextResponse(Readable.toWeb(stream) as unknown as BodyInit, { status: 200, headers: { ...headers, "Content-Length": String(stats.size) }, diff --git a/next.config.ts b/next.config.ts index a9db8e6..9e10fe1 100644 --- a/next.config.ts +++ b/next.config.ts @@ -15,6 +15,9 @@ const securityHeaders = [ const nextConfig: NextConfig = { poweredByHeader: false, + // Emit a self-contained server with only the files it needs, so the Docker + // image does not have to ship the whole node_modules tree. + output: "standalone", images: { remotePatterns: [ {