Dockerfile 最佳实践
目录
Docker 通过读取我们编写的 Dockerfile
来自动构建一个镜像,镜像的每一层对应着 Dockerfile
的指令,我们应该遵循一些规则和最佳实践来保证构建出更加高效的镜像。
FROM ubuntu:18.04
COPY . /app
RUN
make /app
CMD python /app/app.py
每一行指令都会创建一个层:
- FROM:使用
ubuntu:18.04
作为第一层; - COPY:将当前目录的文件复制到镜像;
- RUN:使用
make
构建程序; - CMD:启动容器时运行的命令;
基础
创建“短暂的”容器
短暂意味着容器基本不需要安装和配置就可以停止并销毁,然后重建和替换原来的容器。
构建上下文
构建上下文(Build Context)指的是在执行 docker build
命令时所在的目录。
默认情况下docker会直接使用当前目录下的 Dockerfile,也可以通过 -f
参数来指定。
不过无论如何,在构建的时候当前目录以及子目录下的所有文件(即构建上下文)都会发送到 Docker 守护进程,所以为了加快构建速度,对于不需要的文件和目录,可以通过 .dockerignore
来忽略掉。
从标准输入读取Dockerfile
通过管道(pipe),Docker可以直接从标准输入 stdin
来读取Dockerfile并构建镜像,在进行一些一次性构建、动态生成Dockerfile并且不想写入磁盘时非常有用。比如:
echo -e 'FROM busybox\nRUN echo "hello world"' | docker build -
或者:
docker build -<<EOF
FROM busybox
RUN echo "hello world"
EOF
在这种情况,如果不需要发送构建上下文(build context),可以直接用 -
代替原来的 PATH
参数:
docker build [OPTIONS] -
如果是使用本地目录作为上下文,则需要用 -f
参数来指定Dockerfile,即:-f-
docker build -t myimage:latest -f- . <<EOF
FROM busybox
COPY somefile.txt ./
RUN cat /somefile.txt
EOF
甚至还可以使用远程git,比如:
docker build -t myimage:latest -f- https://github.com/docker-library/hello-world.git <<EOF
FROM busybox
COPY hello.c ./
EOF
使用.dockerignore
跟构建上下文无关的内容,可以通过 .dockerignore
排除掉,格式跟 .gitignore
基本一致。
使用多阶段构建
可以看这篇笔记。
不要安装非必要的包
对于一些”有当前更好“的包,不要安装到镜像中,这样可以大大减少镜像的复杂度、依赖、体积以及构建时间。
解耦应用程序
每个容器应该只关心一件事,把复杂的应用程序分成多个容器可以更加方便的进行水平扩缩容以及重用。
最小化层数
- 只有RUN、COPY、ADD会创建新的层,其他指令指挥增加临时中间层而不会增加构建的大小;
- 使用多阶段构建,只将需要的内容复制到最终镜像中;
对多行参数进行排序
有时候一个RUN指令会带非常多的参数,这时候有必要让每个参数独占一行并进行排序,这样子可以大大降低维护成本。
RUN apt-get update && apt-get install -y \
bzr \
cvs \
git \
mercurial \
subversion \
&& rm -rf /var/lib/apt/lists/*
Dockerfile 指令
FROM
尽可能地使用官方镜像(official images),也推荐使用 apline
镜像来减小体积。
LABEL
使用标签可以有效地对项目的镜像进行组织,具体参考文档。
RUN
将复杂的指令分为多行进行书写,每一行用 \
结尾。
- apt-get:必须将
apt-get update
和apt-get install
写在同一行,否则可能会因为缓存导致问题; - 使用管道
pipe
:多个指令之间有依赖,必须使用管道 |
来串联;
CMD
CMD
指令用来执行镜像中的程序,可以带参数,比如:CMD ["executable", "param1", "param2"...]
。
一般情况下不要将 CMD
和 ENTRYPOINT
两个指令混用,除非你的用户熟悉 ENTRYPOINT
。不过Docker Official Images 推荐这种写法则是将两者混用了。
EXPOSE
EXPOSE
指定了容器会在哪个端口上监听连接,一般情况都应该用传统的端口(比如Web Server用80)。
ENV
用来指定环境变量。
ADD、COPY
两者的功能类似,通常来讲更加推荐使用 COPY
,因为 ADD
除了复制文件还有其他功能(比如本地解压,拉取远程数据)。比如:
ADD rootfs.tar.xz /
ENTRYPOINT
最主要的用法,ENTRYPOINT
设置镜像的主执行命令,CMD
设置默认参数,比如:
ENTRYPOINT ["s3cmd"]
CMD ["--help"]
这样子就可以直接通过 docker run 来执行命令了:
$ docker run s3cmd
$ docker run s3cmd ls s3://bucket
WORKDIR
为了更加明确和可靠,WORKDIR
应该永远使用绝对路径,而不是使用类似 RUN cd ... && do-something
。
ONBUILD
ONBUILD
是在当前Dockerfile构建完成后才会执行,在子镜像通过 FROM
继承当前镜像的时候执行,具体可以参考文档。
参考:https://docs.docker.com/develop/develop-images/dockerfile_best-practices/
评论