01. 基础镜像考虑社区官方发布的镜像,定期更新版本

如无特殊要求,基础镜像推荐使用alpine 镜像,尽量考虑官方发布的镜像。
如无需安装依赖包,推荐使用scratch基础镜像。
跟踪社区的发布情况,定期更新镜像的版本,确保使用镜像的安全可信。不要使用最新版本发布到生成环境,避免基础镜像引入故障。

02. 基础镜像必须指定镜像tag,不能使用latest

不指定tag,默认为latest,latest版本无法做到版本控制(scratch基础镜像、多阶段构建这两种情况可以不指定tag)

  1. ## 错误示例:
  2. FROM alpine
  3. ## 正确示例:
  4. FROM alpine:3.9

03. MAINTAINER 在docker 17.x以上版本逐步废弃

推荐使用LABEL代替MAINTAINER。

  1. ## 错误示例:
  2. MAINTAINER xxxx.xxx@xxx.xxx.xx
  3. ## 正确示例:
  4. LABEL maintainer xxxx.xxx@xxx.xxx.xx

04. 将多条RUN命令合并执行,多条ENV命令合并执行

将多条RUN命令合并执行,每行写一条命令,减少RUN命令的数量。 将多条ENV命令合并执行,每行写一条命令,减少ENV命令的数量。
注意:不是要求只能使用1条RUN或ENV命令。
重要意义:可以减少镜像层数,减少构建镜像过程中启动容器的次数,缩短构建镜像的时间。

  1. ## 错误示例:
  2. RUN mkdir -p /app/conf && chown -R 1230:1230 /app
  3. RUN chmod 755 /home/run.sh
  4. ## 正确示例:
  5. RUN mkdir -p /app/conf \
  6. && chown -R 1230:1230 /app \
  7. && chmod 755 /home/run.sh
  8. ## -----------
  9. ## 错误示例:
  10. ENV DEBUG_PORT=8777
  11. ENV HTTP_PORT=12080
  12. ENV PROGRAM_FILE_CONF=/home/conf
  13. ## 正确示例:
  14. ENV DEBUG_PORT=8777 \
  15. HTTP_PORT=12080 \
  16. PROGRAM_FILE_HOME_CONF=/home/conf

05. 不要使用apk upgrade命令

使用upgrade会升级已安装的依赖包,因此,不要使用apk upgrade命令。

06. 使用apk add安装依赖包,指定版本号,使用无缓存方式,不使用apk update

安装依赖包,一定要指定版本号,控制版本。无缓存方式安装,可以避免update后再清除缓存。

  1. ## 错误示例:
  2. RUN apk update \
  3. && apk add foo=1.0 \
  4. && rm -rf /var/cache/apk/*
  5. RUN apk add --update foo=1.0
  6. && rm -rf /var/cache/apk/*
  7. ## 正确示例:
  8. RUN apk --no-cache add foo=1.0

07. 不要使用apt-get upgrade命令

  1. ## 错误示例:
  2. RUN apt-get upgrade \
  3. && apt-get install python=2.7
  4. ## 正确示例:
  5. RUN apt-get install python=2.7

08. 使用apt-get install安装依赖包要指定版本号

  1. ## 错误示例:
  2. RUN apt-get install python
  3. ## 正确示例:
  4. RUN apt-get install python=2.7

09. 安装依赖包后,需要删除 apt-get lists

  1. ## 错误示例:
  2. RUN apt-get update && apt-get install -y python
  3. ## 正确示例:
  4. RUN apt-get update && apt-get install -y python=2.7 \
  5. && apt-get clean \
  6. && rm -rf /var/lib/apt/lists/*

10. 不使用apt命令, 使用apt-get命令

  1. ## 错误示例:
  2. RUN apt install curl=1.1.0
  3. ## 正确示例:
  4. RUN apt-get install curl=1.1.0

11. 使用pip安装依赖包指定安装版本,使用无缓存方式

  1. ## 错误示例:
  2. RUN pip install django
  3. ## 正确示例:
  4. RUN pip install --no-cache-dir django==1.9

12. 使用npm指定版本安装

  1. ## 错误示例:
  2. RUN npm install express
  3. RUN npm install @myorg/privatepackage
  4. RUN npm install express sax@0.1.1
  5. ## 正确示例:
  6. RUN npm install express@4.1.1 \
  7. && npm install @myorg/privatepackage@">=0.1.0" \
  8. && npm install express@"4.1.1" sax@0.1.1

13. gem指定版本安装依赖包

  1. ## 错误示例:
  2. RUN gem install bundler
  3. ## 正确示例:
  4. RUN gem install bundler:1.1

14. 拷贝多个文件到同一个目标路径,使用一条COPY命令,最后一个参数目标目录使用/结尾

  1. ## 错误示例:
  2. COPY ansible-playbook /usr/local/bin/
  3. COPY ansible /usr/local/bin/
  4. COPY libmysqlclient.so /usr/lib/x86_64-linux-gnu/
  5. COPY libmysqlclient.so.18 /usr/lib/x86_64-linux-gnu/
  6. COPY libmysqlclient.so.18.0.0 /usr/lib/x86_64-linux-gnu/
  7. ## 正确示例:
  8. COPY ansible-playbook ansible /usr/local/bin/
  9. COPY libmysqlclient.so libmysqlclient.so.18 libmysqlclient.so.18.0.0 /usr/lib/x86_64-linux-gnu/

15. 使用ADD命令解压压缩包

  1. ## 错误示例:
  2. COPY posd-*.tar.gz /tmp/
  3. RUN cd /tmp && tar -zxvf posd-*.tar.gz && rm -fr posd-*.tar.gz
  4. ## 正确示例:
  5. ADD posd-*.tar.gz /tmp/

16. 有效的UNIX端口范围0 to 65535

  1. ## 错误示例:
  2. EXPOSE 80000
  3. ## 正确示例:
  4. EXPOSE 65535

17. 强制安装依赖包

  1. ## 错误示例:
  2. RUN apt-get install python=2.7
  3. ## 正确示例:
  4. RUN apt-get install -y python=2.7

18. 避免安装额外的依赖包

  1. ## 错误示例:
  2. RUN apt-get install -y python=2.7
  3. ## 正确示例:
  4. RUN apt-get install -y --no-install-recommends python=2.7

19. 挂载宿主机目录到容器内,不要对容器内目录执行mkdir、chown、chmod操作

  1. 宿主机指定的日志目录(/paasdata/oplog/*),配置目录(/paasdata/opconf/*)等,需要在宿主机完成mkdir、chown和chmod。
  2. ## 错误示例:
  3. RUN mkdir -p /paasdata/oplog/cnrm-manager \
  4. && mkdir -p /paasdata/opconf/cnrm-manager \
  5. && addgroup -g 1340 -S cnrm-manager \
  6. && adduser cnrm-manager -D -s /sbin/nologin -G cnrm-manager -u 1340 \
  7. && chown -R cnrm-manager:cnrm-manager /paasdata/oplog/cnrm-manager /paasdata/opconf/cnrm-manager
  8. ## 正确示例:
  9. RUN addgroup -g 1340 -S cnrm-manager \
  10. && adduser cnrm-manager -D -s /sbin/nologin -G cnrm-manager -u 1340

20. WORKDIR 要使用绝对路径

  1. ## 错误示例:
  2. WORKDIR usr/src/app
  3. ## 正确示例:
  4. WORKDIR /usr/src/app

21. 使用WORKDIR 切换目录

  1. ## 错误示例:
  2. RUN cd /usr/src/app
  3. ## 正确示例:
  4. WORKDIR /usr/src/app

22. 不要使用ssh, vim, shutdown, service, ps, free, top, kill, mount, ifconfig等命令

  1. ## 错误示例:
  2. FROM busybox
  3. RUN top

23. 容器内普通用户运行进程

一般业务容器内使用普通用户运行业务进程,使用USER命令切换用户或su-exec切换到普通用户。

  1. ## 正确示例:
  2. RUN addgroup -g 1340 -S cnrm-manager \
  3. && adduser cnrm-manager -D -s /sbin/nologin -G cnrm-manager -u 1340
  4. USER cnrm-manager:cnrm-manager

24. 不要使用sudo,使用root用户安装依赖包

  1. ## 错误示例:
  2. RUN sudo apt-get install foo==1.0
  3. ## 正确示例:
  4. RUN apt-get install foo==1.0

25. 使用COPY代替ADD 添加文件和目录

  1. ## 错误示例:
  2. ADD requirements.txt /usr/src/app/
  3. ## 正确示例:
  4. COPY requirements.txt /usr/src/app/

26. COPY —from 需要使用已定义的FROM 别名

  1. ## 错误示例:
  2. FROM debian:jesse
  3. RUN stuffF
  4. FROM debian:jesse
  5. COPY --from=build some stuff ./
  6. ## 正确示例:
  7. FROM debian:jesse as build
  8. RUN stuff
  9. FROM debian:jesse
  10. COPY --from=build some stuff ./

27. COPY —from 不能引用基础镜像的别名

  1. ## 错误示例:
  2. FROM debian:jesse as build
  3. COPY --from=build some stuff ./
  4. ## 正确示例:
  5. FROM debian:jesse as build
  6. RUN stuff
  7. FROM debian:jesse
  8. COPY --from=build some stuff ./

28. 多阶段构建别名必须是唯一的

  1. ## 错误示例:
  2. FROM debian:jesse as build
  3. RUN stuff
  4. FROM debian:jesse as build
  5. RUN more_stuff
  6. ## 正确示例:
  7. FROM debian:jesse as build
  8. RUN stuff
  9. FROM debian:jesse as another-alias
  10. RUN more_stuff

29. CMD和ENTRYPOINT使用exec格式

如果容器内业务进程无法响应SIGTERM信号,那么无法实现优雅退出。docker stop停止容器时,dockerd向容器内进程发送SIGTERM信号,终止业务。如果容器内1号进程,无法响应SIGTERM信号,则dockerd向让容器内进程发送SIGKILL信号。

  1. ## 错误示例:
  2. ENTRYPOINT s3cmd
  3. ENTRYPOINT /entrypoint.sh
  4. CMD my-service server
  5. CMD /run.sh
  6. ## 正确示例:
  7. ENTRYPOINT ["s3cmd"]
  8. ENTRYPOINT ["/entrypoint.sh"]
  9. CMD ["my-service", "server"]
  10. CMD ["/run.sh"]

30. 使用可信镜像仓库registry

  1. ## 错误示例:
  2. FROM randomguy/python:3.6
  3. ...
  4. ## 正确示例:
  5. FROM my-registry.com/python:3.6
  6. ...

31. 不要同时使用wget和curl

  1. ## 错误示例:
  2. RUN wget http://google.com
  3. RUN curl http://bing.com
  4. ## 正确示例:
  5. RUN curl http://google.com
  6. RUN curl http://bing.com

32. 不要使用多个CMD

使用多个CMD ,只有最后一个CMD生效。

  1. ## 错误示例:
  2. CMD /bin/true
  3. CMD /bin/false
  4. ## 正确示例:
  5. CMD /bin/false

33. 不要使用多个ENTRYPOINT

使用多个ENTRYPOINT ,只有最后一个ENTRYPOINT 生效。

  1. ## 错误示例:
  2. ENTRYPOINT /bin/true
  3. ENTRYPOINT /bin/false
  4. ## 正确示例:
  5. ENTRYPOINT /bin/false

34. 不使用默认shell配置

RUN 指令中使用管理操作符 | ,Docker 只关注最后一个命令执行与否成功。如下错误示例,即使wget 失败,wc成功也会成功构建镜像。
因此,如果希望执行过程中产生任何错误都失败,需要设置pipefail。

  1. ## 错误示例:
  2. RUN wget -O - https://some.site | wc -l > /number
  3. ## 正确示例:
  4. RUN set -o pipefail \
  5. && wget -O - https://some.site | wc -l > /number

35. 非root运行业务,使用COPY —chown

容器内非root运行业务,要对目录和文件chown,增加镜像大小。构建镜像时,使用COPY —chown,使用 buildkit 或docker v17.x及以上版本。

  1. ## 错误示例:
  2. COPY data /home/conf
  3. RUN addgroup -g 1340 -S app \
  4. && adduser app -D -s /sbin/nologin -G app -u 1340 \
  5. && mkdir /home/conf
  6. && chown -R app:app /home/conf
  7. ## 正确示例:
  8. RUN addgroup -g 1340 -S app \
  9. && adduser app -D /home/app -s /sbin/nologin -G app -u 1340 \
  10. COPY --chown app:app data /home/app/conf

36. CMD和ENTRYPOINT使业务进程响应SIGTERM信号,实现优雅退出

如果容器内业务进程无法响应SIGTERM信号,那么无法实现优雅退出。docker stop停止容器时,dockerd向容器内进程发送SIGTERM信号,终止业务。如果容器内1号进程,无法响应SIGTERM信号,则dockerd向让容器内进程发送SIGKILL信号。

  1. ## 错误示例:
  2. shell格式,容器内1号进程是shsh进程无法响应SIGTERM信号
  3. ENTRYPOINT /entrypoint.sh
  4. 或者
  5. CMD /run.sh
  6. ## 正确示例:
  7. exce格式1:使容器内业务进程成为1号进程,可以响应SIGTERM信号
  8. ENTRYPOINT ["s3cmd"]
  9. 或者
  10. CMD ["s3cmd"]
  11. exec格式2:使用exec将业务进程替换为容器内1号进程
  12. ENTRYPOINT ["/entrypoint.sh"]
  13. entrypoint.sh脚本中使用 exec @
  14. #!/bin/sh
  15. if [ -d "/my-project/logs" ]; then
  16. cd /my-project
  17. chown app:app -R logs
  18. fi
  19. # switch user and run your applicaiton (top is a demo app)
  20. set -- su-exec app:app top 2>&1
  21. exec "$@"
  22. exec格式3:使用exec将业务进程替换为容器内1号进程
  23. CMD exec ${app_path}/start.sh
  24. start.sh 脚本中使用 exec 启动业务进程
  25. #!/bin/bash
  26. ...
  27. exec /app/opsapiserver -pthreshold=${log_level} >> ${app_path}/logs/opsapiserver/opsapiserver.log 2>&1
  28. 自定义函数:sh脚本作为容器内1号进程,自定义函数响应SIGTERM信号
  29. ENTRYPOINT ["/run.sh"]
  30. run.sh 脚本中自定义函数
  31. #!/bin/sh
  32. set -x
  33. pid=0
  34. # SIGTERM-handler
  35. term_handler() {
  36. if [ $pid -ne 0 ]; then
  37. kill -SIGTERM "$pid"
  38. wait "$pid"
  39. fi
  40. exit 143; # 128 + 15 -- SIGTERM
  41. }
  42. # setup handlers
  43. trap 'term_handler' SIGTERM
  44. # run application
  45. app &
  46. pid="$!"
  47. wait