chore: optimize Docker build with multi-stage process and add standalone Next.js output (#75)
This commit is contained in:
@@ -2,37 +2,32 @@ ARG NODE_VERSION=20
|
||||
ARG APP_DIRNAME=web
|
||||
ARG PROJECT=@kan/web
|
||||
|
||||
# 1. Alpine image
|
||||
# 1. Alpine image with build tools
|
||||
FROM node:${NODE_VERSION}-alpine AS alpine
|
||||
RUN apk update
|
||||
RUN apk add --no-cache libc6-compat python3 make g++
|
||||
RUN apk update && \
|
||||
apk add --no-cache libc6-compat python3 make g++
|
||||
|
||||
# Setup pnpm and turbo on the alpine base
|
||||
FROM alpine AS base
|
||||
RUN corepack enable
|
||||
# Replace <your-major-version> with the major version installed in your repository. For example:
|
||||
# RUN npm install turbo@2.1.3 --global
|
||||
RUN npm install turbo@2.3.1 --global
|
||||
|
||||
RUN pnpm config set store-dir ~/.pnpm-store
|
||||
RUN corepack enable && \
|
||||
npm install turbo@2.3.1 dotenv-cli --global && \
|
||||
pnpm config set store-dir ~/.pnpm-store
|
||||
ENV COREPACK_ENABLE_DOWNLOAD_PROMPT=0
|
||||
|
||||
# 2. Prune projects
|
||||
FROM base AS pruner
|
||||
# https://stackoverflow.com/questions/49681984/how-to-get-version-value-of-package-json-inside-of-dockerfile
|
||||
# RUN export VERSION=$(npm run version)
|
||||
|
||||
ARG PROJECT
|
||||
ARG APP_DIRNAME
|
||||
|
||||
# Set working directory
|
||||
WORKDIR /app
|
||||
|
||||
# It might be the path to <ROOT> turborepo
|
||||
# Copy only necessary files for pruning to reduce context size
|
||||
COPY . .
|
||||
|
||||
|
||||
# Generate a partial monorepo with a pruned lockfile for a target workspace.
|
||||
# Assuming "@acme/nextjs" is the name entered in the project's package.json: { name: "@acme/nextjs" }
|
||||
# Include @kan/db for the migration command later.
|
||||
RUN turbo prune --scope=${PROJECT} --scope=@kan/db --docker
|
||||
|
||||
|
||||
# 3. Build the project
|
||||
FROM base AS builder
|
||||
ARG PROJECT
|
||||
@@ -48,17 +43,35 @@ COPY --from=pruner /app/out/pnpm-workspace.yaml ./pnpm-workspace.yaml
|
||||
COPY --from=pruner /app/out/json/ .
|
||||
|
||||
# First install the dependencies (as they change less often)
|
||||
# Use a cache for pnpm store for faster builds on subsequent runs
|
||||
RUN --mount=type=cache,id=pnpm,target=~/.pnpm-store pnpm install --frozen-lockfile
|
||||
|
||||
|
||||
# Copy source code of isolated subworkspace
|
||||
COPY --from=pruner /app/out/full/ .
|
||||
|
||||
# Build only the web application
|
||||
RUN pnpm build --filter=${PROJECT}
|
||||
|
||||
# 4. Final image - runner stage to run the application
|
||||
# 4. Production pruner
|
||||
FROM builder AS productionpruner
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Generate a partial monorepo with a pruned lockfile for a target workspace.
|
||||
# Include @kan/db for the migration command later.
|
||||
RUN turbo prune --scope=@kan/db --docker
|
||||
|
||||
# 5. Productionn db builder
|
||||
FROM productionpruner AS productiondbbuilder
|
||||
|
||||
WORKDIR /app/out/full
|
||||
|
||||
RUN pnpm install
|
||||
|
||||
# 6. Final image - runner stage to run the application
|
||||
FROM base AS runner
|
||||
ARG APP_DIRNAME
|
||||
|
||||
|
||||
# Don't run production as root
|
||||
RUN addgroup --system --gid 1001 nodejs
|
||||
RUN adduser --system --uid 1001 nextjs
|
||||
@@ -68,13 +81,29 @@ WORKDIR /app
|
||||
|
||||
ENV NODE_ENV=production
|
||||
|
||||
COPY --chown=nextjs:nodejs --from=builder /app/ ./
|
||||
# Copy only the necessary files for the Next.js application to run
|
||||
# This includes .next/, public/, and package.json from the web app
|
||||
# And the shared packages/node_modules that the web app depends on
|
||||
COPY --chown=nextjs:nodejs --from=builder /app/apps/${APP_DIRNAME}/.next/ ./apps/${APP_DIRNAME}/.next/
|
||||
COPY --chown=nextjs:nodejs --from=builder /app/apps/${APP_DIRNAME}/public/ ./apps/${APP_DIRNAME}/public/
|
||||
COPY --chown=nextjs:nodejs --from=builder /app/apps/${APP_DIRNAME}/package.json ./apps/${APP_DIRNAME}/package.json
|
||||
COPY --chown=nextjs:nodejs ./apps/${APP_DIRNAME}/entrypoint.sh /app/apps/${APP_DIRNAME}/entrypoint.sh
|
||||
|
||||
RUN chmod +x /app/apps/${APP_DIRNAME}/entrypoint.sh
|
||||
|
||||
# Copy all node_modules from the production-pruner stage. This is crucial for only having
|
||||
# the necessary modules for the runner.
|
||||
COPY --chown=nextjs:nodejs --from=productiondbbuilder /app/out/full/node_modules/ ./node_modules/
|
||||
COPY --chown=nextjs:nodejs --from=productiondbbuilder /app/out/full/package.json ./package.json
|
||||
|
||||
|
||||
# Copy the db package
|
||||
COPY --chown=nextjs:nodejs --from=productionpruner /app/out/full/packages/db/ ./packages/db/
|
||||
|
||||
WORKDIR /app/apps/${APP_DIRNAME}
|
||||
|
||||
ARG PORT=3000
|
||||
ENV PORT=${PORT}
|
||||
EXPOSE ${PORT}
|
||||
|
||||
# Run migration on start for now (until we have a better way to handle this)
|
||||
CMD ["sh", "-c", "cd /app && pnpm db:migrate && cd /app/apps/web && pnpm start"]
|
||||
|
||||
CMD ["./entrypoint.sh"]
|
||||
16
apps/web/entrypoint.sh
Normal file
16
apps/web/entrypoint.sh
Normal file
@@ -0,0 +1,16 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Run migrations from the Turborepo root context
|
||||
echo "Running database migrations..."
|
||||
# Navigate to the Turborepo root to run the db:migrate command
|
||||
pnpm --prefix /app db:migrate
|
||||
|
||||
# Check if migration was successful
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "\nDatabase migration failed! Exiting."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "\nStarting Next.js application..."
|
||||
# Start the Next.js application from the web app's directory
|
||||
exec node .next/standalone/apps/web/server.js
|
||||
@@ -10,6 +10,7 @@ configureRuntimeEnv();
|
||||
|
||||
/** @type {import("next").NextConfig} */
|
||||
const config = {
|
||||
output: "standalone",
|
||||
reactStrictMode: true,
|
||||
|
||||
/** Enables hot reloading for local packages without a build step */
|
||||
|
||||
Reference in New Issue
Block a user