Docker 镜像结构与 Dockerfile 案例
Docker 镜像结构与 Dockerfile 案例
一、Docker 镜像核心概念与结构
1.1 镜像与容器的关系
Docker 镜像与容器是 “静态模板” 与 “动态实例” 的关系,二者紧密关联但职责不同:
对比维度 | Docker 镜像 | Docker 容器 |
状态 | 静态(只读) | 动态(可写) |
核心内容 | 应用程序、依赖库、配置文件、rootfs(文件系统) | 运行中的进程、内存 / CPU 资源、可写层数据 |
生命周期 | 构建后长期存在,可重复使用 | 启动后存在,停止 / 删除后销毁(除非数据持久化) |
依赖关系 | 容器运行的基础(无镜像则无容器) | 镜像的运行实例(一个镜像可创建多个容器) |
1.2 镜像的 “静态转动态” 原理
镜像本身是只读的,无法直接运行,需通过 Docker 守护进程 解析镜像元数据,转化为动态容器:
- 元数据解析:镜像包含 json 格式的元数据文件,记录了容器启动所需的关键信息(如运行进程、环境变量、暴露端口等);
- 环境配置:Docker 守护进程基于元数据,为容器创建独立的 Namespace(网络、挂载、PID 等)、配置资源限制(CPU / 内存);
- 进程启动:在镜像只读层之上挂载 “容器可写层”,启动元数据指定的进程(如 nginx、/bin/bash),完成容器创建;
- 运行期角色:容器启动后,镜像仅提供只读文件系统(供容器内进程访问资源),元数据不再生效,后续修改均在可写层进行。
1.3 镜像的分层存储结构
Docker 镜像采用 分层存储(基于 Union File System),每层对应镜像构建的一个步骤,所有层叠加形成完整的文件系统。
1.3.1 分层结构组成(从下到上)
层级 | 名称 | 作用 | 示例 |
底层 | 内核(Kernel) | 共享宿主主机内核(镜像不包含内核) | Linux 内核 5.4.x |
基础层 | Base 镜像(基础镜像) | 镜像的底层依赖,提供最小化操作系统环境 | centos:7、alpine:3.18 |
中间层 | 镜像操作层 | 基于 Base 镜像的增量修改(如安装软件、复制文件) | RUN yum install nginx、COPY index.html |
顶层 | 容器可写层 | 容器启动时动态添加,仅容器可见,用于临时修改 | 容器内创建的文件、修改的配置 |
1.3.2 Base 镜像的定义
Base 镜像(基础镜像)是分层结构的起点,有两种核心定义:
- 绝对基础镜像:从 scratch(空镜像)构建,不依赖任何其他镜像(如 alpine 官方镜像),体积极小(仅几 MB);
- 相对基础镜像:作为团队 / 项目的统一基础,供其他镜像扩展(如基于 centos:7 定制的 “团队基础镜像”,已预装 gcc、vim)。
1.3.3 分层存储的核心特性
- 只读性:所有镜像层(Base 层、中间层)均为只读,避免误修改;
- 写时复制(CoW):容器修改文件时,仅将文件从只读层复制到可写层修改,不影响原镜像层;
- 层复用:不同镜像可共享相同的基础层 / 中间层(如两个 Nginx 镜像共享 centos:7 基础层),节省磁盘空间。
二、Docker 镜像构建的三种方式
Docker 提供三种主流镜像构建方式,适用于不同场景,其中 Dockerfile 构建 是生产环境的首选。
2.1 方式 1:docker commit 命令(手工构建)
基于运行中的容器创建镜像,本质是将容器可写层的修改 “固化” 为新镜像层。
操作步骤
- 创建并进入容器:
docker run --name test -it centos:7 /bin/bash # 启动 centos 容器mkdir /luoqi # 在容器内创建目录(模拟修改)exit # 退出容器
- 提交容器为镜像:
docker commit test centos1:v1 # 将 test 容器提交为 centos1:v1 镜像
- 验证镜像:
docker images # 查看 centos1:v1 镜像(体积比 centos:7 大,包含 /luoqi 目录)
优缺点
- 优点:操作简单,适合临时生成镜像;
- 缺点:
-
- 手工操作,可重复性差(无法复现构建过程);
-
- 镜像透明性低(无法追溯每层修改内容);
-
- 易引入冗余文件(如容器内临时文件),镜像体积大;
-
- 不推荐生产环境使用(存在安全隐患,无法审计)。
2.2 方式 2:基于本地模板导入
从操作系统模板文件(如 OpenVZ 模板)直接导入镜像,适用于快速获取特定系统环境。
操作步骤
- 下载模板文件:从 OpenVZ 官方地址下载模板(如 Ubuntu 12.04 模板):
wget http://download.openvz.org/template/precreated/ubuntu-12.04-x86-minimal.tar.gz
- 导入为镜像:
docker import ubuntu-12.04-x86-minimal.tar.gz ubuntu:v1 # 导入为 ubuntu:v1 镜像
- 验证镜像:
docker run -it ubuntu:v1 /bin/bash # 启动镜像,验证系统环境
适用场景
- 快速获取冷门操作系统镜像;
- 迁移传统虚拟化环境的模板文件。
2.3 方式 3:Dockerfile 构建(推荐)
Dockerfile 是包含镜像构建指令的文本文件,每条指令对应一层镜像,通过 docker build 命令自动执行指令,生成镜像。
这种方式具有 可重复、透明化、自动化 的优势,是生产环境构建镜像的标准方案。
2.3.1 Dockerfile 的结构
Dockerfile 指令按功能分为四类,执行顺序从上到下:
- 基础镜像信息:指定构建的基础镜像(必选,第一条有效指令);
- 维护者信息:标注镜像作者(可选,推荐用 LABEL 替代旧版 MAINTAINER);
- 镜像操作指令:修改镜像内容(如安装软件、复制文件、设置环境变量);
- 容器启动指令:指定容器启动时执行的命令(如 CMD、ENTRYPOINT)。
2.3.2 基础示例
# 1. 基础镜像信息(基于 centos:7 构建)FROM centos:7# 2. 维护者信息(元数据,可选)LABEL maintainer="your-name <your-email@example.com>"# 3. 镜像操作指令(创建目录、添加文件)RUN mkdir /luoqi # 执行 shell 命令,创建目录RUN touch /luoqi/test # 执行 shell 命令,创建文件# 4. 容器启动指令(默认启动 /bin/bash)CMD ["/bin/bash"]
2.3.3 构建命令
# -t:指定镜像名称和标签(格式:镜像名:标签)# .:指定 Dockerfile 所在目录(当前目录),也可通过 -f 指定自定义路径docker build -t centos-with-dir:v1 .# 强制不使用缓存(适用于依赖更新场景)docker build -t centos-with-dir:v1 --no-cache .
2.3.4 查看构建过程
通过 docker history 查看镜像分层及构建指令,确保透明化:
docker history centos-with-dir:v1
三、Dockerfile 核心指令详解
Dockerfile 指令决定了镜像的分层内容,掌握常用指令的用法是编写高效 Dockerfile 的关键。
3.1 基础指令
指令 | 格式 | 功能说明 | 示例 |
FROM | FROM <镜像名[:标签]> | 指定基础镜像(必选,第一条有效指令) | FROM centos:7、FROM scratch |
LABEL | LABEL <键>=<值> ... | 添加镜像元数据(如作者、版本),可叠加 | LABEL maintainer="test" version="1.0" |
WORKDIR | WORKDIR <目录路径> | 设置后续指令的工作目录(类似 cd),不存在则创建 | WORKDIR /app(后续指令在 /app 下执行) |
ENV | ENV <键>=<值> ... | 设置环境变量,后续指令可引用,容器启动后生效 | ENV PATH=/usr/local/bin:$PATH |
3.2 文件操作指令
指令 | 格式 | 功能说明 | 差异与注意事项 |
COPY | COPY <源路径> <目标路径> | 复制宿主机文件 / 目录到镜像中 | 仅支持本地文件,不支持压缩包自动解压、URL 下载 |
ADD | ADD <源路径/URL> <目标路径> | 复制文件 / 目录,支持额外功能 | 1. 本地压缩包(如 .tar.gz)会自动解压;2. 支持 URL 下载(如 ADD https://xxx.tar.gz /app) |
VOLUME | VOLUME <容器内路径> ... | 在镜像中创建挂载点(仅支持 Docker 管理的卷) | 容器启动时自动创建匿名卷,避免数据丢失(如 VOLUME /data) |
3.3 执行命令指令
指令 | 格式 | 执行时机 | 核心差异 | 示例 |
RUN | 1. RUN <shell 命令>(如 RUN yum install)2. RUN ["可执行文件", "参数1"](JSON 格式) | 镜像构建阶段(生成镜像层) | 用于安装依赖、配置环境,每条 RUN 生成一层 | RUN yum -y install nginx |
CMD | 1. CMD <shell 命令>2. CMD ["可执行文件", "参数1"] | 容器启动阶段(默认命令) | 1. 容器启动时可被 docker run 命令行参数覆盖;2. 一个 Dockerfile 仅最后一条 CMD 生效 | CMD ["/usr/sbin/nginx", "-g", "daemon off;"] |
ENTRYPOINT | 1. ENTRYPOINT <shell 命令>2. ENTRYPOINT ["可执行文件", "参数1"] | 容器启动阶段(入口命令) | 1. 命令行参数会作为参数传递给 ENTRYPOINT(不覆盖);2. 常与 CMD 配合(CMD 传参) | ENTRYPOINT ["nginx"] + CMD ["-g", "daemon off;"] |
3.4 其他常用指令
指令 | 格式 | 功能说明 | 示例 |
EXPOSE | EXPOSE <端口> ... | 声明容器暴露的端口(仅文档说明,不实际映射) | EXPOSE 80 443(需通过 docker run -p 映射) |
USER | USER <用户名/UID> | 指定后续 RUN/CMD/ENTRYPOINT 的执行用户 | USER nginx(后续指令以 nginx 用户执行) |
ARG | ARG <参数名>[=<默认值>] | 定义构建时参数(仅构建阶段生效,容器启动后消失) | ARG VERSION=1.0 + docker build --build-arg VERSION=2.0 |
四、Dockerfile 实战案例
以下通过三个典型案例(Nginx、扫雷游戏、可道云),掌握 Dockerfile 在实际场景中的应用。
4.1 案例 1:构建 Nginx 镜像
目标:基于 centos:7 构建 Nginx 镜像,支持自定义首页,暴露 80 端口。
4.1.1 准备工作
- 下载 CentOS 7 和 EPEL 的 YUM 源文件(解决官方源访问慢问题):
-
- Centos-7.repo(阿里云 CentOS 源)
-
- epel-7.repo(阿里云 EPEL 源)
2.确保 YUM 源文件与 Dockerfile 在同一目录。
4.1.2 编写 Dockerfile
# 基础镜像FROM centos:7# 替换 YUM 源(解决依赖安装慢问题)ADD Centos-7.repo /etc/yum.repos.d/ADD epel-7.repo /etc/yum.repos.d/# 安装 NginxRUN yum clean all && yum -y install nginx# 自定义 Nginx 首页(避免默认 403 错误)RUN rm -rf /usr/share/nginx/html/index.html && echo "123" > /usr/share/nginx/html/index.html# 声明暴露端口(文档说明)EXPOSE 80# 启动 Nginx(前台运行,确保容器不退出)CMD ["/usr/sbin/nginx", "-g", "daemon off;"]
4.1.3 构建与验证
- 构建镜像:
docker build -t mynginx:v1 .
- 启动容器:
# -p 80:80:宿主机 80 端口映射到容器 80 端口docker run -itd --name nginx-test -p 80:80 mynginx:v1
- 访问验证:
浏览器访问 http://宿主机IP,显示 “123”,说明构建成功。
4.2 案例 2:构建 Tomcat 扫雷游戏镜像
目标:基于 centos:7 构建 Tomcat 镜像,部署扫雷游戏(JSP 应用),暴露 8080 端口。
4.2.1 准备工作
- 下载文件:
-
- YUM 源文件(Centos-7.repo、epel-7.repo)
-
- 扫雷游戏压缩包(saolei.zip,包含 saolei.jsp)
2.创建 Tomcat 启动脚本 init.sh(确保前台运行):
#!/bin/bash# Tomcat 前台启动(避免容器退出)/usr/libexec/tomcat/server starttail -f /var/log/tomcat/catalina.out # 保持进程前台运行
3.赋予脚本执行权限:
chmod +x init.sh
4.2.2 编写 Dockerfile
# 基础镜像FROM centos:7# 替换 YUM 源ADD Centos-7.repo /etc/yum.repos.d/ADD epel-7.repo /etc/yum.repos.d/# 安装 Tomcat、unzip(解压压缩包)、curl(可选,用于测试)RUN yum clean all && yum -y install tomcat unzip curl# 设置工作目录(Tomcat 应用目录)WORKDIR /var/lib/tomcat/webapps/# 复制并解压扫雷游戏(移动到 ROOT 目录,避免访问路径前缀)ADD saolei.zip .RUN unzip saolei.zip && mv saolei ROOT # 访问路径:http://IP:端口/saolei.jsp# 复制启动脚本到容器ADD init.sh /init.shRUN chmod +x /init.sh # 确保脚本可执行# 声明暴露端口(Tomcat 默认 8080)EXPOSE 8080# 启动脚本(前台运行)CMD ["/bin/bash", "/init.sh"]
4.2.3 构建与验证
- 构建镜像:
docker build -t saolei-tomcat:v1 .
- 启动容器:
# -P:随机映射容器端口到宿主机(避免端口冲突)docker run -itd --name saolei-test -P saolei-tomcat:v1
- 查看端口:
docker ps # 查看容器映射的宿主机端口(如 0.0.0.0:32768->8080/tcp)
- 访问验证:
浏览器访问 http://宿主机IP:映射端口/saolei.jsp,可正常打开扫雷游戏页面,说明部署成功。
4.3 案例 3:构建可道云(KOD)文件管理平台镜像
目标:基于 centos:7 构建可道云镜像,集成 Nginx(Web 服务)和 PHP-FPM(PHP 解析),支持文件在线管理。
4.3.1 准备工作
- 下载文件:
-
- YUM 源文件(Centos-7.repo、epel-7.repo)
-
- 可道云压缩包(kodexplorer4.40.zip)
-
- Nginx 配置文件(nginx.conf,需支持 PHP 解析)
2.创建 Nginx + PHP-FPM 启动脚本 init.sh:
#!/bin/bash# 启动 PHP-FPM(后台运行)php-fpm -D# 启动 Nginx(前台运行,确保容器不退出)nginx -g 'daemon off;'
3.赋予脚本执行权限:
chmod +x init.sh
4.3.2 编写 Nginx 配置文件(nginx.conf)
核心配置需支持 PHP 解析,关键部分如下:
http {include /etc/nginx/mime.types;default_type application/octet-stream;server {listen 80;server_name localhost;root /code; # 可道云安装目录index index.php index.html;# PHP 解析配置location ~ \.php$ {root /code;fastcgi_pass 127.0.0.1:9000; # PHP-FPM 默认端口fastcgi_index index.php;fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;include fastcgi_params;}}}
4.3.3 编写 Dockerfile
# 基础镜像FROM centos:7# 替换 YUM 源ADD Centos-7.repo /etc/yum.repos.d/ADD epel-7.repo /etc/yum.repos.d/# 安装依赖:Nginx、PHP-FPM、PHP 扩展(GD 用于图片处理、MBSTRING 用于字符编码)、unzipRUN yum clean all && yum -y install nginx php-fpm php-gd php-mbstring unzip# 配置 PHP-FPM:修改运行用户为 nginx(与 Nginx 一致,避免权限问题)RUN sed -i '/^user/c user=nginx' /etc/php-fpm.d/www.confRUN sed -i '/^group/c group=nginx' /etc/php-fpm.d/www.conf# 替换 Nginx 配置文件(支持 PHP 解析)COPY nginx.conf /etc/nginx/nginx.conf# 创建可道云安装目录RUN mkdir /code# 设置工作目录WORKDIR /code# 复制并解压可道云COPY kodexplorer4.40.zip .RUN unzip kodexplorer4.40.zip# 修复权限:确保 nginx 用户可读写可道云目录RUN chown -R nginx.nginx /code# 复制启动脚本ADD init.sh /init.shRUN chmod +x /init.sh# 声明暴露端口(Nginx 默认 80)EXPOSE 80# 启动 Nginx + PHP-FPMCMD ["/bin/bash", "/init.sh"]
4.3.4 构建与验证
- 构建镜像:
docker build -t kod:v1 .
- 启动容器:
# -p 80:80:宿主机 80 端口映射到容器 80 端口(方便直接访问)docker run -itd --name kod-test -p 80:80 kod:v1
- 访问验证:
-
- 浏览器访问 http://宿主机IP,进入可道云初始化页面;
- 按提示设置管理员账号密码,完成初始化后,可正常上传、管理文件,说明部署成功。
- 浏览器访问 http://宿主机IP,进入可道云初始化页面;
五、Dockerfile 优化建议
为提升镜像构建效率、减小镜像体积,编写 Dockerfile 时需注意以下优化点:
5.1 优化构建效率
- 合理排序指令:将 “稳定不变的指令”(如 FROM、RUN yum install)放在前面,“频繁修改的指令”(如 COPY 应用代码)放在后面,最大化利用层缓存;
-
- 反例:若 COPY 放在 RUN yum install 之前,修改代码会导致后续层缓存失效。
2.合并 RUN 指令:将多个相关 RUN 指令通过 && 合并,减少镜像层数(Docker 对层数有默认 限制,且层数过多会影响容器启动效率);
-
- 示例:RUN yum install -y nginx && rm -rf /var/cache/yum/*(安装后清理缓存,一举两得)。
3.使用 .dockerignore 文件:排除构建上下文无关的文件(如日志、测试数据、.git 目录),减 少 Docker 守护进程传输的数据量,加速构建。
5.2 减小镜像体积
- 清理冗余文件:安装依赖后立即清理缓存(如 yum clean all、apt clean),避免缓存文件占用空间;
-
- 示例:RUN yum install -y nginx && yum clean all && rm -rf /var/cache/yum/*。
2.使用轻量级基础镜像:优先选择 alpine(几 MB 大小)替代 centos、ubuntu(数百 MB 大 小),如 FROM alpine:3.18;
-
- 注意:alpine 使用 apk 包管理器,需调整依赖安装命令(如 apk add --no-cache nginx)。
3.多阶段构建:仅保留最终镜像所需的文件,剔除构建过程中的临时依赖(如编译工具、源 码);
-
- 示例(Go 应用):第一阶段用 golang 镜像编译代码,第二阶段用 alpine 镜像仅复制编译后的二进制文件,镜像体积可从 GB 级降至 MB 级。
5.3 提升安全性
- 非 root 用户运行容器:通过 USER 指令指定非 root 用户(如 nginx、appuser),避免容器内进程拥有过高权限;
-
- 示例:RUN useradd -m appuser && su - appuser + USER appuser。
2.避免硬编码敏感信息:不直接在 Dockerfile 中写入密码、密钥,改用 ARG(构建时参数)或 容器启动时通过环境变量(-e)传入;
3.验证基础镜像安全性:优先选择官方镜像或经过安全扫描的镜像,避免使用未知来源的镜像 (可能包含恶意程序)。
六、总结
本文系统讲解了 Docker 镜像的核心结构、构建方式及 Dockerfile 实战,关键要点如下:
- 镜像结构:基于分层存储,由基础层、中间层、容器可写层组成,依赖宿主内核,通过元数据实现 “静态转动态”;
- 构建方式:docker commit 适合临时场景,Dockerfile 是生产环境首选,具有可重复、透明化优势;
- Dockerfile 核心:掌握 FROM、RUN、COPY、CMD 等指令用法,按 “基础信息→操作指令→启动指令” 组织结构;
- 实战与优化:通过 Nginx、扫雷游戏、可道云案例掌握实际应用,结合层缓存、轻量级镜像、多阶段构建优化镜像效率与体积。
掌握 Docker 镜像构建是容器化部署的基础,后续可进一步学习 Docker Compose(多容器编排)、Docker Swarm(容器集群),实现更复杂的应用部署架构。