diff --git a/Dockerfile b/Dockerfile index 9ba4819..af98725 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,28 +1,109 @@ + +# syntax=docker/dockerfile:1 + +# ========================================================= +# Base +# ========================================================= FROM node:20-bookworm-slim AS base + WORKDIR /app + ENV NEXT_TELEMETRY_DISABLED=1 +ENV HUSKY=0 + +# ========================================================= +# Dependencies +# ========================================================= FROM base AS deps -COPY package.json package-lock.json ./ -RUN npm install --no-audit --no-fund +COPY package.json package-lock.json ./ + +RUN npm ci --no-audit --no-fund + + +# ========================================================= +# Builder +# ========================================================= FROM base AS builder + ENV NODE_ENV=production ENV BUILD_STANDALONE=true +ENV NEXT_TELEMETRY_DISABLED=1 +ENV HUSKY=0 + ARG NEXT_PUBLIC_SENTRY_DISABLED=true ENV NEXT_PUBLIC_SENTRY_DISABLED=${NEXT_PUBLIC_SENTRY_DISABLED} + COPY --from=deps /app/node_modules ./node_modules COPY . . + RUN npm run build + +# ========================================================= +# Migration / Seed image +# +# ใช้ target นี้สำหรับ: +# - npm run migrate +# - npm run seed:master-data +# - npm run seed:super-admin +# ========================================================= +FROM base AS migrator + +ENV NODE_ENV=production +ENV NEXT_TELEMETRY_DISABLED=1 +ENV HUSKY=0 + +COPY --from=deps /app/node_modules ./node_modules +COPY package.json package-lock.json ./ + +# Drizzle configuration +COPY drizzle.config.* ./ + +# Migration files +COPY drizzle ./drizzle + +# Database source code ที่ migration/seed scripts อาจ import +COPY src ./src +COPY scripts ./scripts + +# ถ้าโปรเจกต์ใช้ tsconfig path alias +COPY tsconfig.json ./ + +# ถ้ามีไฟล์ config อื่นที่ scripts ใช้งาน ให้เปิดใช้ตามจริง +# COPY next.config.* ./ +# COPY env.* ./ + +CMD ["npm", "run", "migrate"] + + +# ========================================================= +# Production Runner +# ========================================================= FROM node:20-bookworm-slim AS runner + WORKDIR /app + ENV NODE_ENV=production ENV NEXT_TELEMETRY_DISABLED=1 ENV PORT=3000 ENV HOSTNAME=0.0.0.0 -COPY --from=builder /app/public ./public -COPY --from=builder /app/.next/standalone ./ -COPY --from=builder /app/.next/static ./.next/static + +RUN groupadd --system --gid 1001 nodejs \ + && useradd --system --uid 1001 --gid nodejs nextjs + +COPY --from=builder --chown=nextjs:nodejs /app/public ./public +COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ +COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static + +# กรณีแอปมี persistent local storage +RUN mkdir -p /app/storage \ + && chown -R nextjs:nodejs /app/storage + +USER nextjs + EXPOSE 3000 + CMD ["node", "server.js"] + diff --git a/next.config.ts b/next.config.ts index 1c91657..ad828b0 100644 --- a/next.config.ts +++ b/next.config.ts @@ -1,37 +1,37 @@ -import type { NextConfig } from 'next'; -import { withSentryConfig } from '@sentry/nextjs'; +import type { NextConfig } from "next"; +import { withSentryConfig } from "@sentry/nextjs"; // Define the base Next.js configuration const baseConfig: NextConfig = { - output: process.env.BUILD_STANDALONE === 'true' ? 'standalone' : undefined, + output: "standalone", images: { remotePatterns: [ { - protocol: 'https', - hostname: 'api.slingacademy.com', - port: '' + protocol: "https", + hostname: "api.slingacademy.com", + port: "", }, { - protocol: 'https', - hostname: 'placehold.co', - port: '' + protocol: "https", + hostname: "placehold.co", + port: "", }, { - protocol: 'https', - hostname: 'img.clerk.com', - port: '' + protocol: "https", + hostname: "img.clerk.com", + port: "", }, { - protocol: 'https', - hostname: 'clerk.com', - port: '' - } - ] + protocol: "https", + hostname: "clerk.com", + port: "", + }, + ], }, - transpilePackages: ['geist'], + transpilePackages: ["geist"], compiler: { - removeConsole: process.env.NODE_ENV === 'production' - } + removeConsole: process.env.NODE_ENV === "production", + }, }; let configWithPlugins = baseConfig; @@ -48,7 +48,7 @@ if (!process.env.NEXT_PUBLIC_SENTRY_DISABLED) { widenClientFileUpload: true, // Route browser requests to Sentry through a Next.js rewrite to circumvent ad-blockers. - tunnelRoute: '/monitoring', + tunnelRoute: "/monitoring", // Disable Sentry telemetry telemetry: false, @@ -56,17 +56,19 @@ if (!process.env.NEXT_PUBLIC_SENTRY_DISABLED) { // Sentry v10: moved under webpack namespace webpack: { reactComponentAnnotation: { - enabled: true + enabled: true, }, treeshake: { - removeDebugLogging: true - } + removeDebugLogging: true, + }, }, // Disable source map upload when org/project are not configured sourcemaps: { - disable: !process.env.NEXT_PUBLIC_SENTRY_ORG || !process.env.NEXT_PUBLIC_SENTRY_PROJECT - } + disable: + !process.env.NEXT_PUBLIC_SENTRY_ORG || + !process.env.NEXT_PUBLIC_SENTRY_PROJECT, + }, }); }