Background

To run a nuxt.js server in production mode, one needs to ‘build’ it first using nuxt build. This generates a node server to host the app and serve the assets as well as generating a ‘production’ build of the clientside assets.

These build time dependencies are not needed for running the server in production and contribute a lot to the final docker image size.

In order to both build and run the nuxt server within the same Dockerfile, the below sample will make use of Docker’s multi stage build feature

changes

In the below case, the nuxt client for ammobin.ca will used.

Old dockerfile (src)

FROM node:12-alpine
RUN apk --no-cache add wget git g++ make python

WORKDIR /build
COPY package*.json /build/
RUN npm install #--production
RUN apk --no-cache del git g++ make python
COPY . /build

EXPOSE 3000
RUN npm run build
HEALTHCHECK --interval=30s --timeout=1s CMD wget localhost:3000/ping -q  -O/dev/null || exit 1

CMD npm start

build size 732MB (304MB compressed)

New dockerfile (src)

####
# build: pull in + install everything to run nuxt build
####

FROM node:12-alpine as build
RUN apk --no-cache add wget git g++ make python

WORKDIR /build
COPY package*.json /build/
RUN npm install
RUN apk --no-cache del git g++ make python
COPY . /build

RUN npm run build


########
# run: do production install + copy build output of build container and run the node server
########
FROM node:12-alpine as main

WORKDIR /build
COPY --from=build /build/package*.json /build/
RUN npm install --production

# copy min needed to run (built) app
COPY --from=build /build/nuxt.config.ts /build
COPY --from=build /build/.nuxt /build/.nuxt
COPY --from=build /build/static /build/static
RUN apk --no-cache add wget

EXPOSE 3000

HEALTHCHECK --interval=30s --timeout=1s CMD wget localhost:3000/ping -q  -O/dev/null || exit 1

USER node
CMD npm start

build size 295MB (110MB compressed)

conclusion

By isolating the build dependencies from the server dependencies, the final image size was reduced by 60% (64% compressed). While the old build size was not a blocking issue, the reduced size speeds up updates, reduces downtime, and saves some disk space (ammobin.ca runs on a single 20GB VPS using docker-compose).