From 94f046f0d68108e4194adb6fe28b9b77ddd3fa73 Mon Sep 17 00:00:00 2001 From: Khoa Vo Date: Thu, 1 Jan 2026 15:34:52 +0700 Subject: [PATCH] chore: optimize docker image size < 500MB with multi-stage build --- .dockerignore | 29 +++++++++++++++++++ Dockerfile | 60 +++++++++++++++++++++------------------- frontend/next.config.mjs | 2 ++ 3 files changed, 62 insertions(+), 29 deletions(-) create mode 100644 .dockerignore diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..cf8c547 --- /dev/null +++ b/.dockerignore @@ -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 diff --git a/Dockerfile b/Dockerfile index a26f6b0..61a1c65 100644 --- a/Dockerfile +++ b/Dockerfile @@ -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 diff --git a/frontend/next.config.mjs b/frontend/next.config.mjs index c9e796f..a2c7c4e 100644 --- a/frontend/next.config.mjs +++ b/frontend/next.config.mjs @@ -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, },