So I've just created a very basic Node app. I want to practice putting it into a docker container and deploying it on another server
I'm using the steps here (https://nodejs.org/en/docs/guides/nodejs-docker-webapp/) to create a dockerfile that builds a docker image.
I want to take this image and copy it to the server.
So I'm running this command to save it to a tar file which I would copy to the server.
docker save -o <save image to path> <image name>
Upon running that command, my image is 750Mbs in size - this is for a hello world node application.
So I kind of get why this is the case - that 750Mb is for all the layers described in the dockerfile, include node:8 which basically contains the OS to run Node in.
My question is, 750mb is a very big file every time I want to do a deployment. Is it possible to tell docker when running the SAVE command not to package up all the layers? Preferably, it should only package up my custom files for my application plus the dockerfile and it would build the image on the server.
EDIT - my docker file
FROM node:8
# Create app directory
WORKDIR /usr/src/app
# Install app dependencies
# A wildcard is used to ensure both package.json AND package-lock.json are copied
# where available (npm@5+)
COPY package*.json ./
RUN npm install
# If you are building your code for production
# RUN npm install --only=production
# Bundle app source
COPY . .
CMD [ "node", "app.js" ]
docker images are compressed by default. you will notice when running docker pull , where it will download the needed images\layers and then extract\decompress them. there is no need for you to compress the files within your docker images.
A Docker image takes up more space with every layer you add to it. Therefore, the more layers you have, the more space the image requires. Each RUN instruction in a Dockerfile adds a new layer to your image. That is why you should try to do file manipulation inside a single RUN command.
Remove excess files If there's a file in your Dockerfile, such as an installer, that you don't need after it's been used, you can remove it to reduce image size. This needs to occur in the same step in which the file was copied into the image layer.
I took one of my current projects and basically ran your Dockerfile...and got a 1.1 GB docker save
tar file. It compresses well, but a 345 MB gzipped tar file still isn't what you're after. "Use an Alpine base image" is somewhat helpful, but not a silver bullet; switching it to be FROM node:8-alpine
reduces it to a 514 MB uncompressed tar file.
If you don't already have a .dockerignore
file, then your entire existing node_modules
directory will get copied into the image. (In particular, the COPY . .
step will copy your entire working tree in, overwriting the installed modules from the previous step.) It can just contain the single line
node_modules
The Alpine base image, plus not emitting a duplicate node_modules
tree, gets me down to 382 MB uncompressed.
Notice in your example that your npm install
step includes all of the development dependencies as well as the runtime dependencies. That can be a significant cost. If your application doesn't need any precompilation (it is plain JavaScript that Node can run directly) then you can add that --only=production
flag and it will help.
If you do need some level of precompilation (Babel, Webpack, Typescript, ...) then you need a multi-stage build. My actual Dockerfile has three stages. The first does the compilation, and produces a dist
directory with runnable JavaScript. The second produces the node_modules
tree I need at runtime. The third (since we're counting bytes here) copies only the parts we really need from the first two stages.
The sum looks like:
FROM node:8-alpine AS build
WORKDIR /usr/src/app
# Installing dependencies first can save time on rebuilds
# We do need the full (dev) dependencies here
COPY package.json yarn.lock ./
RUN yarn install
# Then copy in the actual sources we need and build
COPY tsconfig.json ./
COPY src/ ./src/
RUN yarn build
FROM node:8-alpine AS deps
WORKDIR /usr/src/app
# This _only_ builds a runtime node_modules tree.
# We won't need the package.json to actually run the application.
# If you needed developer-oriented tools to do this install they'd
# be isolated to this stage.
COPY package.json yarn.lock ./
RUN yarn install --production
FROM node:8-alpine
WORKDIR /usr/src/app
COPY --from=deps /usr/src/app/node_modules ./node_modules/
COPY --from=build /usr/src/app/dist ./dist/
EXPOSE 3000
CMD ["node", "dist/index.js"]
docker save
on this produces a 108 MB uncompressed tar file.
One thing you will notice, if you docker save node:8-alpine
, is that that image is 71 MB on its own. When you docker save
you are forced to copy that every time, and when you docker load
it you will get a distinct copy of it every time. The only way around this is to have a registry server of some sort (Docker Hub, a cloud-hosted thing like Google GCR or AWS ECR, something you run yourself) and docker push
and docker pull
from there.
It turns out the node:8
image is huge; half its size is a full C toolchain (that single layer is 320 MB alone) (try running docker history node:8
). The standard node image also has a node:8-slim
variant which is still Debian-based (and so larger than the Alpine image) but much tidier. I get a 221 MB docker save
tar file adapting my Dockerfile to be based on this image. (And again if you docker pull
it you'll get the base Node runtime once, but if you docker load
it you'll get that over and over again.)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With