Dockerfile是用来构建Docker镜像的文本文件,是由一条条构建镜像所需的指令和参数所组成的文本。


Dockerfile

  1. 每条保留字执行必须为大写字母,至少有一个参数
  2. 指令从上到下,顺序执行
  3. # 表示注释
  4. 每条指令都会创建一个新的镜像层并对镜像进行提交


Dockerfile、镜像、容器 关系

从应用软件的角度来看,Dockerfile、Docker镜像、Docker容器代表软件的三个不同阶段

  • DockerFIle是软件的原料
  • Docker镜像是软件的交付品
  • Docker容器是软件运行态


Docker学习笔记07:Dockerfile_Dockerfile


保留字

FROM

基础镜像,

MAINTAINER

标注维护人员信息

RUN

在构建镜像时候运行

shell格式:

RUN <命令>

RUN yum httpd

等同于在终端操作shell命令

exec格式:

RUN ["可执行文件", "参数1", "参数2"]

RUN ["./test.php", "dev", "offline"]

等价于 RUN ./test.php dev offline

EXPOSE

用于声明容器运行时应该监听的端口,这样在容器与外部进行通信时,外部可以通过这些端口访问容器内的服务。EXPOSE并不会使得容器监听在指定的端口,它只是作为一种元数据,帮助使用者理解容器应该做什么。

# 基于的基础镜像  
FROM ubuntu:latest  
  
# 安装必要的软件,比如一个web服务器  
RUN apt-get update && apt-get install -y nginx  
  
# 将nginx的配置文件复制到容器中  
COPY nginx.conf /etc/nginx/nginx.conf  
  
# 暴露80端口,这是nginx默认监听的端口  
EXPOSE 80  
  
# 设置容器启动后执行的命令  
CMD ["nginx", "-g", "daemon off;"]

基于ubuntu:latest镜像构建了一个容器,并安装了nginx。然后,使用EXPOSE 80来声明这个容器应该监听在80端口上。最后,通过CMD指令来启动nginx并使其以非守护进程模式运行,以便Docker可以捕获其输出。

需要注意的是:

  1. EXPOSE只是声明端口,并不会自动进行端口转发。要真正从宿主机访问容器的EXPOSE端口,还需要使用-p--publish参数来指定端口映射,比如docker run -p 8080:80 myimage,这样宿主机的8080端口就会被映射到容器的80端口。
  2. 可以在Dockerfile中使用多个EXPOSE指令来声明多个端口,比如EXPOSE 80 443
  3. EXPOSE不会让容器自动监听在指定的端口上。需要在容器内部运行的服务中配置好监听端口。
  4. EXPOSE可以作为文档,帮助其他使用此镜像的人理解容器应如何与外部交互。
  5. 当你使用诸如docker-compose之类的工具来编排多个容器时,EXPOSE可以帮助docker-compose理解容器间的端口依赖关系,并自动设置端口映射。


当使用 docker run 命令启动一个容器,并且没有使用 -p 或 --publish 参数来指定端口映射时,EXPOSE 指令在 Dockerfile 中声明的端口将不会被映射到宿主机的任何端口上。

在这种情况下,EXPOSE 指令的作用主要是作为文档说明,告诉其他开发者或用户这个容器内的服务默认应该在哪些端口上监听。但它并不会自动在宿主机上创建任何监听端口或转发规则。

因此,如果您没有使用 -p 参数,那么:

  1. 容器内部的服务仍然会在 EXPOSE 指定的端口上监听,这是容器内部的行为,与宿主机无关。
  2. 从宿主机上,您将无法直接访问这些端口,因为没有端口映射将容器端口与宿主机端口连接起来。
  3. 如果您需要访问容器内的服务,您必须手动使用 -p 或 --publish 参数来指定端口映射。

例如,如果您的容器内有一个服务在 8080 端口上监听,并且您在 Dockerfile 中使用 EXPOSE 8080 声明了这个端口,那么您必须像这样运行容器来映射端口:

docker run -p 80:8080 myimage

这将会把宿主机的 80 端口映射到容器的 8080 端口,这样就可以通过访问宿主机的 80 端口来访问容器内的服务了。

如果没有映射任何端口,那么只能通过其他在容器内部的进程或者通过 docker exec 进入容器内部来访问这些服务。在生产环境中,通常会需要映射端口以便外部可以访问容器内的服务。而在开发或测试环境中,有时可能不需要映射端口,只在容器内部进行测试或调试。


WORKDIR

默认登录进入镜像的路径。

USER

指定镜像一什么样的用户去运行,如果不指定,默认root

ENV

运行时环境,设置环境变量。

ENV指令有两种形式:

  1. ENV <key>=<value> ...:这种形式允许同时设置多个环境变量。每个键值对之间使用空格分隔。例如:
 ENV MY_VAR=my-value ANOTHER_VAR=another-value
  1. ENV <key> <value>:这种形式只设置单个环境变量,整个value字符串(包括空格)都会被当作value。例如:
 ENV MY_VAR "my value with spaces"

这些环境变量在后续的构建步骤中可以作为其他指令的参数,或者在容器运行时被应用程序读取和使用。例如,可以使用ENV指令来设置应用程序的路径、数据库连接信息、API密钥等。

值得注意的是,环境变量的持久性可能会导致一些不可预测的副作用。例如,如果在容器运行时修改了环境变量的值,这个修改将不会持久化到镜像中,但在容器的生命周期内,这个修改会保持有效。

此外,由于Docker的镜像层叠机制,ENV定义的环境变量在后续层次中才能够被应用。这意味着如果在ENV指令之前使用了依赖于这些环境变量的指令,它们可能无法正确解析这些变量。


ADD COPY

在Dockerfile中,ADD和COPY的主要作用都是将文件或目录从构建上下文复制到Docker镜像中。然而,它们之间存在一些关键的区别:

  1. 资源来源
  • COPY指令仅支持从Docker主机上的构建上下文中复制文件或目录到镜像中。它不支持从远程URL或压缩文件中获取资源。
  • ADD指令则更为灵活,它除了可以从Docker主机上的构建上下文中复制文件或目录外,还支持通过URL从远程服务器下载文件,并自动将其添加到镜像中。如果源文件是一个压缩文件(如.tar、.tar.gz、.zip等),ADD指令还会自动解压缩该文件到目标路径。
  1. 缓存机制
  • COPY指令在每次构建镜像时都会复制指定的文件,即使文件内容没有更改,也会进行复制操作。这可能会导致一些不必要的资源消耗和时间浪费。
  • ADD指令在构建镜像时会检查远程文件和本地文件的差异。如果文件内容没有发生更改,它不会重新复制文件,这有助于减少构建时间和资源消耗。
  1. 操作细节
  • COPY指令会保留文件的元数据(如权限、时间戳等),并且仅执行简单的复制操作,不涉及解压缩或重命名。
  • ADD指令除了复制文件外,还可以自动解压缩压缩文件,并在必要时重命名远程文件。

综上所述,COPYADD在Dockerfile中都有复制文件的功能,但它们在资源来源、缓存机制以及操作细节上有所不同。根据具体需求,可以灵活选择使用哪个指令。通常情况下,如果只需要从本地复制文件并且不需要解压缩操作,推荐使用COPY指令,因为它更符合直觉且可以避免不必要的解压缩行为。如果需要从远程URL下载文件或需要自动解压缩功能,则可以使用ADD指令。


VOLUME

容器数据卷,用于数据保存和持久化。

在使用时,如果没有使用-v参数显式的进行数据卷挂载,将会生成一个匿名数据卷。可以通过docker inspect 命令来查看

# 使用Alpine作为基础镜像
FROM alpine:latest

# 安装bash,因为Alpine默认使用ash shell,而ash可能没有echo的完整功能
RUN apk add --no-cache bash


VOLUME /data
# 设置默认命令为bash
ENTRYPOINT ["echo", "-e"]
CMD ["hello world"]

构建镜像并运行,不使用-v参数

docker build -t echo:0.2 .
docker run echo:0.2

使用docker inspect查看

Docker学习笔记07:Dockerfile_Docker_02

使用-v参数

docker run -v /tmp/data:/data echo:0.2

Docker学习笔记07:Dockerfile_Dockerfile_03


CMD

指定容器启动后要运行的命令。

shell格式: cmd <命令>

exec格式:    cmd ["可执行命令", "参数1", "参数2" ,"…"]

参数列表形式:cmd ["参数1", "参数2" ,"…"] 。指定了ENTRYPOINT后,用CMD指定具体的参数。


注意:DockerFile中可以有多个CMD指令,但只有最后一个生效。CMD会被Docker run之后的参数替换


和RUN的区别

  • CMD在docker run的时候运行
  • RUN实在docker build时候运行

ENTRYPOINT

指定容器启动时候运行的的命令

类似于CMD,但是ENTRYPOINT不会被docker run后面的命令覆盖,且这些命令行参数会被单做参数送给ENTRYPOINT指定的程序。

如果和CMD组合使用,CMD的内容作为参数传递给ENTRYPOINT。

如果有CMD但又有

# 使用Alpine作为基础镜像  
FROM alpine:latest  
  
# 安装bash,因为Alpine默认使用ash shell,而ash可能没有echo的完整功能  
RUN apk add --no-cache bash  
  
# 设置默认命令为bash  
ENTRYPOINT ["bash", "-e"]
CMD ["hello world"]

构建镜像

docker build -t echo:0.1 .

不带参数运行

docker run echo:0.1

Docker学习笔记07:Dockerfile_Docker_04

带参数运行,覆盖了CMD中的参数

docker run echo:0.1 "hello\nworld"

Docker学习笔记07:Dockerfile_Docker_05