Taking advantage of multi-stage builds can significantly reduce the size of container images. The following is an example of a Nuxt.js application.
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
nuxt single-stage fba421d5de5b About a minute ago 371MB
nuxt multi-stage a40d0000d0a8 10 minutes ago 22MB
Create a Nuxt.js application with npx create-nuxt-app <app name>
. The composition is free. No matter how you set it, you will get the same result with some differences in size.
Dockerfile-Single
FROM node:lts-alpine
WORKDIR /app
COPY . ./
RUN npm install -g http-server && \
npm install && \
npm run build
EXPOSE 8080
CMD [ "http-server", "dist" ]
For convenience, non-multistage builds are referred to as single stage builds in this article. I didn't understand the correct term, so please let me know if you are familiar with it!
I brought in http-server
to run the application and put the built files on it.
Dockerfile-Multi
FROM node:lts-alpine AS build-stage
WORKDIR /app
COPY . ./
RUN npm install && \
npm run build
FROM nginx:stable-alpine AS production-stage
COPY --from=build-stage /app/dist /usr/share/nginx/html
EXPOSE 80
CMD [ "nginx", "-g", "daemon off;" ]
The build stage uses the node.js image, and the execution stage uses the nginx image. You can name the stage with ʻasand specify which stage the file is from with
--from`.
I put the directory finally generated in the build stage in the document root of nginx.
.dockerignore
.dockerignore
node_modules
dist
Dockerfile*
The Dockerfile is like an error, but node_modules
and dist
swell depending on what you use. Exclude it during the image creation stage as it will be run inside the container by npm install
and npm build
respectively.
$ docker build -t nuxt:single-stage --file Dockerfile-Single .
$ docker run -dit -p 8080:8080 --name nuxt-single nuxt:single-stage
$ docker exec nuxt-single wget -S -O- localhost:8080
Connecting to localhost:8080 (127.0.0.1:8080)
HTTP/1.1 200 OK
(Omitted below: OK if HTML is returned as response data!)
$ docker build -t nuxt:multi-stage --file Dockerfile-Multi .
$ docker run -dit -p 80:80 --name nuxt-multi nuxt:multi-stage
$ docker exec nuxt-multi wget -S -O- localhost:80
Connecting to localhost:80 (127.0.0.1:80)
HTTP/1.1 200 OK
(Omitted below: OK if HTML is returned as response data!)
If you compare the image sizes at this point, you'll get results like the one at the beginning.
In the example given this time, the size of the image is reduced by discarding what is necessary when building the application but not when executing it. In other words, it can be said that multi-stage build is effective when creating an image of a container that requires only an execution environment. For example, a container for CI / CD only needs to be a build product, but rather files required only at build time will cause the container size to increase unnecessarily. It's a good use for multi-stage builds.
You can reduce the network communication caused by npm install
by removing node_modules
from .dockerignore
and omitting npm install
. There is not much difference in time between sending node_modules
to the Docker daemon once and running npm install
, depending on your network environment.
Also, when using a multi-stage build, the intermediate image remains as <none: none>
.
To avoid this, do one of the following:
--target
option (of course the tagged images will remain)docker image prune
after successfully building the last imageIn Related GitHub Issue, it is truncated by "Specification (free translation)", but it can not be managed as an option, or the image builder is updated. It seems that there was a debate about whether it could be dealt with. Currently, it seems that the main line is to make the new builder (BuildKit) the default. (Personally, even if I use BuildKit, I wonder how much difference there is between docker image prune and docker builder prune ...? .. Only intermediate images and caches corresponding to a specific build I want to delete)
Recommended Posts