Dockerfile优化体验

选好镜像,减少最终image体积

Whenever possible, use current Official Repositories as the basis for your image. We recommend the Alpine image since it’s very tightly controlled and kept minimal (currently under 5 mb), while still being a full distribution.

上面这段是来自Docker的官方文档,言语之中极力推荐我们使用Alpine Linux,它的特点是短小精悍,只有不到5M。Base镜像的体积会对最后Build处理的镜像的体积有非常大的影响。举个Docker官方的Node镜像的例子不同Base镜像的Size分别是这样的

ImageSize
8.11-alpine23M
8.11-slim92 MB
8.11266 MB

太明显了

用好Docker cache,加速构建

Docker image 构建的时候会对当前构建的层(Layer)做缓存,当前层的上一层没有变的时候,会直接在上一层的基础上进行Build,这样的话能大大加快后续的构建速度。当然我们可以强制不使用缓存,在docker build命令后加上 --no-cache=true就可以强制让Docker从头开始构建。 先举个例子,假设我们的目录是这样的

web
├── Dockerfile
├── config
├── node_modules
├── package.json
├── src
└── yarn.lock

不太好的Dockerfile里面是这样的

FROM node:8.11.1-alpine
LABEL maintainer="Iron Lu <lrironsora@gmail.com>"
WORKDIR ./app
COPY ./ ./
RUN yarn install && yarn build --production
...

上面这段代码最大的问题是。会有很大一部分情况,我们是修改了src目录,但没有更改项目的依赖的,但是yarn install在每一轮安装的时候都被行了一遍,每次装依赖的时间开销是完全可以用Docker cache避免的。 正确的做法如下

FROM node:8.11.1-alpine
LABEL maintainer="Iron Lu <lrironsora@gmail.com>"
WORKDIR ./app
COPY package.json yarn.lock ./
# Copy package.json和yarn.lock到镜像中
# 当项目的依赖没有改变,仅改变了src中的代码时
# 接下来的构建会直接通过缓存,从下面开始
RUN yarn install
COPY ./ ./
RUN yarn install && yarn build --production

再说一个用错Docker cache的例子

FROM ubuntu:14.04
RUN apt-get update
RUN apt-get install -y curl nginx

RUN apt-get update那一行会被缓存,导致我们无法安装最新版的package。 所以,在官方文档中,特别强调了这样的一句

Always combine RUN apt-get update with apt-get install in the same RUN statement.

正确的做法当然是

RUN apt-get update && apt-get install -y \
    package-bar \
    package-baz \
    package-foo

移除不必要的文件,进一步减少image体积

使用.dockerignore文件来忽略那些不应该被打包进来的文件。

显然,node_modules是绝对不能放进来的。这部分是由项目决定的。凡是与镜像构建或容器运行无关的,都不应该被放进来。 下面是我项目中的例子

dist/
node_modules/
Dockerfile
.eslintrc.yml
README.md
sass-lint.yml
yarn-error.log
config/webpack/public

删除镜像内不会再用到的文件

举个例子,我们在Docker中使用webpack 构建了dist文件,那么src就不会再被用到。而且node_modules里的一些依赖也不会再被用到,比如babel, webpack等,那么我们就可以把它移除掉来缩减体积。 关于怎么移除node_modules里无用的依赖,我的做法是:将所有Build相关的依赖放入package.jsondevDependencies中,在dependencies中只放静态文件服务器所需要的2个package: expressconnect-history-api-fallback

这也许是个不好的实现

我的Dockerfile如下

WORKDIR ./app
ADD package.json ./ yarn.lock ./ package-lock.json ./
RUN yarn install

COPY ./ ./
RUN yarn build:prod &&\
  yarn install --production &&\
  # 删除node_moudules无用依赖
  yarn cache clean &&\
  # 这一步可以清掉yarn的缓存
  rm -rf config/webpack/public

当执行yarn install --production时,node_modules中无用的package都会被移除掉,移除前后的体积对比如下

~/foo/ du -hs node_modules
357M    node_modules
~/foo/ yarn install --production
✨  Done in 7.31s.
~/foo du -hs node_modules
2.2M    node_modules

差不多少了350M,减肥是很有效果的。

移除安装过程中的下载的包

rm -rf /var/lib/apt/lists/*
# 删除Ubuntu apt-get 的缓存
yum clean all
# 删除CentOS yum的缓存
yarn clean
# 删除yarn的缓存

参考资料

Best practices for writing Dockerfiles

Tips to Reduce Docker Image Sizes