使用 Docker 和 pnpm 优化打包 Nuxt.js 全栈项目

Nexmoe February 25, 2024
This article is an AI translation and may contain semantic inaccuracies.

This article walks you through creating an optimized Docker image for a full-stack project that combines Prisma and Nuxt.js, using pnpm as the package manager.

I reduced the final image size from 1.12GB to 160.21MB.

Project stack

Nuxt.js is a server-side rendering framework based on Vue.js and is great for building modern web apps.

My project uses Nuxt to build a full-stack app directly.

  • Nuxt3
  • Prisma
  • PNPM

Start building

First, we use the lighter node:20-alpine base image to reduce the final image size. Alpine Linux is popular for being secure, minimal, and small.

Multi-stage builds are one of the most effective strategies to shrink Docker images. We’ll use three stages.

Stage 1: Build dependencies

ARG NODE_VERSION=node:20-alpine

FROM $NODE_VERSION AS dependency-base

WORKDIR /app

RUN npm install -g pnpm

COPY package.json pnpm-lock.yaml ./

RUN pnpm install --frozen-lockfile`

This stage installs project dependencies. We use pnpm instead of npm because it is more efficient in cache and disk usage.

Most projects now use pnpm as their package manager instead of npm.

Stage 2: Build the application

FROM dependency-base AS production-base

COPY . .

RUN pnpm run build 

In this stage we copy the project source and run the build command. For Nuxt, this generates the assets needed for static output and server-side rendering.

Stage 3: Build the production image

FROM $NODE_VERSION AS production

COPY --from=production-base /app/.output /app/.output

ENV NUXT_HOST=0.0.0.0 \
    NUXT_APP_VERSION=latest \
    DATABASE_URL=file:./db.sqlite \
    NODE_ENV=production

WORKDIR /app

EXPOSE 3000

CMD ["node", "/app/.output/server/index.mjs"]

Finally, we create a production image. It contains only the files needed to run the app, reducing unnecessary layers and keeping it lean.

We also define environment variables like NUXT_HOST and DATABASE_URL, which are required by Nuxt and Prisma. DATABASE_URL points to the SQLite file in the project root.

The app runs on port 3000, and we specify the startup command to run the Nuxt server.

Image size comparison

Comparing:

  • 3-stage build
  • 2-stage build
  • Direct build

a3c345aaa51a4b8b802c25bc9d3591c0.png

Dockerfile overview

# Use a smaller base image
ARG NODE_VERSION=node:20-alpine

# Stage 1: Build dependencies
FROM $NODE_VERSION AS dependency-base

# Create app directory
WORKDIR /app

# Install pnpm
RUN npm install -g pnpm

# Copy the package files
COPY package.json pnpm-lock.yaml ./

# Install dependencies using pnpm
RUN pnpm install --frozen-lockfile

# Stage 2: Build the application
FROM dependency-base AS production-base

# Copy the source code
COPY . .

# Build the application
RUN pnpm run build

# Stage 3: Production image
FROM $NODE_VERSION AS production

# Copy built assets from previous stage
COPY --from=production-base /app/.output /app/.output

# Define environment variables
ENV NUXT_HOST=0.0.0.0 \
    NUXT_APP_VERSION=latest \
    DATABASE_URL=file:./db.sqlite \
    NODE_ENV=production

# Set the working directory
WORKDIR /app

EXPOSE 3000

# Start the app
CMD ["node", "/app/.output/server/index.mjs"]