Vấn đề: Docker Image Next.js bị “béo phì”
Nếu anh em từng deploy Next.js lên VPS theo cách truyền thống, chắc hẳn sẽ có lúc đứng hình khi thấy Image nặng cả GB. Vấn đề thường nằm ở việc chúng ta bê nguyên node_modules (cả dev lẫn prod) vào môi trường chạy. Kết quả? Mỗi lần push/pull lên Registry mất tới 5-7 phút, tiêu tốn băng thông và làm chậm cả hệ thống CI/CD.
Tại sao một ứng dụng web đơn giản lại nặng nề như một phần mềm desktop? Câu trả lời nằm ở đống “hành lý cồng kềnh” không cần thiết. Từ phiên bản 12.2, Next.js hỗ trợ Standalone Mode — một tính năng giúp lọc ra đúng những gì tối thiểu để app vận hành. Áp dụng cách này, mình đã giảm được dung lượng Image từ 1.2GB xuống chỉ còn vỏn vẹn 120MB.
Standalone Mode: Nó hoạt động như thế nào?
Thông thường, lệnh next start đòi hỏi toàn bộ thư mục dependency. Nhưng thực tế, app chạy production không cần đến những package dùng để build hay test.
Khi bật standalone, Next.js sử dụng @vercel/nft để phân tích mã nguồn một cách thông minh. Nó tự động nhặt ra các file cần thiết và gom vào thư mục .next/standalone. Thư mục này bao gồm một bản rút gọn của node_modules, đủ để app tự đứng vững mà không cần bản gốc.
Đây là chìa khóa để triển khai Multi-stage builds. Chúng ta build app ở một stage, sau đó chỉ copy thư mục nhỏ gọn này sang stage cuối (runner). Image cuối cùng sẽ cực kỳ tinh gọn.
Hướng dẫn tối ưu Image từng bước
Bước 1: Cấu hình Next.js
Đầu tiên, hãy kích hoạt tính năng này trong file next.config.js:
/** @type {import('next').NextConfig} */
const nextConfig = {
output: 'standalone',
}
module.exports = nextConfig
Chạy npm run build, bạn sẽ thấy thư mục .next/standalone xuất hiện. Đây chính là “trái tim” của ứng dụng mà chúng ta sẽ đưa vào Docker.
Bước 2: Sử dụng .dockerignore
Đừng bỏ qua file này nếu không muốn Docker tốn thời gian quét những thứ vô ích. Một file .dockerignore chuẩn chỉnh sẽ giúp tốc độ build tăng rõ rệt.
node_modules
.next
.git
.env*
README.md
Bước 3: Dockerfile tối ưu (Multi-stage)
Dưới đây là cấu trúc Dockerfile 3 giai đoạn: deps, builder và runner. Cách chia này giúp tận dụng tối đa layer caching của Docker.
# Stage 1: Cài đặt dependencies
FROM node:20-alpine AS deps
RUN apk add --no-cache libc6-compat
WORKDIR /app
COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml* ./
RUN \
if [ -f package-lock.json ]; then npm ci; \
elif [ -f yarn.lock ]; then yarn --frozen-lockfile; \
elif [ -f pnpm-lock.yaml ]; then corepack enable pnpm && pnpm i --frozen-lockfile; \
else echo "Không tìm thấy lockfile." && exit 1; \
fi
# Stage 2: Build ứng dụng
FROM node:20-alpine AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
ENV NEXT_TELEMETRY_DISABLED 1
RUN npm run build
# Stage 3: Runner - Môi trường Production
FROM node:20-alpine AS runner
WORKDIR /app
ENV NODE_ENV production
ENV NEXT_TELEMETRY_DISABLED 1
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
COPY --from=builder /app/public ./public
# Thiết lập quyền hạn
RUN mkdir .next
RUN chown nextjs:nodejs .next
# Copy output từ standalone mode
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
USER nextjs
EXPOSE 3000
ENV PORT 3000
CMD ["node", "server.js"]
Tại sao Stage 3 lại hiệu quả?
- Sử dụng
node:20-alpinegiúp nền tảng Image nhẹ nhất có thể. - Chạy trực tiếp
node server.jsthay vì qua npm, giúp giảm overhead tài nguyên. - Chúng ta phải copy thủ công
staticvàpublicvì chế độ standalone không tự gom các file tĩnh này.
Bước 4: Kiểm tra kết quả
Build và kiểm tra dung lượng Image của bạn:
docker build -t nextjs-app .
docker images
Bạn sẽ thấy sự khác biệt. Nếu trước đây Image nặng 1.2GB, giờ nó chỉ loanh quanh 120MB-150MB. Thời gian deploy thực tế trên Github Actions của mình giảm từ 4 phút xuống còn 45 giây.
Kinh nghiệm thực tế từ “xương máu”
1. Biến môi trường (Environment Variables): Standalone mode khóa cứng các biến môi trường lúc build. Để linh hoạt, hãy dùng process.env trong code và truyền giá trị qua flag -e khi chạy docker run.
2. Tip debug cấu hình nhanh: Khi cấu hình Docker lỗi, việc đọc log JSON thô trong terminal rất khó chịu. Mình thường copy nhanh log đó vào formatter tại toolcraft.app/vi/tools/developer/json-formatter để soi cấu hình — nhanh hơn việc loay hoay cài extension cho VS Code nhiều. Nó giúp mình thấy ngay chỗ nào đang sai lệch giữa môi trường dev và prod.
3. Đừng quên thư viện Sharp: Nếu app dùng next/image, hãy cài thêm sharp vào stage builder. Thiếu nó, tính năng tối ưu ảnh của Next.js sẽ chạy rất chậm và tốn CPU trên server.
Kết luận
Tối ưu Docker không chỉ là để tiết kiệm vài GB ổ cứng. Nó làm toàn bộ quy trình từ build đến deploy trở nên mượt mà, chuyên nghiệp hơn. Với Standalone Mode, bạn không còn phải lo lắng về việc server bị treo vì thiếu bộ nhớ khi kéo Image nặng nề nữa.
Hy vọng kỹ thuật này giúp anh em rút ngắn thời gian deploy để dành thời gian cho những việc quan trọng hơn. Nếu gặp lỗi khi config, cứ để lại bình luận phía dưới nhé!

