注:本文章使用 VMware虚拟机 + RockyLinux9 的环境来演示和操作。

Dockerfile教程

本人在初学Dockerfile的时候一头雾水,那些指令完全不懂啥意思,后来也是各种查各种搜才算是搞明白了。现在我把我的学习笔记写成了教程,然后发出来,希望能给各位学习容器的初学者一些帮助~

一、啥是Dockerfile?

可以把它理解成一张制作镜像的图纸,在你画好这张图纸以后,Docker 就会根据这张图纸里面描述的内容,自动地把我们需要的,自定义的什么外观、什么功能的镜像创建好。

这就和买衣服一样:你去商店选了一件喜欢的衣服,但颜色只有黑白红,大小只有X、M、L三个码。此时,如果你需要制作一件同款式的XXL码的绿色衣服,就需要自己写出自己的需求:

FROM T恤模版
RUN 颜色:绿色 && 码数:XXL
COPY 另一件衣服上好看的装饰  放到我这件衣服上来
CMD 这衣服第一次穿的时候会怎么样
WORKDIR 这件衣服从哪开始穿进去

当你做好后,丢给厂家(Docker)来制作,这样一件自己定制的独特的衣服就做完了~

这件衣服就是通过Dockfile创建出来的镜像

首先来了解Dockerfile指令集合,先过一眼了解一下就好,后面会详细讲:

二、Dockerfile常见指令集合:

指令功能语法示例对应 Docker 命令功能注意事项
FROM指定基础镜像(必须为第一条指令)FROM rocky9docker pull(拉取基础镜像)支持多阶段构建(FROM ... AS stage
RUN在容器内执行命令(安装依赖、配置环境)RUN dnf -y install toolsdocker exec(容器内执行命令)合并命令减少镜像层数
COPY复制宿主机文件到镜像(仅限本地文件)COPY ./app /usr/src/appdocker cp(宿主机与容器间文件复制)路径需相对于 Dockerfile 目录
ADD类似 COPY,支持远程 URL 和自动解压压缩文件ADD https://example.com/file.tar.gz /tmpdocker cp + 自动解压/下载优先使用 COPY 保证可预测性
CMD定义在容器创建时执行的默认命令(可被 docker run 覆盖)CMD ["/usr/local/nginx/sbin/nginx", "-g", "daemon off;"]docker run <image> 的默认参数仅最后一条生效;与 ENTRYPOINT 组合时作为参数
ENTRYPOINT定义容器启动时的主命令(不可被覆盖,常与 CMD 组合)ENTRYPOINT ["python", "app.py"]docker run --entrypoint 的默认主命令需用 --entrypoint 覆盖
ENV设置环境变量(容器运行时仍有效)ENV NODE_ENV=productiondocker run -e(运行时设置变量)支持多变量一行定义
EXPOSE声明容器监听端口(需配合 -p 映射到宿主机)EXPOSE 80docker run -p(端口映射)仅声明,不实际开放端口
WORKDIR设置工作目录(后续命令基于此目录执行)WORKDIR /appdocker exec -w(覆盖工作目录)自动创建目录
USER指定运行命令的用户(提升安全性)USER nobodydocker run -u(指定运行用户)需提前创建用户
           |                                                |

(最后三个不常用,了解即可)

指令详解

docker镜像构建命令

当我们的镜像制作好后,需要在终端下执行这条命令来构建我们所需要的镜像

docker build -t image:v1 ./
# docker build -t 镜像名:标签(版本号)+ Dockerfile文件所在目录
FROM:要新制作的镜像来自哪个镜像

选择一个能用docker images 查看到的现有镜像,作为新制作镜像的模板镜像

例如:

FROM rocky:8

在FROM中,如果镜像名太长,可以给镜像起别名(注意大小写一致),后面的指令中使用这个别名也能正常使用这个镜像:

FROM RockyLinux:8.5 AS rocky
RUN:在构建镜像时执行的命令

镜像构建时,自动在镜像内执行的shell命令,一般是一些安装、配置的命令。这样能给快速完成镜像的制作,就不需要说我还要镜像创建容器,在容器里面安装好了,再打包回镜像那么麻烦了

例如:在编译源码包时,需要安装一些相关的软件才能编译,我们可以直接使用RUN就能快速安装好这些源码包了:

FROM rocky:8
RUN dnf -y install make pcre pcre-devel gcc zlib-devel openssl-devel openssl
RUN dnf clean all

这样就可以由一个最小化镜像,得到一个拥有

CMD:在使用镜像run创建容器启动时自动执行的命令,可被覆盖

在使用docker run 创建一个容器的时候,如果什么参数都不加,就会默认执行 CMD 上面所写的 shell 命令

但如果加了参数就会覆盖掉这个默认命令

例如:查看/etc下所有文件目录的详细信息

FROM myrocky:1
CMD [ "ls /etc", "-l"]

在构建这个镜像后运行容器:

docker run -it --rm img

你就可以看到,容器刚创建好后,就执行了一次 ls -l /etc,查看了/etc 下所有文件的详细信息

需要注意的是CMD的书写格式:以双引号包裹住命令/选项/参数,然后以逗号分隔开每一个命令/选项/参数

RUN 和 CMD的区别:
  • RUN 是在构建镜像时执行命令,用于在镜像内部执行命令
    比如预安装一些软件或者做配置;
  • CMD 是在启动容器时执行命令,定义容器启动时会做什么
    比如:启动容器就运行nginx服务,我们就可以免得再进容器里面手动运行了
ENTRYPOINT:也是执行默认命令,但不能被直接覆盖

和CMD命令类似,也是在容器启动时,执行一个默认命令,但不能被覆盖。而 CMD的默认命令可以被你写的新参数覆盖掉
如果要覆盖ENTRYPOINT命令,需要 docker run 时加上 --entrypoint 参数

例如:

FROM myrocky1
ENTRYPOINT ["/usr/bin/echo"]
CMD [""]

这个镜像运行容器后,你在 docker run 后面给的参数会成为 echo 输出的文字

COPY与ADD:都是把外部文件复制进镜像内,但一个解包一个不解包
  • COPY 就是单纯地复制文件,完全等于 docker cp

  • ADD 会先把压缩文件解压,再把解压后的东西复制到目标目录

  • COPY 占用资源更少更快,而 ADD 行为更复杂速度更慢

例如:

准备一个tar包:

touch myfile
tar -zcf myfile.tar.gz ./myfile 

在Dockerfile文件中分别使用这两个命令,看看有什么区别

FROM myrocky:1

COPY myfile.tar.gz /tmp
ADD myfile.tar.gz /var/tmp

执行后发现,COPY并没有解压tar包,而ADD解压了

但这并不意味着COPY就没有ADD好,COPY的复制速度更快,而ADD因为要走解包流程,会执行得稍慢一些,暂用资源也会多一些。所以要根据实际情况来权衡使用哪个。

[root@docker-0001 myimg]# docker run -it --rm img:v3 ls /tmp/
myfile.tar

[root@docker-0001 myimg]# docker run -it --rm img:v3 ls /var/tmp/
hosts  issue
USER:指定以什么用户运行容器

在USER指令之后的指令,都会以这个USER指定的用户运行容器,而之前的都是默认用户(ROOT)

FROM myrocky:1

RUN touch /tmp/file1
USER nobody
RUN touch /tmp/file2

CMD ["/bin/bash"]

docker build构建后,运行容器:

[root@docker-0001 myimg]# docker run -it --rm img:v4

bash-5.1$ ls -l /tmp/file*
-rw-r--r-- 1 root   root   0 Nov  5 02:41 /tmp/file1
-rw-r--r-- 1 nobody nobody 0 Nov  5 02:41 /tmp/file2
  • 可以看到在使用了USER前的file1还是root
  • 使用后创建的file2所属者和所属组变变成了nobody。
ENV:给容器设置永久的环境变量

比如设置时区这样的环境变量:

FROM myrocky:1
RUN touch /tmp/file1
CMD ["/bin/bash"]

ENV TZ='Asia/Shanghai'   # 设置了上海时区

构建后,查看创建的文件的详细信息:

[root@docker-0001 myimg]# docker run -it --rm img:v5

[bash-5.1]$ ls -l /tmp/file1
-rw-r--r-- 1 root   root   0 Nov  5 10:41 /tmp/file1

相比上面的案例,时间已经变成了10:41

EXPOSE:端口映射声明

声明一个将要开放映射的端口号,但声明以后并不意味着端口直接可以使用了:

例如:

FROM myrocky:1
RUN touch /tmp/file1
CMD ["/bin/bash"]

EXPOSE 12345/tcp

创建容器后查看容器信息:

[root@docker-0001 myimg]# docker ps

CONTAINER ID   IMAGE       COMMAND       CREATED         STATUS             PORTS       NAMES
4847a14085e3   img         "/bin/bash"   3 seconds ago   Up 3 seconds       12345/tcp   loving_ramanujan
  • 端口已经有了,但因为内部没有任务服务与这个端口号映射,所以这个端口号仅仅只是声明出来但未被使用而已。
  • 要使用这个开放出来的端口,还需要在创建容器的时候加上 -p 来映射容器内的端口与之匹配
WORKDIR:设置默认的工作目录

说白了就是

FROM myrocky:1
CMD ["/bin/bash"]
WORKDIR /tmp

构建镜像,运行容器后可以查看工作目录:

[root@docker-0001 myimg]# docker run -it --rm img:v6
bash-5.1$ pwd
/tmp 

pwd 命令可以看到默认工作目录变成了 /tmp

相似功能命令对比总结:
COPY vs ADD:
  • COPY 完全对应 docker cp,仅本地文件复制;
  • ADD 额外支持远程和解压,但行为更复杂,执行会稍慢一些。
CMD vs RUN:
  • CMD 是用镜像运行容器时执行的命令;
  • RUN是在镜像构建时,往镜像内执行的命令
CMD vs ENTRYPOINT:
  • CMD 的默认命令可被 docker run 直接覆盖;
  • ENTRYPOINT 需添加 --entrypoint 才能覆盖。

三、小案例:利用Dockerfile自动制作一个简单的镜像文件

vim /root/Dockerfile

FROM rockylinux:9 
COPY yum.sh /opt/
RUN /opt/yum.sh

执行:

docker build -t myos:2.0 /opt
# 格式:docker build -t 容器名:标签(版本号) Dockerfile所在目录

编写Dockerfile的大致思路

  1. 准备基础镜像,并创建容器
  2. 先在这个容器内制作需要的服务:比如编译安装一个nginx服务,并确保服务正常运行
  3. 根据刚才制作镜像时所写的命令,编写成一个Dockerfile

四、分阶段制作镜像

在制作镜像的时候,Dockerfile经常会写得很长一大串,不方便阅读和理解。

我们可以在一个Dockerfile内,分成两个阶段来制作镜像。

这就好像写文章分段落一样,一段式文章虽然占用页数可能少,但读者会看着很乱很累,而且抓不住主题、抓不住重点。这时,分段的好处就来了…

分阶段制作镜像的好好处很多,比如:增强可读性、可维护性、减小镜像体积等等…

分阶段制作一个Nginx镜像:

(需要提前准备一个nginx的源码tar包)

# 阶段一:make编译Nginx源码文件
FROM img:base as builder
ADD nginx-1.27.5.tar.gz /
WORKDIR /nginx-1.27.5/
RUN dnf -y install pcre-devel zlib-devel openssl-devel gcc make
RUN dnf clean all
RUN ./configure --prefix=/usr/local/nginx --with-http_ssl_module
RUN make
RUN make install
RUN echo "Nginx is running!" > /usr/local/nginx/html/index.html

# 阶段二:配置Nginx各项启动参数
FROM img:base
RUN dnf -y install pcre zlib openssl && dnf clean all
COPY --from=builder /usr/local/nginx /usr/local/nginx  # --from 的意思是将上面制作好的镜像拿来用,复制里面的文件过来
WORKDIR /usr/local/nginx/
ENV PATH=${PATH}:/usr/local/nginx/sbin/
EXPOSE 80/tcp
CMD ["nginx","-g","daemon off;"]

这里我们分了两步来制作一个nginx镜像:

第一阶段:编译nginx制作一个临时镜像
- 我们把源码包解压拷贝了进镜像内,并设置了默认的工作目录。
- 用了一长串的RUN命令,分别安装了编译所需的各个工具、然后执行了make编译(这些都是nginx源码包编译的必要步骤)

第二阶段:制作镜像的成品
- 继续将上面制作一半的镜像进行配置,然后安装剩下的编译工具
- COPY的意思是,将上面镜像里面编译出来的nginx目录给拷贝到这个成品镜像里面
- 设置工作路径和环境变量,环境变量可以让我们直接输入 `nginx` 就能直接运行 nginx,而不用进到 nginx 目录里面再运行。
- 配置暴露端口,默认命令,整个Dockerfile就完成了。

完成后直接执行 build 命令,启动容器,我们就可以直接得到一个可以使用的 nginx 容器了

更多推荐