Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
.next
.git
.gitignore
.eslintignore
.prettierignore
node_modules
npm-debug.log
yarn-debug.log
yarn-error.log
.env
.env.example
.env.local
.env.*.local
.DS_Store
.vscode
.idea
*.md
!README.md
.turbo
.cache
dist
build
coverage
.husky
39 changes: 39 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Stage 1: Builder
FROM node:24-alpine AS builder
RUN apk add --no-cache libc6-compat curl
WORKDIR /app

COPY package.json package-lock.json ./
RUN npm ci --ignore-scripts --no-fund --no-audit

COPY . .

# Check that output: 'standalone' is enabled (not commented)
RUN grep -E "^\s*output:" next.config.ts | grep -q "standalone" || (echo "ERROR: next.config.ts must have output: 'standalone' enabled" && exit 1)

# Ensure public directory exists (required for COPY in runtime stage)
RUN mkdir -p public

ENV NODE_ENV=production
RUN npm run build && \
curl -sf https://gobinaries.com/tj/node-prune | sh && \
node-prune .next/standalone/node_modules
Comment on lines +18 to +20
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Piping curl to sh from a URL is a security risk as it executes remote code without verification. For improved security and reproducibility, it's better to download a specific, versioned release of node-prune from its official GitHub repository.

RUN npm run build && \
    (curl -sfL https://github.com/tj/node-prune/releases/download/v1.7.0/node-prune_1.7.0_linux_amd64.tar.gz | tar -xz -C /usr/local/bin) && \
    node-prune .next/standalone/node_modules



# Stage 2: Runtime (distroless)
FROM gcr.io/distroless/nodejs24-debian12:nonroot
WORKDIR /app

ENV NODE_ENV=production \
PORT=3000 \
HOSTNAME=0.0.0.0
Comment on lines +27 to +29
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Hardcoding runtime configuration like PORT and HOSTNAME in the Docker image reduces its portability. It's better to configure these via environment variables at runtime (which you are already doing in docker-compose.yml). I suggest removing them from the Dockerfile and only keeping NODE_ENV=production.

ENV NODE_ENV=production


# Copy optimized build output
COPY --from=builder --chown=nonroot:nonroot /app/.next/standalone ./
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

This COPY command will fail because the .next/standalone directory is only generated when output: 'standalone' is set in next.config.ts. The current project configuration does not include this setting. To fix this, you need to update your next.config.ts to enable standalone output.

COPY --from=builder --chown=nonroot:nonroot /app/.next/static ./.next/static
COPY --from=builder --chown=nonroot:nonroot /app/public ./public

USER nonroot
EXPOSE 3000

CMD ["server.js"]
20 changes: 20 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
version: '3.8'

services:
app:
container_name: next-template
build: .
init: true

ports:
- '3000:3000'

environment:
NODE_ENV: production
HOSTNAME: 0.0.0.0
PORT: 3000
# Uncomment to enable persistent file storage
# volumes:
# - ./data:/app/data

restart: unless-stopped
2 changes: 1 addition & 1 deletion next.config.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { NextConfig } from 'next';

const nextConfig: NextConfig = {
/* config options here */
// output: 'standalone', // Uncomment this for Docker deployments
skipProxyUrlNormalize: true
};

Expand Down