Dockerfile完全指南
基础镜像的选择
基础原则
- 官方镜像优于非官方的镜像,如果没有官方镜像,则尽量选择Dockerfile开源的。
- 固定版本Tag而不是每次都使用lastest
- 尽力选择体积小的镜像<-alpine 精简版>
[root@node01 ~]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
nginx latest f876bfc1cc63 5 weeks ago 192MB
mysql 5.7 5107333e08a8 12 months ago 501MB
nginx 1.21.0 4f380adfc10f 3 years ago 133MB
nginx 1.21.0-alpine a6eb2a334a9f 3 years ago 22.6MB
可以看到,nginx:1.21.0
大小为133MB,而nginx:1.21.0-alpine
大小仅22.6MB
通过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
- Dockerfile-bad
FROM ubuntu:20.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 build -f dockerfile.bad -t ipinfo-bad .
镜像的大小和分层
[root@node01 dockerfile]# docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
ipinfo-bad latest e5260983907f 15 minutes ago 159MB
zhangshao3/mynginx 1.0 4c17ff0fc31c 5 days ago 192MB
tomcat latest f62f518e5c5c 3 weeks ago 467MB
nginx latest f876bfc1cc63 5 weeks ago 192MB
mysql 5.7 5107333e08a8 12 months ago 501MB
nginx 1.21.0 4f380adfc10f 3 years ago 133MB
nginx 1.21.0-alpine a6eb2a334a9f 3 years ago 22.6MB
[root@node01 dockerfile]# docker image history e526
IMAGE CREATED CREATED BY SIZE COMMENT
e5260983907f 15 minutes ago RUN /bin/sh -c rm -rf ipinfo_2.0.1_linux_amd… 0B buildkit.dockerfile.v0
<missing> 15 minutes ago RUN /bin/sh -c mv ipinfo_2.0.1_linux_amd64 /… 9.36MB buildkit.dockerfile.v0
<missing> 15 minutes ago RUN /bin/sh -c tar zxf ipinfo_2.0.1_linux_am… 9.36MB buildkit.dockerfile.v0
<missing> 15 minutes ago RUN /bin/sh -c wget https://github.com/ipinf… 4.85MB buildkit.dockerfile.v0
<missing> 16 minutes ago RUN /bin/sh -c apt-get install -y wget # bui… 7.65MB buildkit.dockerfile.v0
<missing> 16 minutes ago RUN /bin/sh -c apt-get update # buildkit 55.4MB buildkit.dockerfile.v0
<missing> 2 months ago /bin/sh -c #(nop) CMD ["/bin/bash"] 0B
<missing> 2 months ago /bin/sh -c #(nop) ADD file:7486147a645d8835a… 72.8MB
<missing> 2 months ago /bin/sh -c #(nop) LABEL org.opencontainers.… 0B
<missing> 2 months ago /bin/sh -c #(nop) LABEL org.opencontainers.… 0B
<missing> 2 months ago /bin/sh -c #(nop) ARG LAUNCHPAD_BUILD_ARCH 0B
<missing> 2 months ago /bin/sh -c #(nop) ARG RELEASE 0B
每一行的RUN命令都会产生一层image layer,导致镜像的臃肿
改进后Dockerfile
FROM ubuntu:20.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 build -f dockerfile.good -t ipinfo-good .
[root@node01 dockerfile]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
ipinfo-good latest 3ec094c48160 30 seconds ago 145MB
ipinfo-bad latest e5260983907f 33 minutes ago 159MB
zhangshao3/mynginx 1.0 4c17ff0fc31c 5 days ago 192MB
tomcat latest f62f518e5c5c 3 weeks ago 467MB
nginx latest f876bfc1cc63 5 weeks ago 192MB
mysql 5.7 5107333e08a8 12 months ago 501MB
nginx 1.21.0 4f380adfc10f 3 years ago 133MB
nginx 1.21.0-alpine a6eb2a334a9f 3 years ago 22.6MB
[root@node01 dockerfile]# docker image history 3ec094c48160
IMAGE CREATED CREATED BY SIZE COMMENT
3ec094c48160 48 seconds ago RUN /bin/sh -c apt-get update && apt-get… 72.5MB buildkit.dockerfile.v0
<missing> 2 months ago /bin/sh -c #(nop) CMD ["/bin/bash"] 0B
<missing> 2 months ago /bin/sh -c #(nop) ADD file:7486147a645d8835a… 72.8MB
<missing> 2 months ago /bin/sh -c #(nop) LABEL org.opencontainers.… 0B
<missing> 2 months ago /bin/sh -c #(nop) LABEL org.opencontainers.… 0B
<missing> 2 months ago /bin/sh -c #(nop) ARG LAUNCHPAD_BUILD_ARCH 0B
<missing> 2 months ago /bin/sh -c #(nop) ARG RELEASE 0B
可以观察到,一条命令,只占用72.8MB
文件复制和目录操作
往镜像里复制文件有两种方式,COPY
和ADD
。
复制普通文件
COPY
和ADD
都可以把local的一个文件复制到镜像里,如果目标目录不存在,则会自动创建。
FROM python:3.9.5-alpine3.13
COPY hello.py /app/hello.py
复制压缩文件
ADD
比COPY
高级一点的地方就是,如果复制的是一个gzip等压缩文件时,ADD会帮助我们自动去解压缩文件。
FROM python:3.9.5-alpine3.13
ADD hello.tar.gz /app/
如何选择
因此在 COPY 和 ADD 指令中选择的时候,可以遵循这样的原则,所有的文件复制均使用 COPY 指令,仅在需要自动解压缩的场合使用 ADD。
# 切换目录,类似于cd, 如果目录不存在,自动创建
WORKDIR /app
构建参数和环境变量
ARG
和ENV
是经常易被混淆的两个Dockerfile的语法,都可以用来设置一个“变量”,但实际上两个有很多不同的地方。
FROM ubuntu:20.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:20.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:20.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 可以在镜像build的时候动态修改value, 通过 --build-arg
$ docker image build -f .\Dockerfile-arg -t ipinfo-arg-2.0.0 --build-arg VERSION=2.0.0 .
$ docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
ipinfo-arg-2.0.0 latest 0d9c964947e2 6 seconds ago 124MB
$ docker container run -it ipinfo-arg-2.0.0
root@b64285579756:/#
root@b64285579756:/# ipinfo version
2.0.0
root@b64285579756:/#
ENV 设置的变量可以在Image中保持,并在容器中的环境变量里。
CMD容器启动命令
CMD可以用来设置容器启动时默认会执行的命令。
- 容器启动时默认执行的命令
- 如果docker container run启动容器时制定了其他命令,则CMD命令会被忽略
- 如果定义了多个CMD,只有最后一个会被执行
快速删除已退出的镜像
[root@node01 dockerfile]# docker system prune -f
Deleted Containers:
19775fd1fe8c40d07394eebee075f586a485a3192c03492d72b510ba2515e763
519f513648cdc281a995c69e1f2ecb4f2cc033240ff3dbfba0c3caf078eadc9b
e07a6f8a70f7266a5502bc0c477f831a274f81397ac6e432579a10b433588dd3
Deleted build cache objects:
hez2u8fzodf8afths4nwvtve8
wf9env47fjy7l1h48j6kxhvgz
xh4h2kju1k40362q46k54c1ud
Total reclaimed space: 167.2kB
快速删除所有的镜像
docker image prune -a
创建一个带cmd的dockerfile
FROM ubuntu:20.04
ENV VERSION=2.0.1
# 如果网络情况不佳,可制定阿里源
RUN sed -i 's/archive.ubuntu.com/mirrors.aliyun.com/g' /etc/apt/sources.list && \
sed -i 's/security.ubuntu.com/mirrors.aliyun.com/g' /etc/apt/sources.list
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 build -f dockerfile.cmd -t ipinfo .
docker container run -it ipinfo
随后启动容器,发现已经进入ubuntu shell中,这是因为ubuntu的基础镜像中有自定义的CMD
docker image history ipinfo
如果启动容器时指定了其他命令,cmd会被忽略。
# 追加ipinfo命令
docker container run -it ipinfo ipinfo
如果容器中包含多个CMD
命令
FROM ubuntu:20.04
ENV VERSION=2.0.1
RUN sed -i 's/archive.ubuntu.com/mirrors.aliyun.com/g' /etc/apt/sources.list && \
sed -i 's/security.ubuntu.com/mirrors.aliyun.com/g' /etc/apt/sources.list
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
CMD ["ipinfo"]
此时,既有ubuntu默认的cmd也有制定的cmd。
docker build -f dockerfile.cmd -t ipinfo-new .
docker container run -it ipinfo-new
进入容器,发现执行的CMD为ipinfo
,即如果dockerfile中指定了CMD,优先执行。
针对一次性执行的容器,可以在创建时带上-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传递参数。
使用CMD命令
FROM ubuntu:20.04
CMD ["echo", "hello docker"]
使用ENTRYPOINT命令
FROM ubuntu:20.04
ENTRYPOINT ["echo", "hello docker"]
Both
FROM ubuntu:20.04
ENTRYPOINT ["echo"]
CMD []
# 构件cmd镜像
docker build -f dockerfile-cmd -t demo-cmd .
运行demo-cmd镜像
docker container run --rm -it demo-cmd
结果输出“hello docker”
使用命令追加在container命令后,执行追加的命令,会覆盖dockerfile中的cmd。
docker container run --rm -it demo-cmd echo "hello world"
结果输出“hello world”
# 构件entrypoint
docker container run --rm -it demo-entrypoint
结果输出“hello docker”
docker container run --rm -it demo-entrypoint echo "hello world"
结果输出“hello docker echo hello world”,原因是在ENTRYPOINT中设置的echo命令是一定会被执行的。
# 构建both
docker container run --rm -it demo-both
结果打印为空
# 追加cmd
docker container run --rm -it demo-both hello world
Shell格式和Exec格式
CMD和ENTRYPOINT同时支持shell和exec格式。
注意shell脚本问题
FROM ubuntu:20.04
ENV NAME=docker
CMD echo "hello $NAME"
假如我们要把上面的CMD改成Exec格式,下面这样改是不行的。
FROM ubuntu:20.04
ENV NAME=docker
CMD ["echo", "hello $NAME"]
它会打印出 hello $NAME
, 而不是 hello docker
,需要以shell脚本的方式去执行。
FROM ubuntu:20.04
ENV NAME=docker
CMD ["sh", "-c", "echo hello $NAME"]
练习
编写Tomcat DockerFile
FROM centos
MAINTAINER zhang<xxxxx@qq.com>
ADD jdk-8u11-linux-x64.tar.gz /usr/local
ADD apache-tomcat-9.0.98.tar.gz /usr/local
RUN yum -y install vim
ENV MYPATH=/usr/local
WORKDIR $MYPATH
ENV JAVA_HOME /usr/local/jdk1.8.0_11
ENV CLASSPATH $JAVA_HOME/lib/dt.jar:${JAVA_HOME}/lib/tools.jar
ENV CATALINA_HOME /usr/local/apache-tomcat-9.0.98
ENV CATALINA_BASE /usr/local/apache-tomcat-9.0.98
ENV PATH $PATH:$JAVA_HOME/bin:${CATALINA_HOME}/bin:${CATALINA_HOME}/lib
EXPOSE 8080
CMD /usr/local/apache-tomcat-9.0.98/bin/startup.sh && tail -f /usr/local/apache-tomcat-9.0.98/logs/catalina.out
构件镜像
docker build -t diy_tomcat .
启动镜像
docker run -d -p 9090:8080 --name diy_tomcat -v /root/tomcat/test:/usr/local/apache-tomcat-9.0.98/webapps/test -v /root/tomcat/logs/:/usr/local/apache-tomcat-9.0.98/logs diytomcat
查看运行状态
docker ps -a
浏览器可正常访问tomcat首页
随后在/root/tomcat/test下编辑index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>我的复杂网页</title>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<!-- 这里将是网页内容的主要部分 -->
<h3>hello docker</h3>
</body>
查看/test
路径