Docker高级管理--Dockerfile镜像制作

前言

在容器化运维的工作流中,拉取官方镜像只是基础场景,当我们需要定制环境、预装依赖、固化业务配置时,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

设置工作目录,后续 RUNCMDENTRYPOINTCOPYADD 等指令都会在该目录下执行,目录不存在时会自动创建。

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 指令书写规范

  1. 大小写 :Dockerfile 指令不区分大小写,但建议使用大写(如 FROMRUN),增强可读性。
  2. 指令顺序:指令顺序直接影响构建效率,应将不常变动的指令(如安装依赖、复制配置文件)放在前面,充分利用层缓存。
  3. 注释 :使用 # 添加注释,解释指令作用,提升代码可维护性。

4.2 基础镜像选择

  • 稳定性与安全性:优先选择官方维护的稳定镜像(如 CentOS、Ubuntu、Alpine),避免使用非官方镜像带来的安全风险。
  • 镜像大小优化:对镜像体积有严格要求时,可选择轻量级镜像(如 Alpine),其体积小巧,适合构建资源占用少的容器。

4.3 文件操作注意事项

  • COPY 与 ADD 的选择 :优先使用 COPY,仅在需要自动解压归档文件或从远程 URL 下载文件时使用 ADD,避免功能复杂性带来的安全风险和不可预测性。
  • 文件路径 :使用相对路径时,需确保路径在构建上下文(执行 docker build 时指定的目录)中有效,只有该目录下的文件 / 目录能被复制到镜像中。
  • 使用 .dockerignore 文件 :类似 .gitignore,用于排除构建过程中不需要的文件(如日志、临时文件、本地配置),减少构建上下文体积,提升构建效率。

4.4 执行命令优化

  1. RUN 命令合并 :将多个相关命令合并为一个 RUN 指令(使用 && 连接),减少镜像层数,降低镜像体积。
  2. 清理临时文件 :在同一 RUN 指令中清理临时文件和缓存(如 yum clean allrm -rf /var/lib/apt/lists/*),避免不必要的文件残留。
  3. CMD 与 ENTRYPOINT 搭配ENTRYPOINT 作为主命令,CMD 作为默认参数,容器启动时传递的参数会覆盖 CMD 内容,作为 ENTRYPOINT 的参数执行。

4.5 安全与配置要点

  • 敏感信息处理 :避免在 ENVARG 中直接设置密码、密钥等敏感信息,可在容器运行时通过环境变量传递。
  • 端口声明与映射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 命令,忽略所有缓存。

五、本章总结

  1. Docker 镜像的分层结构与存储原理,理解了 UnionFS 如何实现镜像的高效复用与轻量运行。
  2. Dockerfile 基础语法,包括 FROMRUNCOPYADDCMDENTRYPOINT 等常用指令的使用场景与区别。
  3. 完整的实战案例,从零构建 Nginx、Tomcat、MySQL、PHP 容器镜像,掌握了自定义镜像的全流程操作。
  4. Dockerfile 最佳实践,包括缓存优化、镜像体积压缩、安全配置、敏感信息处理等关键技巧。

Dockerfile 作为镜像构建的核心工具,其灵活性和可维护性让我们能够定制化构建符合各种业务场景的容器镜像,为应用的高效部署与运维提供了坚实基础。后续我们还可以结合 Docker Compose、Kubernetes 等工具,实现多容器应用的编排与管理,进一步发挥容器化技术的优势。

相关推荐
ooseabiscuit1 小时前
Laravel10.x重磅发布:新特性全解析
android·java·开发语言·mysql
0xDevNull1 小时前
Tomcat 运行原理与架构深度解析
java·架构·tomcat
ch.ju1 小时前
Java程序设计(第3版)第三章——数组
java·开发语言
_waylau1 小时前
“Java+AI全栈工程师”问答01:Spring MVC登录页面错误提示
java·开发语言·vue.js·后端·spring·mvc·springcloud
bukeyiwanshui1 小时前
20260509 Dockerfile案例
linux·运维·docker
Giggle12181 小时前
上门家政服务平台 | 多端协同,源码交付,用户端小程序+H5、服务端APP、管理后台
java·小程序·架构·产品运营·个人开发
李斯维1 小时前
工厂设计模式(Factory Pattern):工厂方法与抽象工厂的实例演示
java·设计模式
myloveasuka1 小时前
通配符 “?“
java
岳来1 小时前
docker 容器重启策略RestartPolicy
运维·docker·容器·restartpolicy