chore: optimize docker image size < 500MB with multi-stage build

This commit is contained in:
Khoa Vo 2026-01-01 15:34:52 +07:00
parent 7bb58693dd
commit 94f046f0d6
3 changed files with 62 additions and 29 deletions

29
.dockerignore Normal file
View file

@ -0,0 +1,29 @@
# Git
.git
.gitignore
# Node
node_modules
npm-debug.log
# Python
venv
__pycache__
*.pyc
*.pyo
*.pyd
# Next.js
.next
out
# Docker
Dockerfile
.dockerignore
# OS
.DS_Store
Thumbs.db
# Misc
*.log

View file

@ -1,58 +1,60 @@
# --- Stage 1: Frontend Builder ---
FROM node:18-alpine AS builder
WORKDIR /app/frontend
COPY frontend/package*.json ./
# Install dependencies including devDependencies for build
RUN npm install --legacy-peer-deps
COPY frontend/ ./
# Build with standalone output
ENV NEXT_PUBLIC_API_URL="http://localhost:8000"
RUN npm run build
# --- Stage 2: Final Runtime Image ---
FROM python:3.11-slim
# Install Node.js and dependencies
# Install system dependencies
RUN apt-get update && apt-get install -y \
curl \
gnupg \
ffmpeg \
ca-certificates \
nodejs \
npm \
&& update-ca-certificates \
&& curl -fsSL https://deb.nodesource.com/setup_18.x | bash - \
&& apt-get install -y nodejs \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /app
# --- Backend Setup ---
# Backend Setup
COPY backend/requirements.txt ./backend/requirements.txt
RUN pip install --no-cache-dir -r backend/requirements.txt
# --- Frontend Setup ---
COPY frontend/package*.json ./frontend/
WORKDIR /app/frontend
# Install dependencies (ignoring peer deps conflicts)
RUN npm install --legacy-peer-deps
# Frontend Setup (Copy from Builder)
# Copy the standalone server
COPY --from=builder /app/frontend/.next/standalone /app/frontend
# Copy static files (required for standalone)
COPY --from=builder /app/frontend/.next/static /app/frontend/.next/static
COPY --from=builder /app/frontend/public /app/frontend/public
COPY frontend/ .
# Build Next.js (with ignore-lint already set in next.config.mjs)
# We set API URL to http://localhost:8000 because in this container strategy,
# the browser will access the backend directly.
# Wait, for client-side fetches, "localhost" refers to the user's machine.
# If we run this container on port 3000 and 8000, localhost:8000 works internally via Rewrites.
# ENV NEXT_PUBLIC_API_URL="http://localhost:8000" Removed to use relative path proxying
# Build Next.js
ENV NEXTAUTH_URL=http://localhost:3000
# Secret should be provided at runtime via docker run -e or docker-compose
ARG NEXTAUTH_SECRET_ARG=default_dev_secret_change_in_production
ENV NEXTAUTH_SECRET=${NEXTAUTH_SECRET_ARG}
RUN npm run build
# --- Final Setup ---
WORKDIR /app
# Copy Backend Code
COPY backend/ ./backend/
# Create a start script
# We also implement a "seed data" check.
# If the volume mount is empty (missing data.json), we copy from our backup.
# Create start script
RUN mkdir -p backend/data_seed && cp -r backend/data/* backend/data_seed/ || true
# Set Environment Variables
ENV NODE_ENV=production
ENV PORT=3000
ENV HOSTNAME="0.0.0.0"
# Note: Standalone mode runs with 'node server.js'
RUN echo '#!/bin/bash\n\
if [ ! -f backend/data/data.json ]; then\n\
echo "Data volume appears empty. Seeding with bundled data..."\n\
cp -r backend/data_seed/* backend/data/\n\
fi\n\
uvicorn backend.main:app --host 0.0.0.0 --port 8000 &\n\
cd frontend && npm start -- -p 3000\n\
cd frontend && node server.js\n\
' > start.sh && chmod +x start.sh
EXPOSE 3000 8000

View file

@ -1,7 +1,9 @@
/** @type {import('next').NextConfig} */
const nextConfig = {
// strict mode true is default but good to be explicit
// strict mode true is default but good to be explicit
reactStrictMode: true,
output: "standalone",
eslint: {
ignoreDuringBuilds: true,
},