因为是第一次学,所以写的有点繁琐,后面熟悉了之后可以删除掉繁琐的内容,留下精简的内容。
Dockerfile 是一个文本文件,包含了一系列指令,这些指令定义了如何构建一个 Docker 镜像。以下是编写 Dockerfile 的详细指南,涵盖了从基础到高级的各个方面。
1、创建Dockerfile文件
touch Dockerfile
在项目根目录下创建名为Dockerfile的文件,这个文件将包含所有用于构建镜像的指令,docker build的时候会用到它。
2、编写Dockerfile指令
以python web应用为例
2.1指定基础镜像(From)
每个Dockerfile必须以From指令开始,它指定了构建新镜像所基于的基础镜像。选择合适的基础镜像是非常重要的,因为它决定了你应用的运行环境。
# 使用官方 Python 运行时作为父镜像 From python:3.9-slim
2.2设置工作目录(WORKDIR)
使用 WORKDIR 指令设置容器内的工作目录。后续的命令如COPY 和 RUN 都会在这个目录中执行。使用docker exec 进入容器后也会默认进入该目录。
#设置工作目录为 /app
WORKDIR app/
2.3复制项目文件到容器(COPY 或 ADD)
使用COPY或ADD指令,将本地文件或者目录复制到容器内的指定位置。COPY更加明确高效,推荐优先使用。
#将当前目录的内容复制到容器的 /app 目录中
COPY . .
更加推荐使用COPY的原因如下:
1、明确性:COPY
更加明确,用户一眼就能看出它只是简单地复制文件或目录,不会涉及其他复杂的操作。
2、性能:由于 COPY
不执行额外的操作(如解压归档文件),因此通常比 ADD
更快。
3、可预测性:COPY
的行为更加可预测,减少了意外行为的可能性。
总结:
COPY:当你需要简单的将文件或目录从主机复制到容器中时。
ADD: 当你需要从远程URL获取文件、处理归档文件或者利用其更高级的功能时。
2.4安装依赖项
使用RUN指令执行命令,通常用于安装软件包或者配置环境。为了优化镜像大小和构建速度,建议合并多个RUN指令,并清理不必要的文件。
# 更新软件包索引并安装所需的系统依赖项 RUN apt-get update && \apt-get install -y --no-install-recommends \libpq-dev \gcc \&& rm -rf /var/lib/apt/lists/*# 安装 Python 依赖项 RUN pip install --no-cache-dir -r requirements.txt
其中,requirements.txt是需要下载的python依赖。
2.5暴露端口(EXPOSE)
使用EXPOSE指令声明容器要运行时要监听的端口。这有助于文档化容器的服务端口,但实际的端口映射需要再运行时通过-p参数完成。
#暴露应用程序监听的端口 EXPOSE 5000
2.6设置环境变量(ENV)
使用ENV指令设置环境变量,可以在后续的RUN指令或启动容器后的进程中访问这些变量。
#设置环境变量
ENV FLASK_ENV=production
2.7定义默认命令(CMD)
#定义运行容器时执行的命令 CMD ["python", "app.py"]
2.8 入口点(ENTERPOINT)[可选]
如果你的应用程序有固定的启动命令,可以使用ENTRYPOINT 来定义不可变的入口点。CMD提供的参数会被追加到ENTRYPOINT后面作为参数传递。
# 如果需要固定启动命令,可以使用 ENTRYPOINT # ENTRYPOINT ["flask", "run", "--host=0.0.0.0"]
语法:ENTRYPOINT ["executable", "param1", "param2"]
最佳实践
- 保持层最小化:尽量减少RUN指令数量,合并多个指令为一个以减少镜像层数。
- 多阶段构建:对于编译型语言,可以先在一个阶段中编译代码,然后将编译结果复制到另外一个
什么是多阶段构建:
多阶段构建(Multi-stage Build)是Docker提供的一种优化镜像构建的技术,它允许再一个Dockerfile中定义多个构建阶段。每个阶段可以使用不同的基础镜像,并且可以从一个阶段复制文件到另外一个阶段。这种技术的主要目的是为了创建更加精简、更安全的最终生产镜像,同时保持开发和构建过程中的灵活性。
多阶段构建的优势
- 减少镜像大小:通过只将必要的文件从构建阶段复制到最终镜像中,可以显著减小最终镜像的体积。
- 提高安全性:避免将编译工具和其他不必要的依赖项包含在最终镜像中,从而减少了潜在的安全风险。
- 简化构建过程:可以在一个 Dockerfile 中管理整个构建流程,包括编译、测试和打包等步骤。
- 更好的缓存利用:不同阶段可以独立缓存,使得重复构建更快。
多阶段构建的工作原理
在一个典型的多阶段构建过程中,Dockerfile 通常分为几个阶段:
- 构建阶段:用于编译应用程序或生成静态资源。这一阶段通常基于包含所有必要开发工具的基础镜像。
- 测试阶段(可选):用于运行单元测试或其他类型的测试。
- 发布阶段:用于创建最终的生产就绪镜像。这一阶段通常基于一个较小的基础镜像,如
alpine
或者scratch
,并且只包含运行时所需的文件。
例如,以下是使用Go语音编写的简单Web应用程序,并希望利用Docker的多阶段构建来创建一个轻量级的生成镜像。
# 第一阶段:构建阶段 FROM golang:1.20-alpine AS builder# 设置工作目录 WORKDIR /app# 复制 go.mod 和 go.sum 文件并下载依赖包 COPY go.mod go.sum ./ RUN go mod download# 复制源代码并构建应用 COPY . . RUN CGO_ENABLED=0 GOOS=linux go build -o myapp .# 第二阶段:发布阶段 FROM alpine:latest# 安装必要的运行时依赖(如果需要) # RUN apk --no-cache add <dependencies># 设置工作目录 WORKDIR /root/# 从构建阶段复制生成的二进制文件 COPY --from=builder /app/myapp .# 暴露应用程序监听的端口 EXPOSE 8080# 定义启动命令 CMD ["./myapp"]
构建镜像:
docker build -t my-go-app .
运行容器:
docker run -d -p 8080:8080 my-go-app
这样,你就成功地使用了多阶段构建来创建了一个只包含应用程序二进制文件和必要运行时依赖的小型镜像。这种做法不仅减小了镜像大小,还提高了安全性,因为最终的镜像中不包含任何构建工具或源代码。
注意:
在多阶段构建中,虽然 Dockerfile 中包含了多个 FROM
指令,但实际上并不会生成多个独立的 Docker 镜像。相反,这些 FROM
指令定义了不同的构建阶段,所有这些阶段最终会合并成一个最终的 Docker 镜像。
每个 FROM
指令标志着一个新的构建阶段开始,你可以在这个阶段中执行各种命令(如安装依赖、编译代码等)。每个阶段可以基于不同的基础镜像,并且可以在后续阶段中通过 COPY --from=<stage>
从之前的阶段复制文件。
例如,我们之前提到的 Go Web 应用
# 第一阶段:构建阶段 FROM golang:1.20-alpine AS builder# 设置工作目录、下载依赖、复制源代码并构建应用...# 第二阶段:发布阶段 FROM alpine:latest# 从构建阶段复制生成的二进制文件... COPY --from=builder /app/myapp .# 暴露端口并定义启动命令...
第一阶段:使用 golang:1.20-alpine
来编译应用程序,但这个阶段不会成为最终镜像的一部分。
第二阶段:使用 alpine:latest
作为基础镜像,并从第一阶段复制编译好的二进制文件。这一阶段的结果才是最终的 Docker 镜像,它将被用来创建容器。
当你运行 docker build
命令时,Docker 会依次处理每一个阶段,但最终只会输出最后一个阶段所定义的镜像。前面阶段的构建产物(如编译后的二进制文件)会根据需要被复制到后续阶段,但它们本身不会形成单独的镜像。因此,即使 Dockerfile 中有多个 FROM
指令,多阶段构建的结果仍然是一个单一的 Docker 镜像,这个镜像仅包含最后一个阶段指定的内容。