Comparing next start and next standalone with docker

taylor-vick-M5tzZtFCOfs-unsplash

I wrote about how to use nextjs with docker.

I wanted to compare using next standalone like in the article and just using next start.

I describe the new docker file I used for using next start in docker at the bottom of this article. But let's jump straight to the learnings.

Docker Image Size

The standalone mode I used in my original article creates a docker container of 230MB.

The next start mode creates a docker container of 750MB.

If you're passing around many docker containers throughout the day the lower size of standalone mode can be a big difference.

Static Asset serving

Standalone next build provides you a separate set of static assets that you can use in AWS S3 or similar instead of your docker container.

This is a big advantage of standalone mode if you have many static assets or your site is very busy.

Conclusion

The next start method is easier to setup in docker. It's also the default way to run nextjs and will always support all next features.

The standalone method is a bit more complex to setup in docker but once you set it up it doesn't change very often.

The standalone image is less than half the size of the full app method.

I'm going to continue using standalone for now!

Appendix: The next start dockerfile used

To setup the test we use the following dockerfile:

# A container with pnpm and python3 is required
FROM node:18-alpine as pnpm_base

WORKDIR /app
RUN npm i --global --no-update-notifier --no-fund pnpm@7
RUN apk add --no-cache g++ make py3-pip libc6-compat

# run fetch in a separate step to avoid re-fetching deps on every change
FROM pnpm_base as fetched_deps
WORKDIR /app
ENV NODE_ENV production
COPY pnpm-lock.yaml ./
RUN pnpm config set store-dir /workdir/.pnpm-store
RUN pnpm fetch

# install all deps from cache
FROM fetched_deps as with_all_deps
COPY . ./
RUN pnpm install --offline

# Build the BE
# the main issue with building everything is the FE depends on a running BE
FROM with_all_deps as builder
RUN pnpm --filter='*frontend' build
RUN pnpm --filter='*frontend' deploy pruned --prod

# Production image - only take pruned assets
FROM node:18-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
RUN addgroup --system --gid 1001 app
RUN adduser --system --uid 1001 app
USER app

COPY --chown=app:app --from=builder /app/apps/frontend/ /

EXPOSE 5000
ENV PORT 5000

CMD ["npm", "start"]

Here is another example using yarn

FROM node:18-alpine as base

FROM base as with_all_deps
WORKDIR /app
COPY . ./
RUN yarn
RUN yarn build
RUN yarn cache clean --all

FROM node:18-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
RUN addgroup --system --gid 1001 app
RUN adduser --system --uid 1001 app
USER app

COPY --chown=app:app --from=with_all_deps /app ./

EXPOSE 5000
ENV PORT 5000

CMD ["yarn", "start"]