docker-Dockerfile
docker-Dockerfile
Dockerfile 是一个文本文件,用于定义如何构建 Docker 镜像。它包含了一系列指令,告诉 Docker 如何组装镜像(例如基于什么基础镜像、复制哪些文件、安装哪些软件、设置什么环境变量等)。通过 docker build 命令,Docker 会逐条执行这些指令,最终生成一个可运行的镜像。
一、作用
- 自动化构建:无需手动操作容器,只需编写 Dockerfile,即可重复、一致地构建镜像。
- 环境标准化:确保开发、测试、生产环境一致,避免“在我电脑上能跑”的问题。
- 版本控制:Dockerfile 可以像代码一样纳入 Git 管理,方便追踪和协作。
- 简化部署:基于 Dockerfile 构建的镜像可以快速分发和运行,简化应用部署流程。
二、指令
| 指令 | 说明 |
|---|---|
FROM |
指定基础镜像,用于后续的指令构建。 |
MAINTAINER |
指定Dockerfile的作者/维护者。(已弃用,推荐使用LABEL指令) |
LABEL |
添加镜像的元数据,使用键值对的形式。 |
RUN |
在构建过程中在镜像中执行命令。 |
CMD |
指定容器创建时的默认命令。(可以被覆盖) |
ENTRYPOINT |
设置容器创建时的主要命令。(不可被覆盖) |
EXPOSE |
声明容器运行时监听的特定网络端口。 |
ENV |
在容器内部设置环境变量。 |
ADD |
将文件、目录或远程URL复制到镜像中。 |
COPY |
将文件或目录复制到镜像中。 |
VOLUME |
为容器创建挂载点或声明卷。 |
WORKDIR |
设置后续指令的工作目录。 |
USER |
指定后续指令的用户上下文。 |
ARG |
定义在构建过程中传递给构建器的变量,可使用 “docker build” 命令设置。 |
ONBUILD |
当该镜像被用作另一个构建过程的基础时,添加触发器。 |
STOPSIGNAL |
设置发送给容器以退出的系统调用信号。 |
HEALTHCHECK |
定义周期性检查容器健康状态的命令。 |
SHELL |
覆盖Docker中默认的shell,用于RUN、CMD和ENTRYPOINT指令。 |
三、指令使用
3.1、FROM
指定基础镜像,必须是 Dockerfile 的第一条有效指令
FROM ubuntu:22.04
3.2、MAINTAINER (已弃用)
指定作者信息(已弃用,推荐用 LABEL 替代)
MAINTAINER zhangsan <zhangsan@example.com>
3.3、LABEL
添加镜像元数据,键值对形式。
LABEL version="1.0" description="这是一个Web应用镜像" maintainer="lisi"
3.4、RUN
在构建镜像时执行命令,常用来安装软件包。
RUN apt-get update && apt-get install -y nginx
3.5、CMD
设置容器启动时的默认命令,可被 docker run 后的命令覆盖。
CMD ["nginx", "-g", "daemon off;"]
3.6、ENTRYPOINT
设置容器启动时的主命令,不易被覆盖(除非使用 --entrypoint 参数)
ENTRYPOINT ["top", "-b"]
3.7、EXPOSE
声明容器运行时监听的端口,仅作说明,不实际发布端口。
EXPOSE 80 443
3.8、ENV
设置环境变量,供后续指令和容器内程序使用。
ENV APP_HOME /usr/src/app
ENV NODE_ENV=production
3.9、ADD
将文件、目录或远程 URL 复制到镜像中,并支持自动解压 tar 包
ADD ./app.tar.gz /usr/src/app/
ADD https://example.com/file.txt /tmp/
3.10、COPY
将文件或目录复制到镜像中(比 ADD 更纯粹,推荐用于本地文件复制)。
COPY package.json /usr/src/app/
COPY src/ /usr/src/app/src/
3.11、VOLUME
为容器创建挂载点,用于持久化数据或共享目录
VOLUME /data
3.12、WORKDIR
设置后续指令的工作目录(相当于 cd)
WORKDIR /usr/src/app
RUN pwd # 此时工作目录为 /usr/src/app
3.13、USER
指定运行后续指令的用户(用户名或 UID)
USER node
RUN whoami # 输出 node
3.14、ARG
定义构建参数,可通过 --build-arg 传入
ARG VERSION=latest
FROM alpine:$VERSION
3.15、ONBUILD
当该镜像被用作其他 Dockerfile 的基础镜像时,触发后续指令。
ONBUILD COPY . /app/src
ONBUILD RUN make /app/src
3.16、STOPSIGNAL
设置停止容器时发送的系统调用信号。
STOPSIGNAL SIGQUIT
3.17、HEALTHCHECK
定义健康检查命令,Docker 会定期执行并判断容器状态。
HEALTHCHECK --interval=30s --timeout=3s --retries=3 \
CMD curl -f http://localhost/ || exit 1
3.18、SHELL
覆盖默认的 shell(Linux 默认为 [“/bin/sh”, “-c”],Windows 默认为 [“cmd”, “/S”, “/C”])。
SHELL ["/bin/bash", "-c"]
RUN echo "使用 bash 执行"
四、如何使用
编写 Dockerfile
在项目根目录创建一个名为 Dockerfile(无扩展名)的文件,写入指令。例如一个简单的 Python Web 应用的 Dockerfile(以测试环境为例):
# 使用官方 Python 3.9 镜像作为基础
FROM python:3.9-slim
# 设置工作目录
WORKDIR /app\
# 复制依赖文件
COPY requirements.txt .
# 安装 Python 依赖
RUN pip install --no-cache-dir -r requirements.txt
# 复制应用代码
COPY . .
# 声明容器运行时监听的端口(Flask 默认 5000)
EXPOSE 5000
# 设置环境变量(可选)
ENV FLASK_APP=app.py
ENV FLASK_ENV=development
# 容器启动命令
CMD ["flask", "run", "--host=0.0.0.0"]
- 构建镜像
在 Dockerfile 所在目录执行以下命令,-t用于给镜像命名(标签):
docker build -t my-python-app .
Docker 会读取当前目录(上下文)中的 Dockerfile 并执行构建。
- 运行容器
基于构建的镜像创建并启动容器:
这样就将容器内的 5000 端口映射到本机的 5000 端口,可以访问应用了。docker run -p 5000:5000 my-python-app
备注
1、什么是过多无意义的层会造成镜像膨胀过大?
在 Docker 中,镜像是由一系列只读层(Layer)叠加而成的。每个 Dockerfile 指令(如 RUN、COPY、ADD 等)在执行时都会创建一个新的层。理解这一点对优化镜像大小非常重要。
分层存储原理
- 层(Layer):每个指令执行后,文件系统的变化会被记录为一个新层。这些层是只读的,并堆叠在一起,构成最终的镜像。
- 缓存与复用:由于层是独立的,Docker 可以缓存每一层。如果某层没有变化,后续构建可以直接使用缓存,加快构建速度。
- 联合文件系统(UnionFS):Docker 使用联合文件系统(如 overlay2)将这些层组合成一个统一的文件系统视图。
原因
1、每一层都会保存文件系统的变化
即使你在后续层中删除了某个文件,该文件实际上仍然存在于之前的层中。Docker 只是在新的层中标记该文件为“已删除”,但底层文件依然占据空间。例如:
FROM ubuntu:20.04
RUN apt update && apt install -y large-package # 层1:安装 large-package,产生大量文件
RUN apt remove -y large-package # 层2:删除 large-package
结果:镜像仍然包含 large-package 的文件,因为层1永久保存了它们,层2只是添加了一个删除标记(在联合文件系统中,上层文件会隐藏下层同名文件,但下层文件仍存在于镜像中)。最终镜像大小几乎等于安装 large-package 后的体积。
2、每个层本身是独立的文件
每个层都是存储在磁盘上的文件或目录。层数越多,元数据开销也越大,虽然单个层可能很小,但累积起来也会占用额外空间。
3、临时文件或中间产物残留
如果在同一层中创建临时文件并在下一层删除,这些文件仍然占据空间。例如:
RUN wget https://example.com/bigfile.tar.gz # 层1:下载大文件
RUN tar -xzf bigfile.tar.gz # 层2:解压,但上一层的 tar.gz 文件仍然存在
RUN rm bigfile.tar.gz # 层3:删除 tar.gz,但文件依然在层1中
正确做法是将下载、解压、删除放在同一个 RUN 指令中,这样所有操作都在一层内完成,中间文件不会保留到最终镜像。
如何避免层膨胀
1、合并 RUN 指令
RUN apt update && \
apt install -y package1 package2 && \
apt clean && \
rm -rf /var/lib/apt/lists/*
这样所有操作都在同一层,中间产生的临时文件(如 apt 缓存)在该层结束时被清理,不会增加额外层。
2、使用多阶段构建
对于编译型语言(如 Go、Java),可以在一个阶段编译,然后将编译产物复制到最终镜像,避免携带编译工具链和源码。
# 构建阶段
FROM golang:1.18 AS builder
WORKDIR /app
COPY . .
RUN go build -o myapp
# 最终阶段
FROM alpine:latest
COPY --from=builder /app/myapp /usr/local/bin/
CMD ["myapp"]
3、谨慎使用 COPY 和 ADD
每次 COPY 都会创建新层,尽量将需要复制的文件合并复制(如一次性复制整个目录),减少层数。
4、使用 .dockerignore
排除不必要的文件进入构建上下文,避免它们被添加到镜像中(尽管它们不会产生层,但会影响构建速度和上下文大小)
更多推荐
所有评论(0)