基础镜像的选择 (FROM)
基本原则
- 官方镜像优于非官方的镜像,如果没有官方镜像,则尽量选择Dockerfile开源的
- 固定版本tag而不是每次都使用 latest
- 尽量选择体积小的镜像
$ docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
bitnami/nginx 1.18.0 dfe237636dde 28 minutes ago 89.3MB
nginx 1.21.0-alpine a6eb2a334a9f 2 days ago 22.6MB
nginx 1.21.0 d1a364dc548d 2 days ago 133MB
Build一个Nginx镜像
假如我们有一个 index.html 文件
<h1>Hello Docker</h1>
准备一个Dockerfile
FROM nginx:1.21.0-alpine
ADD index.html /usr/share/nginx/html/index.html
通过 RUN 执行指令
RUN 主要用于在Image里执行指令,比如安装软件,下载文件等。
$ apt-get update
$ apt-get install wget
$ wget https://github.com/ipinfo/cli/releases/download/ipinfo-2.0.1/ipinfo_2.0.1_linux_amd64.tar.gz
$ tar zxf ipinfo_2.0.1_linux_amd64.tar.gz
$ mv ipinfo_2.0.1_linux_amd64 /usr/bin/ipinfo
$ rm -rf ipinfo_2.0.1_linux_amd64.tar.gz
Dockerfile
FROM ubuntu:21.04
RUN apt-get update
RUN apt-get install -y wget
RUN wget https://github.com/ipinfo/cli/releases/download/ipinfo-2.0.1/ipinfo_2.0.1_linux_amd64.tar.gz
RUN tar zxf ipinfo_2.0.1_linux_amd64.tar.gz
RUN mv ipinfo_2.0.1_linux_amd64 /usr/bin/ipinfo
RUN rm -rf ipinfo_2.0.1_linux_amd64.tar.gz
镜像的大小和分层
# 执行指令
$ docker image buil -f Dockerfile.bad -t ipinfo .
$ docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
ipinfo latest 97bb429363fb 4 minutes ago 138MB
ubuntu 21.04 478aa0080b60 4 days ago 74.1MB
$ docker image history 97b
IMAGE CREATED CREATED BY SIZE COMMENT
97bb429363fb 4 minutes ago RUN /bin/sh -c rm -rf ipinfo_2.0.1_linux_amd… 0B buildkit.dockerfile.v0
<missing> 4 minutes ago RUN /bin/sh -c mv ipinfo_2.0.1_linux_amd64 /… 9.36MB buildkit.dockerfile.v0
<missing> 4 minutes ago RUN /bin/sh -c tar zxf ipinfo_2.0.1_linux_am… 9.36MB buildkit.dockerfile.v0
<missing> 4 minutes ago RUN /bin/sh -c wget https://github.com/ipinf… 4.85MB buildkit.dockerfile.v0
<missing> 4 minutes ago RUN /bin/sh -c apt-get install -y wget # bui… 7.58MB buildkit.dockerfile.v0
<missing> 4 minutes ago RUN /bin/sh -c apt-get update # buildkit 33MB buildkit.dockerfile.v0
<missing> 4 days ago /bin/sh -c #(nop) CMD ["/bin/bash"] 0B
<missing> 4 days ago /bin/sh -c mkdir -p /run/systemd && echo 'do… 7B
<missing> 4 days ago /bin/sh -c [ -z "$(apt-get indextargets)" ] 0B
<missing> 4 days ago /bin/sh -c set -xe && echo '#!/bin/sh' > /… 811B
<missing> 4 days ago /bin/sh -c #(nop) ADD file:d6b6ba642344138dc… 74.1MB
每一行的RUN命令都会产生一层image layer, 导致镜像的臃肿。
改进版Dockerfile
FROM ubuntu:21.04
RUN apt-get update && \
apt-get install -y wget && \
wget https://github.com/ipinfo/cli/releases/download/ipinfo-2.0.1/ipinfo_2.0.1_linux_amd64.tar.gz && \
tar zxf ipinfo_2.0.1_linux_amd64.tar.gz && \
mv ipinfo_2.0.1_linux_amd64 /usr/bin/ipinfo && \
rm -rf ipinfo_2.0.1_linux_amd64.tar.gz
$ docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
ipinfo-new latest fe551bc26b92 5 seconds ago 124MB
ipinfo latest 97bb429363fb 16 minutes ago 138MB
ubuntu 21.04 478aa0080b60 4 days ago 74.1MB
$ docker image history fe5
IMAGE CREATED CREATED BY SIZE COMMENT
fe551bc26b92 16 seconds ago RUN /bin/sh -c apt-get update && apt-get… 49.9MB buildkit.dockerfile.v0
<missing> 4 days ago /bin/sh -c #(nop) CMD ["/bin/bash"] 0B
<missing> 4 days ago /bin/sh -c mkdir -p /run/systemd && echo 'do… 7B
<missing> 4 days ago /bin/sh -c [ -z "$(apt-get indextargets)" ] 0B
<missing> 4 days ago /bin/sh -c set -xe && echo '#!/bin/sh' > /… 811B
<missing> 4 days ago /bin/sh -c #(nop) ADD file:d6b6ba642344138dc… 74.1MB
文件复制和目录操作 (ADD,COPY,WORKDIR)
往镜像里复制文件有两种方式,COPY 和 ADD , 我们来看一下两者的不同。
复制普通文件
COPY 和 ADD 都可以把local的一个文件复制到镜像里,如果目标目录不存在,则会自动创建
FROM python:3.9.5-alpine3.13
COPY hello.py /app/hello.py
比如把本地的 hello.py 复制到 /app 目录下。 /app这个folder不存在,则会自动创建
复制压缩文件
ADD 比 COPY高级一点的地方就是,如果复制的是一个gzip等压缩文件时,ADD会帮助我们自动去解压缩文件。
FROM python:3.9.5-alpine3.13
ADD hello.tar.gz /app/
如何选择?
因此在 COPY 和 ADD 指令中选择的时候,可以遵循这样的原则,所有的文件复制均使用 COPY 指令,仅在需要自动解压缩的场合使用 ADD。
目录变更 WORKDIR
WORKDIR和 cd 一样改变目录的路径,但是比cd的好处就是,如果目录不存在会自动创建目录。
WORKDIR 更改路径之后,后面的 ADD 和 COPY 会放到当前目录下,就是WORKDIR更改后的路径 。
构建参数和环境变量 (ARG vs ENV)
ARG 和 ENV 是经常容易被混淆的两个Dockerfile的语法,都可以用来设置一个“变量”。 但实际上两者有很多的不同。
FROM ubuntu:21.04
RUN apt-get update && \
apt-get install -y wget && \
wget https://github.com/ipinfo/cli/releases/download/ipinfo-2.0.1/ipinfo_2.0.1_linux_amd64.tar.gz && \
tar zxf ipinfo_2.0.1_linux_amd64.tar.gz && \
mv ipinfo_2.0.1_linux_amd64 /usr/bin/ipinfo && \
rm -rf ipinfo_2.0.1_linux_amd64.tar.gz
ENV
FROM ubuntu:21.04
ENV VERSION=2.0.1
RUN apt-get update && \
apt-get install -y wget && \
wget https://github.com/ipinfo/cli/releases/download/ipinfo-${VERSION}/ipinfo_${VERSION}_linux_amd64.tar.gz && \
tar zxf ipinfo_${VERSION}_linux_amd64.tar.gz && \
mv ipinfo_${VERSION}_linux_amd64 /usr/bin/ipinfo && \
rm -rf ipinfo_${VERSION}_linux_amd64.tar.gz
ARG
FROM ubuntu:21.04
ARG VERSION=2.0.1
RUN apt-get update && \
apt-get install -y wget && \
wget https://github.com/ipinfo/cli/releases/download/ipinfo-${VERSION}/ipinfo_${VERSION}_linux_amd64.tar.gz && \
tar zxf ipinfo_${VERSION}_linux_amd64.tar.gz && \
mv ipinfo_${VERSION}_linux_amd64 /usr/bin/ipinfo && \
rm -rf ipinfo_${VERSION}_linux_amd64.tar.gz
区别
ARG只是在构建的时候保存变量,在生成镜像后就不会在保存。更多关注在构建时候使用。
ENV会直接保存在镜像中,变量一直存在。更多在关注镜像和之后构建出来的容器时候使用。
动态修改ARG的值
VERSION的值就会覆盖之前的2.0.1
docker image build -f . \Dockerfile-arg -t ipinfo-arg-2.0.0 --build-arg VERSI0N=2.0.0 .
CMD 容器启动命令
CMD可以用来设置容器启动时默认会执行的命令。
- 容器启动时默认执行的命令
- 如果docker container run启动容器时指定了其它命令,则CMD命令会被忽略
- 如果定义了多个CMD,只有最后一个会被执行。
继续使用之前的dockerfile
FROM ubuntu:21.04
ENV VERSION=2.0.1
RUN apt-get update && \
apt-get install -y wget && \
wget https://github.com/ipinfo/cli/releases/download/ipinfo-${VERSION}/ipinfo_${VERSION}_linux_amd64.tar.gz && \
tar zxf ipinfo_${VERSION}_linux_amd64.tar.gz && \
mv ipinfo_${VERSION}_linux_amd64 /usr/bin/ipinfo && \
rm -rf ipinfo_${VERSION}_linux_amd64.tar.gz
$ docker image build -t ipinfo .
$ docker container run -it ipinfo
root@8cea7e5e8da8:/# pwd
/
root@8cea7e5e8da8:/#
默认进入到shell是因为在ubuntu的基础镜像里有定义CMD
$docker image history ipinfo
IMAGE CREATED CREATED BY SIZE COMMENT
db75bff5e3ad 24 hours ago RUN /bin/sh -c apt-get update && apt-get… 50MB buildkit.dockerfile.v0
<missing> 24 hours ago ENV VERSION=2.0.1 0B buildkit.dockerfile.v0
<missing> 7 days ago /bin/sh -c #(nop) CMD ["/bin/bash"] 0B
<missing> 7 days ago /bin/sh -c mkdir -p /run/systemd && echo 'do… 7B
<missing> 7 days ago /bin/sh -c [ -z "$(apt-get indextargets)" ] 0B
<missing> 7 days ago /bin/sh -c set -xe && echo '#!/bin/sh' > /… 811B
<missing> 7 days ago /bin/sh -c #(nop) ADD file:d6b6ba642344138dc… 74.1MB
执行结束后直接删除
加 --rm
docker container run --rm -it ipinfo ipinfo 8.8.8.8
容器启动命令 ENTRYPOINT
ENTRYPOINT 也可以设置容器启动时要执行的命令,但是和CMD是有区别的。
- CMD 设置的命令,可以在docker container run 时传入其它命令,覆盖掉 CMD 的命令,但是 ENTRYPOINT 所设置的命令是一定会被执行的。
- ENTRYPOINT 和 CMD 可以联合使用,ENTRYPOINT 设置执行的命令,CMD传递参数
demo-cmd
FROM ubuntu:21.04
CMD ["echo", "hello docker"]
把上面的Dockerfile build成一个叫 demo-cmd 的镜象
docker image build -f .\demo-cmd -t demo-cmd .
$ docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
demo-cmd latest 5bb63bb9b365 8 days ago 74.1MB
demo-entrypoint
FROM ubuntu:21.04
ENTRYPOINT ["echo", "hello docker"]
build成一个叫 demo-entrypoint 的镜像
docker image build -f .\demo-cmd -t demo-entrypoint .
$ docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
demo-entrypoint latest b1693a62d67a 8 days ago 74.1MB
run 对比
但是如果我们docker container run
的时候指定命令,则该命令会覆盖掉CMD的命令,如:
$ docker container run -it --rm demo-cmd echo "hello world"
hello world
但是ENTRYPOINT的容器里ENTRYPOINT所定义的命令则无法覆盖,一定会执行
$ docker container run -it --rm demo-entrypoint
hello docker
$ docker container run -it --rm demo-entrypoint echo "hello world"
hello docker echo hello world
$
ENTRYPOINT和CMD 一起使用小实例
Dockerfile
FROM ubuntu:21.04
ENTRYPOINT ["echo"]
CMD []
docker image build -f .\demo-entrypoint-cmd -t demo-entrypoint-cmd .
PS E:\images> docker image ls
demo-entrypoint-cmd latest d497d3254709 5 weeks ago 80MB
PS E:\images> docker container run --rm -it demo-entrypoint-cmd
PS E:\images> docker container run --rm -it demo-entrypoint-cmd test
test
后面什么都不输入,就会返回一个空,而输入一个test,就会打印出来
Shell 格式和 Exec 格式
CMD
和ENTRYPOINT
同时支持shell格式和Exec格式。
Shell格式
CMD echo "hello docker"
ENTRYPOINT echo "hello docker"
Exec格式
以可执行命令的方式
ENTRYPOINT ["echo", "hello docker"]
CMD ["echo", "hello docker"]
注意shell脚本的问题
FROM ubuntu:21.04
ENV NAME=docker
CMD echo "hello $NAME"
假如我们要把上面的CMD改成Exec格式,下面这样改是不行的, 大家可以试试。
FROM ubuntu:21.04
ENV NAME=docker
CMD ["echo", "hello $NAME"]
它会打印出 hello $NAME
, 而不是 hello docker
,那么需要怎么写呢? 我们需要以shell脚本的方式去执行:
FROM ubuntu:21.04
ENV NAME=docker
CMD ["sh", "-c", "echo hello $NAME"]