在本篇文章中,我们将带你认识 Dockerfile —— 构建 Docker 镜像的"蓝图"。我们会介绍它的基本概念和常用指令,帮助你理解如何使用它来打包你的应用。
简单了解 Docker(背景知识)
在我们深入 Dockerfile 之前,简单回顾一下几个核心概念:
- Docker 是什么?想象一下集装箱:无论里面装什么货物(代码、库、配置),集装箱本身(Docker)都能让货物在任何港口(任何机器环境)轻松装卸和运行。Docker 就是这样一个能打包、发布和运行应用程序的平台。
- 容器 (Container) 是什么?它是一个运行起来的、包含了应用及其所有依赖的标准化单元。你可以把它看作一个轻量级的、隔离的"小虚拟机"。
- 镜像 (Image) 是什么?它是创建容器的只读模板或"快照"。Dockerfile 就是用来定义如何构建这个镜像的说明书。
对中小企业的好处:使用 Docker 可以帮助团队实现开发、测试、生产环境的一致性,减少"在我电脑上明明是好的"这类问题,加快应用上线速度,并更有效地利用服务器资源。
Dockerfile 是什么?
Dockerfile 本质上是一个文本文件,里面包含了一系列的指令 (Instructions)和参数。每一条指令描述了在构建镜像过程中的一个步骤,例如:需要哪个基础环境、要安装什么软件、拷贝哪些文件进去、容器启动时要运行什么命令等等。
Dockerfile 的基本规则:
- 指令不区分大小写,但推荐使用大写,更清晰。
- 使用#开头的行是注释。
- 通常,Dockerfile 的第一条指令是FROM,指定基础镜像。
- 每一条指令都会在镜像中创建新的一层 (Layer)。
一个典型的 Dockerfile 结构可能包含:
- 基础镜像信息:使用FROM指令指定依赖的基础镜像。
- (可选) 维护者信息:使用LABEL指令添加元数据。
- 镜像构建指令:使用RUN,COPY,ADD等指令来安装软件、复制文件等。
- 容器启动指令:使用CMD,ENTRYPOINT等指令指定容器启动时执行的命令。
Dockerfile 常用指令详解
下面我们来逐一了解一些最常用的 Dockerfile 指令:
FROM
用途:指定构建新镜像所依赖的基础镜像。必须是 Dockerfile 的第一条非注释指令。
格式:
FROM [--platform=<platform>] <image> [AS <name>]
FROM [--platform=<platform>] <image>[:<tag>] [AS <name>]
FROM [--platform=<platform>] <image>[@<digest>] [AS <name>]
示例:
# 使用官方的 Ubuntu 最新版作为基础镜像
FROM ubuntu:latest
# 使用特定版本的 Alpine Linux 作为基础镜像,并给这个阶段命名为 builder
FROM alpine:3.18 AS builder
中小企业提示:选择基础镜像很重要!尽量选择官方、受信任且体积小的镜像(如alpine、debian-slim),可以显著减小最终镜像的大小,加快下载和部署速度,并减少安全风险。
LABEL
用途:为镜像添加元数据 (Metadata),如版本号、描述、作者等。这些信息不会影响镜像功能,但有助于管理和识别镜像。
格式:
LABEL <key>=<value> <key>=<value> <key>=<value> ...
示例:
LABEL version="1.0" description="我的第一个 Web 应用镜像" maintainer="Your Name <you@example.com>"
RUN
用途:在构建镜像过程中执行指定的命令。通常用于安装软件包、创建目录、编译代码等。
格式:
# Shell 格式 (简单命令推荐)
RUN <command># Exec 格式 (官方推荐,尤其适合包含空格或特殊字符的命令)
RUN ["executable", "param1", "param2"]
示例:
# 更新软件包列表并安装 Nginx (Shell 格式)
RUN apt-get update && apt-get install -y nginx
# 创建一个目录 (Exec 格式)
RUN ["mkdir", "/app"]
最佳实践:尽量将多个RUN命令合并成一条,使用&&连接。因为每条RUN指令会创建一个新的镜像层,合并命令可以减少层数,优化镜像体积和构建速度。同时,在安装包后清理缓存(如apt-get clean)也是个好习惯。
RUN apt-get update && \apt-get install -y --no-install-recommends software-properties-common && \apt-get clean && \rm -rf /var/lib/apt/lists/*
CMD
用途:指定容器启动时默认执行的命令。一个 Dockerfile 中只应有一个CMD指令,如果写了多个,只有最后一个会生效。
格式:
# Shell 格式
CMD command param1 param2# Exec 格式 (官方推荐)
CMD ["executable","param1","param2"]# 作为 ENTRYPOINT 的默认参数 (后面会讲)
CMD ["param1","param2"]
示例:
# 容器启动时运行 python server.py (Exec 格式)
CMD ["python", "server.py"]# 容器启动时执行 /bin/bash (Shell 格式)
CMD /bin/bash
注意:如果在docker run命令后面指定了其他命令,CMD指定的命令会被覆盖。
ENTRYPOINT
用途:配置容器启动时总是执行的命令。它与CMD类似,但更常用于将容器设置为一个"可执行程序"。
格式:
# Shell 格式
ENTRYPOINT command param1 param2# Exec 格式 (官方推荐)
ENTRYPOINT ["executable", "param1", "param2"]
ENTRYPOINT和CMD的配合使用:
- 如果只用ENTRYPOINT(Exec 格式),docker run命令行后面的参数会追加到ENTRYPOINT命令之后。
- 如果同时使用ENTRYPOINT(Exec 格式) 和CMD(Exec 格式,且CMD提供的是参数列表),CMD的内容会作为ENTRYPOINT的默认参数。如果在docker run时提供了参数,则会覆盖CMD提供的默认参数。
示例:
# Dockerfile
ENTRYPOINT ["ls"]
CMD ["-a"]# 运行 `docker run <image>` 时,实际执行 `ls -a`
# 运行 `docker run <image> -l` 时,实际执行 `ls -l` (-l 覆盖了 CMD 的 -a)
何时使用?如果你想让容器表现得像一个固定的可执行文件,并且允许用户传递参数给这个文件,那么ENTRYPOINT(Exec 格式) +CMD(参数格式) 是常用模式。如果只是想提供一个默认的启动命令,且允许用户完全替换它,那么单独使用CMD更简单。
EXPOSE
用途:声明容器在运行时计划监听的网络端口。这并不会自动将端口发布到主机,它更像是一个文档记录和元数据,告诉使用者这个容器打算使用哪个端口。
格式:
EXPOSE <port> [<port>/<protocol>...]
示例:# 声明容器将监听 80 端口 (默认 TCP)
EXPOSE 80# 声明容器将监听 80/tcp 和 443/tcp 端口
EXPOSE 80/tcp 443/tcp
注意:要想从主机访问容器的这个端口,你仍然需要在docker run时使用-p或-P参数来做端口映射。例如:docker run -p 8080:80 将主机的 8080 端口映射到容器的 80 端口。
VOLUME
用途:创建一个挂载点,用于持久化存储数据或在容器间共享数据。它可以将主机的目录或 Docker 管理的卷挂载到容器内的指定路径。
格式:
VOLUME ["<path1>", "<path2>"...]
VOLUME <path>
示例:
# 声明 /data 目录用于存储持久化数据VOLUME ["/data"]# 声明 /app/config 和 /app/logs 用于挂载配置和日志
VOLUME /app/config /app/logs
注意:VOLUME指令创建的挂载点,其数据默认不会包含在镜像中,并且在容器删除后,Docker 管理的卷通常仍然存在(除非显式删除)。这使得数据可以在容器生命周期之外保持不变。在docker run时,可以使用-v参数将主机目录或命名卷挂载到这里。
COPY
用途:将构建上下文(通常是 Dockerfile 所在的目录及其子目录)中的文件或目录复制到镜像内的指定路径。
格式:
COPY [--chown=<user>:<group>] [--chmod=<perms>] <src>... <dest>
COPY [--chown=<user>:<group>] [--chmod=<perms>] ["<src>",... "<dest>"] # 路径含空格时推荐
示例:
# 将当前目录下的 app.py 文件复制到镜像的 /app/ 目录下
COPY app.py /app/# 将当前目录下的 config 目录复制到镜像的 /etc/myapp/config/ 目录下
COPY config/ /etc/myapp/config/
最佳实践:
- 只复制你需要的文件和目录,避免将不必要的文件(如.git目录、临时文件)复制进镜像。可以使用.dockerignore文件来排除它们。
- 相比ADD,COPY的行为更明确(只复制本地文件),通常是首选。
ADD
用途:与COPY类似,但功能更强大:
- 可以复制本地文件/目录到镜像。
- 如果源是一个URL,它会尝试下载文件并复制到。
- 如果源是一个本地的tar 压缩包(如.tar.gz,.tar.bz2,.tar.xz),它会自动解压到。
格式:与COPY相同。
示例:
# 下载一个文件并放到 /tmp/
ADD https://example.com/file.zip /tmp/# 复制本地的 myapp.tar.gz 并自动解压到 /usr/src/
ADD myapp.tar.gz /usr/src/
注意:由于ADD的行为(特别是自动解压和下载)有时不够透明和可控,官方更推荐:
- 复制本地文件/目录:使用COPY。
- 下载文件:使用RUN wget或RUN curl,这样更清晰,且可以在同一步骤中进行解压、清理等操作。
ENV
用途:设置环境变量。这些变量在后续的RUN指令中可用,并且在容器运行时也会保留。
格式:
ENV <key>=<value> ...
ENV <key> <value> # 旧格式,不推荐
示例:
# 设置应用版本和工作目录
ENV APP_VERSION="1.0" WORK_DIR="/app"# 在后续指令中使用环境变量
WORKDIR $WORK_DIR
RUN echo "Building version $APP_VERSION" > version.txt
注意:使用ENV设置的环境变量会持久存在于镜像和容器中。如果只是想在构建过程中临时使用变量,应该考虑使用ARG。
ARG
用途:定义构建时变量。这些变量只在docker build过程中有效,容器运行时不可用(除非用ENV重新定义)。可以通过docker build --build-arg =来传递值。
格式:
ARG <name>[=<default value>]
示例:
# 定义一个构建时参数 USER,默认值为 guest
ARG USER=guest# 定义一个没有默认值的参数 PASSWORD
ARG PASSWORDRUN useradd $USER
# RUN echo "Password is $PASSWORD" # 可以在构建时使用# 如果希望构建参数在容器中也可用,可以结合 ENV
ARG APP_PORT=8080
ENV PORT=$APP_PORT
EXPOSE $PORT
构建时传递参数:# 传递 USER 参数
docker build --build-arg USER=admin -t myimage .# 同时传递 USER 和 PASSWORD 参数
docker build --build-arg USER=admin --build-arg PASSWORD=secret -t myimage .
何时使用?当你需要根据不同的构建环境(如开发、测试、生产)传入不同的配置(如代理服务器地址、特定版本号),或者不想将敏感信息(如密码、token)硬编码到 Dockerfile 或ENV中时,ARG是很好的选择。
WORKDIR
用途:设置后续RUN,CMD,ENTRYPOINT,COPY,ADD指令的工作目录。如果指定的目录不存在,它会自动创建。
格式:
WORKDIR /path/to/workdir
示例:
WORKDIR /app# 下面的 COPY 和 RUN 都在 /app 目录下执行
COPY . .
RUN pip install -r requirements.txtWORKDIR /data
# 现在工作目录切换到了 /data# 可以使用相对路径
WORKDIR /app
WORKDIR sub-dir # 现在的工作目录是 /app/sub-dir
RUN pwd # 输出 /app/sub-dir
最佳实践:推荐使用绝对路径,并尽量在 Dockerfile 开头就设置好主要的工作目录,避免在根目录 (/) 下执行过多操作。
以上就是 Dockerfile 中一些最核心、最常用的指令。理解并熟练运用它们,是编写高效、规范 Dockerfile 的基础。
你也可以查阅Docker 官方文档获取更全面的指令列表和详细信息。