Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I reduce my Docker Image size for deployment?

Tags:

node.js

docker

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" ]
like image 830
Diskdrive Avatar asked Sep 10 '18 14:09

Diskdrive


People also ask

Can we compress docker image?

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.

Why is my docker image so large?

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.

How do I reduce the size of a docker image in Windows?

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.


1 Answers

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.)

like image 67
David Maze Avatar answered Sep 24 '22 17:09

David Maze