前言
在容器化运维的工作流中,拉取官方镜像只是基础场景,当我们需要定制环境、预装依赖、固化业务配置时,Dockerfile 就是实现镜像自动化构建的核心工具。
一、Docker 镜像管理基础
Docker 镜像不仅是容器运行的核心,更是应用交付的标准格式。它包含了应用程序及其所有依赖(代码、运行时、库、配置文件),能确保应用在开发、测试、生产环境中完全一致运行。
在实际运维中,我们常需要对已定型的容器进行配置修改、软件安装,而要将这些修改迁移复用,就必须通过 Dockerfile 把环境和操作固化成新的镜像。
1.1 Docker 镜像的分层结构
Docker 镜像并非单一文件,而是基于 UnionFS(联合文件系统) 构建的多层文件系统,这是 Docker 高效、轻量的核心原因:
- 指令与镜像层一一对应 :Dockerfile 中的每一条指令,都会创建一个独立的只读镜像层,可通过
docker history <镜像名>查看各层内容和大小。 - 容器读写层:容器运行时,会在所有镜像层之上添加一层可读写层,容器内的文件修改、新增都会写入这一层。删除容器时,读写层会被清除,修改也随之丢失。
- 层缓存与复用:所有镜像层都会被缓存,构建镜像时,若指令未修改、文件未变更,Docker 会直接复用缓存层,大幅提升构建速度。
- 缓存失效机制:某一层缓存失效后,其后续所有层的缓存都会失效,因此合理安排指令顺序至关重要。
- 文件删除的本质:如果在某一层添加文件,下一层删除该文件,镜像中仍会包含该文件(只是容器中不可见),因此需及时清理安装包、缓存,避免镜像体积膨胀。
Docker 镜像默认存储在 /var/lib/docker/<storage-driver> 目录中,不同存储驱动(如 overlay2、devicemapper)的存储结构略有差异。
二、Dockerfile 语法基础
Dockerfile 是被 Docker 程序解释的文本脚本,由多条指令组成,每条指令对应一条 Linux 命令,Docker 会自动处理指令间的依赖关系,最终生成定制化镜像。
2.1 基础指令
(1) FROM
指定基础镜像,所有 Dockerfile 都必须以 FROM 开头,定义新镜像基于哪个镜像构建。
dockerfile
# 示例:基于 CentOS 7 构建新镜像
FROM centos:7
(2) MAINTAINER
用于指定镜像维护者信息,新版 Docker 推荐使用 LABEL 指令替代。
dockerfile
MAINTAINER John Doe <johndoe@example.com>
(3) LABEL
为镜像添加元数据(作者、版本、描述等),方便镜像管理与识别。
dockerfile
LABEL version="1.0" description="This is a custom image" maintainer="John"
2.2 环境设置指令
(1) ENV
设置环境变量,变量在容器运行时持续存在,可被容器内的应用程序使用。
dockerfile
# 设置 MySQL root 用户密码环境变量
ENV MYSQL_ROOT_PASSWORD=password
(2) ARG
定义构建镜像时可传递的参数,仅在镜像构建过程中有效,容器运行时不可用。
dockerfile
# 定义一个名为 VERSION 的参数,默认值为 1.0
ARG VERSION=1.0
2.3 文件操作指令
(1) COPY
将本地文件或目录复制到镜像中,仅支持本地文件,语法清晰、性能好,是文件复制的首选。
dockerfile
# 将本地的 app.py 文件复制到镜像的 /app/ 目录下
COPY app.py /app/
(2) ADD
与 COPY 类似,可复制文件 / 目录到镜像,额外支持从远程 URL 下载文件和自动解压 tar/zip/tgz/xz 等归档文件。仅在需要这些额外功能时使用。
dockerfile
# 从指定 URL 下载文件并复制到镜像的 /app/ 目录下,自动解压 tar.gz 文件
ADD http://example.com/file.tar.gz /app/
(3) WORKDIR
设置工作目录,后续 RUN、CMD、ENTRYPOINT、COPY 和 ADD 等指令都会在该目录下执行,目录不存在时会自动创建。
dockerfile
# 将工作目录设置为 /app
WORKDIR /app
2.4 执行命令指令
(1) RUN
在构建镜像过程中执行命令,常用于安装软件包、配置环境等操作,每条 RUN 会创建新的镜像层。建议合并多个相关命令,减少镜像层数。
dockerfile
# 在镜像中更新软件源并安装 Python 3
RUN apt-get update && apt-get install -y python3
(2) CMD
为容器提供默认的执行命令,一个 Dockerfile 中只能有一个 CMD,多个则只有最后一个生效。容器启动时,若未指定其他命令,会执行 CMD 指定的命令,且 CMD 可被 docker run 后的参数替换。
dockerfile
# 指定容器启动时默认执行 python3 app.py 命令
CMD ["python3", "app.py"]
(3) ENTRYPOINT
配置容器启动时执行的命令,与 CMD 类似,但不会被 docker run 后面的命令覆盖,而是将 docker run 后的命令作为参数传递给 ENTRYPOINT。
dockerfile
# 容器启动时默认执行 python3,使用 docker run <image> test.py 时,会执行 python3 test.py
ENTRYPOINT ["python3"]
CMD ["app.py"]
2.5 网络和暴露端口指令
(1) EXPOSE
声明容器运行时会监听的端口,仅作为文档说明,不会实际进行端口映射,需配合 docker run -p/-P 实现端口映射。
dockerfile
# 声明容器会监听 8080 端口
EXPOSE 8080
2.6 容器挂载指令
(1) VOLUME
创建一个可从本地主机或其他容器挂载的挂载点,用于持久化数据或共享数据,避免容器删除时数据丢失。
dockerfile
# 创建一个名为 /app/data 的挂载点
VOLUME ["/app/data"]
三、Dockerfile 实战案例(Nginx/Tomcat/MySQL/PHP)
实验环境
- 操作系统:CentOS 7
- 提前准备:基础源码包、配置文件、启动脚本,统一放在自定义工作目录
案例 1:构建 Nginx 容器
(1) 拉取 CentOS 基础镜像
bash
运行
[root@localhost ~]# docker pull centos:7
(2) 创建 Dockerfile 工作目录
bash
运行
[root@localhost ~]# mkdir /opt/nginx
[root@localhost ~]# cd /opt/nginx
(3) 创建 Dockerfile 文件
dockerfile
FROM centos:7
RUN rm -rf /etc/yum.repos.d/*
RUN curl -o /etc/yum.repos.d/CentOS-Base.repo https://mirrors.aliyun.com/repo/Centos-7.repo
RUN yum clean all
RUN yum -y install pcre-devel zlib-devel gcc make openssl-devel
ADD nginx-1.19.5.tar.gz /opt
WORKDIR /opt/nginx-1.19.5
RUN ./configure --prefix=/usr/local/nginx && make && make install
ADD nginx.conf /usr/local/nginx/conf/nginx.conf
EXPOSE 80
EXPOSE 443
ADD run.sh /run.sh
RUN chmod 775 /run.sh
CMD ["/run.sh"]
(4) 编写 Nginx 启动脚本 run.sh
bash
运行
#!/bin/bash
/usr/local/nginx/sbin/nginx
(5) 用 Dockerfile 创建镜像
bash
运行
[root@localhost nginx]# docker build -t mynginx .
(6) 启动容器
bash
运行
# 方式1:基础启动
[root@localhost nginx]# docker run -d --name nginx01 -p 8080:80 mynginx
# 方式2:挂载静态文件目录
[root@localhost nginx]# docker run -d -it -p 8081:80 --name nginx02 -v /www/html/web mynginx /bin/bash -c /run.sh
# 方式3:挂载配置文件与静态文件
[root@localhost nginx]# docker run -dit \
-p 8083:80 \
-v /www/html:/web \
-v /nginx/nginx.conf:/usr/local/nginx/conf/nginx.conf \
--name nginx04 \
mynginx \
/bin/bash -c /run.sh
(7) 访问 Nginx 网站
bash
运行
# 测试页面
[root@localhost nginx]# echo "web test">/www/html/index.html
# 访问地址:http://192.168.10.101:8083
案例 2:构建 Tomcat 容器
(1) 创建工作目录
bash
运行
[root@localhost ~]# mkdir /opt/tomcat/
[root@localhost ~]# cd /opt/tomcat/
(2) 创建 Dockerfile 文件
dockerfile
FROM centos:7
ADD jdk-8u91-linux-x64.tar.gz /usr/local/
ENV JAVA_HOME /usr/local/jdk1.8.0_91
ENV JAVA_BIN /usr/local/jdk1.8.0_91/bin
ENV JRE_HOME /usr/local/jdk1.8.0_91/jre
ENV PATH $PATH:/usr/local/jdk1.8.0_91/bin:/usr/local/jdk1.8.0_91/jre/bin
ENV CLASSPATH /usr/local/jdk1.8.0_91/jre/lib:/usr/local/jdk1.8.0_91/lib:/usr/local/jdk1.8.0_91/lib/charsets.jar
ADD apache-tomcat-8.5.16.tar.gz /
RUN mv /apache-tomcat-8.5.16 /usr/local/tomcat
EXPOSE 8080
ADD run.sh /run.sh
RUN chmod 775 /run.sh
CMD ["/run.sh"]
(3) 创建启动脚本 run.sh
bash
运行
#!/bin/bash
/usr/local/tomcat/bin/startup.sh
tailf /run
备注:
tailf /run让启动脚本始终运行,避免容器退出。
(4) 生成镜像并运行容器
bash
运行
# 生成镜像
[root@localhost tomcat]# docker build -t mytomcat .
# 运行容器
[root@localhost tomcat]# docker run -d -i -p 8080:8080 --name tomcat01 mytomcat
# 访问地址:http://192.168.101.101:8080
案例 3:构建 MySQL 容器
(1) 创建工作目录
bash
运行
[root@localhost ~]# mkdir /opt/mysql
[root@localhost ~]# cd /opt/mysql
(2) 创建 Dockerfile 文件
dockerfile
FROM centos:7
RUN rm -rf /etc/yum.repos.d/*
RUN curl -o /etc/yum.repos.d/CentOS-Base.repo https://mirrors.aliyun.com/repo/Centos-7.repo
RUN yum clean all
RUN yum -y install mariadb mariadb-server
RUN chown -R mysql:mysql /var/lib/mysql
ADD init.sh /init.sh
RUN chmod 775 /init.sh
RUN /init.sh
EXPOSE 3306
CMD ["mysqld_safe"]
(3) 编写 MySQL 初始化脚本 init.sh
bash
运行
#!/bin/bash
mysql_install_db --user=mysql
sleep 3
mysqld_safe &
sleep 3
mysqladmin -u "root" password "123456"
mysql -uroot -p123456 -e "grant all privileges on *.* to 'root'@'%' identified by '123456';"
mysql -uroot -p123456 -e "grant all privileges on *.* to 'root'@'localhost' identified by '123456';"
mysql -uroot -p123456 -e "flush privileges;"
(4) 生成镜像并运行容器
bash
运行
# 生成镜像
[root@localhost mysql]# docker build -t mysql .
# 创建容器
[root@localhost mysql]# docker run -id -p 3306:3306 mysql
# 测试连接
[root@localhost mysql]# yum -y install mysql
[root@localhost mysql]# mysql -uroot -p123456 -h 192.168.10.101 -P 3306
案例 4:构建 PHP 容器
(1) 创建工作目录
bash
运行
[root@localhost ~]# mkdir /opt/php
[root@localhost ~]# cd /opt/php
(2) 创建 Dockerfile 文件(yum 安装)
dockerfile
FROM centos:7
MAINTAINER jacker
RUN rm -rf /etc/yum.repos.d/*
RUN curl -o /etc/yum.repos.d/CentOS-Base.repo https://mirrors.aliyun.com/repo/Centos-7.repo
RUN yum clean all
RUN yum install epel-release -y && yum install -y php php-fpm php-common php-mysqlnd
EXPOSE 9000
CMD ["php-fpm"]
(3) 生成镜像并启动容器
bash
运行
# 生成镜像
[root@localhost php]# docker build -t myphp .
# 启动容器
[root@localhost php]# docker run -d -i -p 9000:9000 myphp /bin/bash
四、Dockerfile 语法注意事项与最佳实践
4.1 指令书写规范
- 大小写 :Dockerfile 指令不区分大小写,但建议使用大写(如
FROM、RUN),增强可读性。 - 指令顺序:指令顺序直接影响构建效率,应将不常变动的指令(如安装依赖、复制配置文件)放在前面,充分利用层缓存。
- 注释 :使用
#添加注释,解释指令作用,提升代码可维护性。
4.2 基础镜像选择
- 稳定性与安全性:优先选择官方维护的稳定镜像(如 CentOS、Ubuntu、Alpine),避免使用非官方镜像带来的安全风险。
- 镜像大小优化:对镜像体积有严格要求时,可选择轻量级镜像(如 Alpine),其体积小巧,适合构建资源占用少的容器。
4.3 文件操作注意事项
- COPY 与 ADD 的选择 :优先使用
COPY,仅在需要自动解压归档文件或从远程 URL 下载文件时使用ADD,避免功能复杂性带来的安全风险和不可预测性。 - 文件路径 :使用相对路径时,需确保路径在构建上下文(执行
docker build时指定的目录)中有效,只有该目录下的文件 / 目录能被复制到镜像中。 - 使用
.dockerignore文件 :类似.gitignore,用于排除构建过程中不需要的文件(如日志、临时文件、本地配置),减少构建上下文体积,提升构建效率。
4.4 执行命令优化
- RUN 命令合并 :将多个相关命令合并为一个
RUN指令(使用&&连接),减少镜像层数,降低镜像体积。 - 清理临时文件 :在同一
RUN指令中清理临时文件和缓存(如yum clean all、rm -rf /var/lib/apt/lists/*),避免不必要的文件残留。 - CMD 与 ENTRYPOINT 搭配 :
ENTRYPOINT作为主命令,CMD作为默认参数,容器启动时传递的参数会覆盖CMD内容,作为ENTRYPOINT的参数执行。
4.5 安全与配置要点
- 敏感信息处理 :避免在
ENV或ARG中直接设置密码、密钥等敏感信息,可在容器运行时通过环境变量传递。 - 端口声明与映射 :
EXPOSE仅作为声明,实际端口映射需通过docker run -p/-P实现,避免容器端口直接暴露在公网。 - 非 root 用户运行容器:在 Dockerfile 中创建普通用户,避免容器以 root 权限运行,提升容器安全性。
- 健康检查配置 :使用
HEALTHCHECK指令配置容器健康检查,让 Docker 定期探测容器内服务状态,自动重启异常容器。
dockerfile
# 示例:配置健康检查
HEALTHCHECK --interval=30s --timeout=3s --retries=3 \
CMD curl -f http://localhost:80/ || exit 1
4.6 缓存利用与清理
- 缓存机制:Docker 构建镜像时会复用未修改的层缓存,将不常变动的指令放在前面,可最大化利用缓存,加快构建速度。
- 强制不使用缓存 :需要强制重新构建镜像时,可使用
docker build --no-cache命令,忽略所有缓存。
五、本章总结
- Docker 镜像的分层结构与存储原理,理解了 UnionFS 如何实现镜像的高效复用与轻量运行。
- Dockerfile 基础语法,包括
FROM、RUN、COPY、ADD、CMD、ENTRYPOINT等常用指令的使用场景与区别。 - 完整的实战案例,从零构建 Nginx、Tomcat、MySQL、PHP 容器镜像,掌握了自定义镜像的全流程操作。
- Dockerfile 最佳实践,包括缓存优化、镜像体积压缩、安全配置、敏感信息处理等关键技巧。
Dockerfile 作为镜像构建的核心工具,其灵活性和可维护性让我们能够定制化构建符合各种业务场景的容器镜像,为应用的高效部署与运维提供了坚实基础。后续我们还可以结合 Docker Compose、Kubernetes 等工具,实现多容器应用的编排与管理,进一步发挥容器化技术的优势。