一个很简单的Dockerfile教程!没有什么晦涩难懂的话,讲得详细又简单
注:本文章使用 VMware虚拟机 + RockyLinux9 的环境来演示和操作。
Dockerfile教程
本人在初学Dockerfile的时候一头雾水,那些指令完全不懂啥意思,后来也是各种查各种搜才算是搞明白了。现在我把我的学习笔记写成了教程,然后发出来,希望能给各位学习容器的初学者一些帮助~
一、啥是Dockerfile?
可以把它理解成一张制作镜像的图纸,在你画好这张图纸以后,Docker 就会根据这张图纸里面描述的内容,自动地把我们需要的,自定义的什么外观、什么功能的镜像创建好。
这就和买衣服一样:你去商店选了一件喜欢的衣服,但颜色只有黑白红,大小只有X、M、L三个码。此时,如果你需要制作一件同款式的XXL码的绿色衣服,就需要自己写出自己的需求:
FROM T恤模版
RUN 颜色:绿色 && 码数:XXL
COPY 另一件衣服上好看的装饰 放到我这件衣服上来
CMD 这衣服第一次穿的时候会怎么样
WORKDIR 这件衣服从哪开始穿进去
当你做好后,丢给厂家(Docker)来制作,这样一件自己定制的独特的衣服就做完了~
这件衣服就是通过Dockfile创建出来的镜像
首先来了解Dockerfile指令集合,先过一眼了解一下就好,后面会详细讲:
二、Dockerfile常见指令集合:
| 指令 | 功能 | 语法示例 | 对应 Docker 命令功能 | 注意事项 |
|---|---|---|---|---|
| FROM | 指定基础镜像(必须为第一条指令) | FROM rocky9 | docker pull(拉取基础镜像) | 支持多阶段构建(FROM ... AS stage) |
| RUN | 在容器内执行命令(安装依赖、配置环境) | RUN dnf -y install tools | docker exec(容器内执行命令) | 合并命令减少镜像层数 |
| COPY | 复制宿主机文件到镜像(仅限本地文件) | COPY ./app /usr/src/app | docker cp(宿主机与容器间文件复制) | 路径需相对于 Dockerfile 目录 |
| ADD | 类似 COPY,支持远程 URL 和自动解压压缩文件 | ADD https://example.com/file.tar.gz /tmp | docker 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=production | docker run -e(运行时设置变量) | 支持多变量一行定义 |
| EXPOSE | 声明容器监听端口(需配合 -p 映射到宿主机) | EXPOSE 80 | docker run -p(端口映射) | 仅声明,不实际开放端口 |
| WORKDIR | 设置工作目录(后续命令基于此目录执行) | WORKDIR /app | docker exec -w(覆盖工作目录) | 自动创建目录 |
| USER | 指定运行命令的用户(提升安全性) | USER nobody | docker 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的大致思路
- 准备基础镜像,并创建容器
- 先在这个容器内制作需要的服务:比如编译安装一个nginx服务,并确保服务正常运行
- 根据刚才制作镜像时所写的命令,编写成一个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 容器了
更多推荐
所有评论(0)