Docker 从入门到实战全攻略:镜像、Dockerfile、数据卷与网络详解

1 Docker镜像

什么是 Docker 镜像?
Docker 镜像是 只读的容器模板,包含启动容器所需的一切资源:

  • 文件系统:操作系统、应用程序、依赖库等(如 Ubuntu 系统、Python 环境)。
  • 配置信息:启动命令、环境变量、网络设置等。

Docker镜像的主要特点

分层结构(像摞积木)
镜像由多个 只读层(Layer) 堆叠而成,每层对应一次构建操作(如安装软件、复制文件)。
好处:

  • 共享基础层(如多个镜像共用 Ubuntu 层),节省磁盘空间。
  • 修改时只需新增一层(不覆盖底层),类似 “在积木顶上加一块新积木”。

当使用docker commit提交这个修改过的容器文件系统为一个新的镜像时,保存的内容仅为最上层读写文件系统中被被更新过的文件。分层达到了在不同镜像之间共享镜像层的效果。

写时复制(Copy-on-Write,COW)
容器运行时,在镜像层上方添加 可读写层。

  • 当容器修改文件时,先将文件从只读层复制到可读写层再修改,未修改的文件仍共享底层数据。
  • 好处:多个容器共享同一镜像时,内存和磁盘占用更低。

内容寻址(按 “内容指纹” 管理)

  • 每个镜像层通过 内容哈希值(类似文件指纹) 唯一标识,而非随机 ID。
  • 好处:相同内容的层自动共享,避免重复存储,提升拉取 / 推送效率。

联合挂载(像拼拼图)
通过联合文件系统(如 Overlay2)将所有层 “拼” 成一个完整文件系统,用户看不到分层,只看到最终结果。

联合挂载就像 “把多个文件夹叠在一起,形成一个虚拟的、包含所有文件的新文件夹”,上层文件会覆盖下层,但原始文件不会被修改。Docker 利用这种技术实现了镜像的分层存储和容器的高效运行。

关键概念对比(新手必知)

概念 说明
镜像(Image) 只读模板,用于创建容器(类比“安装包”)。
容器(Container) 镜像的运行实例(类比“正在运行的软件”),可读写。
仓库(Repository) 镜像的“仓库”,用于存储镜像的不同版本(如 Docker Hub 上的 nginx 仓库)。
注册中心(Registry) 存放仓库的服务(如 Docker Hub 是公共注册中心,企业可搭建私有注册中心)。
Dockerfile 构建镜像的“菜谱”,用简单指令描述镜像的制作步骤(如拉取基础镜像、安装软件)。

2 dockerfile

2.1dockerfile介绍

dockerfile是啥

想象你要做一道菜,你需要一个菜谱告诉你用什么食材(基础镜像),按什么顺序加调料(执行命令),最后怎么装盘(容器启动命令)。Dockerfile 就是 Docker 镜像的 “菜谱”。

dockerfile解决了什么问题

Dockerfile 中的每⼀条指令都会创建新的镜像层,这些镜像可以被 Docker Daemon 缓存。再次制作镜像时,Docker 会尽量复⽤缓存的镜像层(using cache),⽽不是重新逐层构建,这样可以节省时间和磁盘空间。Dockerfile 的操作流程可以通过docker image history [镜像名称]查询

docker build 构建流程

  1. 准备阶段:收集并打包构建上下文
  2. 传输阶段:将上下文发送给 Docker 引擎
  3. 执行阶段:引擎解析指令并分层构建镜像

上下文准备与过滤
构建上下文是指执行 docker build 命令时指定的路径(默认是当前目录 .),包含所有需要打包进镜像的文件。

示例:将当前目录作为上下文构建镜像

docker build -t myapp:v1 .

关键点:

.dockerignore 文件:用于排除不需要的文件(类似 .gitignore),提高传输效率。

HTTP 请求与镜像构建

  • 客户端行为:
    • 将上下文目录打包为 .tar 文件
    • 向 Docker 引擎(daemon)发送 HTTP POST 请求,携带:构建参数(如 -t 标签)打包后的上下文文件

分层构建与缓存机制
Docker 采用 分层构建 和 缓存复用 机制来加速镜像构建:

  • 分层构建示例
    Dockerfile:
dockerfile
FROM ubuntu:20.04         # 层1:基础系统
RUN apt-get update        # 层2:更新软件源
RUN apt-get install -y nginx  # 层3:安装 Nginx
COPY . /app               # 层4:复制应用代码
CMD ["nginx", "-g", "daemon off;"]  # 层5:启动命令

构建过程:

每执行一条指令,创建一个临时容器
执行指令并提交变更,生成新的镜像层
删除临时容器,保留镜像层.

  • 缓存匹配规则
    完全匹配:指令必须与缓存中的完全相同(包括空格和参数顺序)
    dockerfile
    • 以下两条指令生成的缓存不同
RUN apt-get install -y python3  # 缓存1
RUN apt-get install python3 -y  # 缓存2(参数顺序不同)
  • 内容校验:对于 ADD 和 COPY 指令,会计算文件内容的哈希值
COPY app.py /app/  # 如果 app.py 内容变化,缓存失效

建议:合理安排指令顺序:

不变的指令(如 FROM、RUN apt-get install)放在前面 频繁变更的指令(如 COPY .)放在后面

2.2 关键字

以下是对 Dockerfile 中常用指令的详细解析,以表格形式对比说明其功能、语法和使用场景,帮助快速理解和记忆:

Dockerfile 常用指令速查表

指令 功能 语法 核心作用 注意事项
FROM 指定基础镜像(必须为第一条指令) FROM <镜像名>[:标签] 基于现有镜像构建新镜像,如 FROM ubuntu:20.04 - 基础镜像可以是公共仓库(如 Docker Hub)或私有仓库
- 支持 scratch 空镜像
MAINTAINER 设置镜像作者信息(已过时,推荐用 LABEL MAINTAINER <作者名或邮箱> 标注镜像维护者信息 - Docker 官方已不推荐使用
- 建议用 LABEL maintainer="xxx" 替代
RUN 在镜像构建阶段执行命令(可创建新镜像层) RUN <命令>RUN ["executable", "param"] 安装依赖、编译代码等,如 RUN apt-get install -y python3 - 每条 RUN 生成一个镜像层
- 建议合并多条命令减少层数(如 && 连接)
CMD 设置容器启动时的默认命令(一个 Dockerfile 只能有一条 CMD CMD ["executable","param1"]CMD command param 定义容器启动时执行的命令,如 CMD ["nginx", "-g", "daemon off;"] - 若 ENTRYPOINT 存在,CMD 作为参数传递
- 可被 docker run 命令覆盖
LABEL 为镜像添加元数据标签(键值对) LABEL = = 标注镜像版本、用途等信息,如 LABEL version="1.0.0" - 支持多个标签
- 可通过 docker inspect 查看
EXPOSE 声明容器运行时监听的端口(需配合 docker run -p 映射端口) EXPOSE <端口> [<端口>...] 提示用户容器使用的端口,如 EXPOSE 80 443 - 仅声明端口,不实际开启防火墙
- 需在运行时用 -p-P 映射
ENV 设置环境变量(构建时和运行时均有效) ENV ENV = 定义环境变量,如 ENV APP_ENV=production - 可在 RUN 指令中引用(如 RUN echo $APP_ENV
- 容器内可通过 $key 访问
ADD 复制文件或目录从上下文到镜像中(支持远程 URL 和压缩包自动解压) ADD <源路径> <目标路径> 复制文件到镜像,如 ADD app.tar.gz /app/ - 远程 URL 会下载文件
- 压缩包(tar、gz 等)会自动解压
- 不推荐用于远程文件
COPY 复制文件或目录从上下文到镜像中(仅支持本地文件) COPY <源路径> <目标路径> 更安全的文件复制方式,如 COPY requirements.txt /app/ - 源路径必须在构建上下文中
- 支持 --chown 参数修改权限
ENTRYPOINT 设置容器的入口程序(可让容器以进程形式运行,CMD 作为参数) ENTRYPOINT ["executable","param1"]ENTRYPOINT command param 定义容器启动时的主程序,如 ENTRYPOINT ["/app/main"] - 通常配合 CMD 使用
- 不会被 docker run 命令覆盖,参数会追加到后面
VOLUME 定义容器的数据卷(用于持久化存储或共享数据) VOLUME <路径>VOLUME ["路径1", "路径2"] 声明数据卷挂载点,如 VOLUME /data - 数据卷会绕过联合文件系统,直接存储在宿主机
- 需在运行时用 -v 绑定
USER 设置后续指令(RUNCMDENTRYPOINT)执行时的用户 USER <用户名或UID> 切换执行用户,如 USER app - 需确保用户存在于镜像中
- 若用户不存在,会在构建时报错
WORKDIR 设置后续指令的工作目录(相当于 cd 目录 WORKDIR <目录路径> 切换工作目录,如 WORKDIR /app - 可多次切换
- 路径支持环境变量(如 WORKDIR $APP_DIR
ARG 定义构建时的参数(不存储在镜像中,仅在构建阶段使用) ARG <参数名>[=<默认值>] 传递构建参数,如 ARG VERSION=1.0 - 可通过 docker build --build-arg <参数>=值 传入
- 不可在容器运行时访问
ONBUILD 设置子镜像构建时触发的指令(当前镜像被其他镜像继承时执行) ONBUILD <指令> 定义“钩子”指令,如 ONBUILD RUN apt-get update - 仅在其他镜像 FROM 当前镜像时生效
- 慎用,可能导致构建逻辑复杂
STOPSIGNAL 设置容器接收退出信号的机制(如 SIGTERMSIGKILL STOPSIGNAL <信号量> 自定义容器停止信号,如 STOPSIGNAL SIGINT - 需与宿主机信号机制兼容

关键指令对比与最佳实践

1. RUN vs CMD vs ENTRYPOINT
指令 执行时机 用途 典型场景
RUN 构建阶段执行 安装依赖、修改文件系统等(生成镜像层) RUN apt-get install -y nginx
CMD 容器启动阶段执行 定义默认命令(可被 docker run 覆盖) CMD ["nginx", "-g", "daemon off;"]
ENTRYPOINT 容器启动阶段执行 定义入口程序(参数可通过 CMDdocker run 传递) ENTRYPOINT ["/app/main"] 配合 CMD ["--config=prod"]

2. ADD vs COPY

指令 源支持 自动解压 网络请求 安全性 推荐场景
ADD 本地文件、远程URL、压缩包 较低(远程文件风险) 复制压缩包并自动解压
COPY 本地文件 较高 复制本地配置文件、代码
3. ENV vs ARG
指令 作用范围 是否存储到镜像 访问时机 用途
ENV 构建阶段和运行阶段 构建时和运行时 定义环境变量(如时区、版本)
ARG 仅构建阶段 仅限构建时 传递临时参数(如 Git 哈希)

指令使用原则

  1. 分层最小化

    • 合并多条 RUN 命令(如 RUN apt-get update && install -y ...),减少镜像层数。
    • 避免频繁生成小层,影响构建效率和镜像体积。
  2. 缓存最大化

    • 不变的指令(如 FROMRUN 安装依赖)放在前面,利用缓存。
    • 频繁变更的指令(如 COPY .)放在后面,减少缓存失效范围。
  3. 安全性优先

    • COPY 替代 ADD 复制本地文件,避免远程 URL 风险。
    • 使用非 root 用户运行容器(USER app),降低安全漏洞影响。
  4. 清晰可维护

    • 添加注释(# 安装 Web 服务器),说明每条指令的目的。
    • 合理使用 LABEL 标注镜像元数据,便于团队协作和检索。

2.3 基础版实战

实战内容运行一个nginx镜像

docker build -t my-nginx:v1 . 

这个命令用于构建 Docker 镜像,下面详细解析其各个部分的含义和作用:

核心参数

参数 作用
-t 指定镜像的名称(tag),格式为 名称:版本号。若不指定版本,默认为 latest
-f 指定 Dockerfile 的路径。若省略,默认使用当前目录下的 Dockerfile
. 构建上下文(build context)的路径,表示将该目录下的所有文件发送给 Docker 引擎用于构建。

docker内容

FROM nginx:1.23-alpine
LABEL maintainer="[email protected]" version="1.0.0" description="Custom Nginx with custom config and page"

# 3. 清理默认配置(删除官方默认的 nginx.conf)
RUN rm -rf /etc/nginx/conf.d/default.conf && \
    rm -rf /etc/nginx/nginx.conf

# 4. 将自定义配置复制到镜像中
COPY nginx.conf /etc/nginx/nginx.conf

# 5. 将自定义网页内容复制到镜像的 HTML 目录
COPY html/ /usr/share/nginx/html/

# 在 Dockerfile 中通过 EXPOSE 80 声明容器内服务监听 80 端口
EXPOSE 80

# 7. 定义容器启动命令(保持与官方镜像一致)
CMD ["nginx", "-g", "daemon off;"]

第一句 而言 建议你先拉取镜像到本地
docker pull nginx:1.23-alpine
第二句而言
# 旧写法(已过时) MAINTAINER Your Name

3 4 5句
COPY 指令会覆盖目标路径下的同名文件
docker run -it --rm my-nginx:v1 sh
你可以通过辨别文件是否改变

第七 运行命令

docker run -d -p 80:80 my-nginx-image

前面一个80是宿主机的端口,第二个80是容器端口

这个CMD是这个nginx的启动命令,当你运行容器的时候,自动启动

2.4 docker进阶

Docker 17.05版本以后,新增了Dockerfile多阶段构建。所谓多阶段构建,实际上是允许⼀个

Dockerfile 中出现多个 FROM 指令。这样做有什么意义呢?

多个 FROM 指令的意义
多个 FROM 指令并不是为了⽣成多根的层关系,最后⽣成的镜像,仍以最后⼀条 FROM 为准,之
前的 FROM 会被抛弃,那么之前的FROM ⼜有什么意义呢?

每⼀条 FROM 指令都是⼀个构建阶段,多条 FROM 就是多阶段构建,虽然最后⽣成的镜像只能是
最后⼀个阶段的结果,但是,能够将前置阶段中的⽂件拷⻉到后边的阶段中,这就是多阶段构建的
最⼤意义。

3 数据卷

3.1 数据卷基本概念

一、为什么需要数据卷?先看容器的 “天生缺陷”
假设你用 Docker 运行了一个网站容器:

容器里的文件藏得深:容器里的文件被 “封装” 在镜像的多层只读文件里,加上最上层的读写层,想直接在电脑上找到它们?难如大海捞针。

  • 多个容器像孤岛:比如你有两个网站容器,都想读取同一份用户数据,传统方式下它们互不认识,无法共享。
  • 容器一删数据就没:容器删掉后,里面的文件就像随船沉没的宝藏,说没就没了。

二、数据卷是什么?给容器加个 “外挂硬盘”
数据卷(Volume)是 Docker 提供的一种独立于容器生命周期的存储机制,可以理解为:

  • 一个专门存数据的文件夹,位于电脑(宿主机)上,但可以被容器直接访问。
  • 不受容器 “生死” 影响:容器删了,数据卷还在;新容器来了,还能接着用之前的数据。
  • 支持多容器共享:多个容器可以同时读写同一个数据卷,就像多人共用一个 U 盘。

三、数据卷的核心功能:解决三大痛点

  1. 数据持久化:删容器,数据不丢
    场景:你用容器运行数据库(如 MySQL),数据存在容器里。一旦容器崩溃或删除,数据就没了。
  2. 宿主机与容器互访:轻松读写容器文件
    场景:你想修改容器里的配置文件(如 Nginx 的nginx.conf),传统方式要进容器里改,麻烦!
  3. 多容器共享数据:多个容器共用一份数据
    场景:一个 Web 容器和一个文件处理容器,都需要读取同一批图片文件。
    解决方案:两个容器挂载同一个数据卷。

3.2 数据卷的操作

创建数据卷

docker volume create --name vol_simple

指定容器数据卷

//创建⼀个指定名字的volume ,并挂载到容器中的/data⽬录
docker run -d -v vol_simple:/data ubuntu

指定宿主机数据卷

docker run -d -v $HOME/data:/data ubuntu /bin/bash

设置权限

docker run -it -v $HOME/data:/data:ro -v /data1 -v /data2 ubuntu
/bin/bash

ro表示只读,rw表示读写,默认为rw

同时挂载多个数据卷

docker run -it -v $HOME/data:/data:ro -v /data1 -v /data2 ubuntu /bin/bash

通过dockerfile指定数据卷挂载

//dockerfile 添加指令
VOLUME /data
// 指定多个数据卷的挂载点
VOLUME ["/data1","/data2"]

没有设置宿主机数据卷

在 Docker 中,使用 VOLUME /data 声明的数据卷确实没有显式命名,但 Docker 会自动为其生成一个唯一的匿名卷(Anonymous Volume)
docker inspect <容器ID> | grep -A 10 Mounts

4 docker 网络

4.1 基本概念

网桥
在⼀台未经特殊⽹络配置的Ubuntu机器上安装完Docker之后,在宿主机上通过ifconfig命令可以看
到多了⼀块名为docker0的⽹卡。该⽹卡即是docker0⽹桥,⽹桥在这⾥的作⽤相当于交换机,为连接在其上的设备转发数据帧。⽹桥上的veth⽹卡设备相当于交换机上的端⼝,可以将多个容器或虚拟机连接其上,这些端⼝⼯作在⼆层,所以是不需要配置IP信息的。

CNN
CNM(Container Network Model)译为容器⽹络模型,定义了构建容器虚拟化⽹络的模型。该模
型主要包含三个核⼼组件 沙盒、端点、⽹络
*一、沙盒(Sandbox)容器的 “通信工具箱”

  • 类比:相当于每个容器的 “私人房间”,里面放着这个容器的 “通信设备”(网络配置)。
    比如:房间里有 “电话号码本”(DNS 设置)、“地图”(路由表)、“窗户”(网络接口,比如网卡)。
    每个房间(沙盒)可以有多个 “窗户”(端点),通过不同的 “窗户” 连接到不同的 “街道”(网络)。

  • 作用:管理容器自己的网络设置,比如分配 IP 地址、设置访问规则。不同容器的 “房间” 互相隔离,确保网络配置不会混乱。

二、端点(Endpoint):容器的 “网络接口”

  • 类比:相当于 “房间窗户” 和 “街道” 之间的 “门”,一端连容器(沙盒),另一端连网络(街道)。
    每个 “门”(端点)只能属于一个 “房间”(沙盒)和一条 “街道”(网络),不能同时连两条街。
    多个容器可以通过各自的 “门” 连接到同一条 “街道”,这样它们就能在这条街上 “见面”(通信)。
  • 作用:作为容器接入网络的 “入口 / 出口”,比如容器通过端点获取网络数据,或者发送数据到网络。
    一个容器可以有多个端点(多个 “门”),连接到不同的网络(不同的 “街道”),实现 “跨网络通信”。

三、网络(Network):容器的 “通信街道”

  • 类比:相当于一条 “虚拟街道”,所有通过端点连接到这条街的容器(房子),都能在这条街上直接 “聊天”(通信)。
    比如:“街道 A” 上的所有容器可以互相访问 IP 地址,“街道 B” 上的容器只能在 B 内通信,不同街道的容器默认不能直接交流(除非有 “路由器” 之类的额外设置)。
  • 作用:定义容器之间的通信范围,同一网络内的容器可以直联通,不同网络的容器需要额外配置才能通信。

libnetwork

ibnetwork 实现了CNM 的Docker⽹络组件库。libnetwork内置了⼏种⽹络驱动。

  • bridge驱动。此驱动为Docker的默认设置,使⽤这个驱动的时候,libnetwork将创建出来的docker容器连接到Docker⽹桥上。作为最常规的模式,bridge模式已经可以满⾜docker容器最基本
    的使⽤需求了。
  • host驱动。使⽤这种驱动的时候,libnetwork将不为docker容器创建⽹络协议栈,即不会创建
    独⽴的network namespace。Docker容器中的进程处于宿主机的⽹络环境中,相当于docker容器与宿主机共同使⽤⼀个network namespace,使⽤宿主机的⽹卡、IP和端⼝等信息。但是,容器的其他⽅⾯,如⽂件系统、进程列表等还是和宿主机隔离的。
  • remote 驱动。这个驱动实际上并没有做真正的⽹络服务实现,⽽是调⽤了⽤户⾃⾏实现的⽹络驱动插件,使libnetwork实现了驱动的可插件化,更好的满⾜了⽤户的多种需求。⽤户只要根据libnetwork提供的协议标准,实现其所要求的各个接⼝并向Docker daemon进⾏注册。

4.2 实战

以下是 Docker 网络管理命令的实战演示,结合具体场景帮助你理解如何操作:

一、创建自定义网络(实战场景:微服务组网)

假设你有两个容器:web-app(前端)和 api-server(后端),需要让它们通过自定义网络通信。

1. 创建一个桥接网络(命名为 my-microservices
docker network create my-microservices
  • 验证:查看网络列表
    docker network ls
    # 输出中应包含 my-microservices 网络
    
2. 启动容器并加入网络
启动前端容器(web-app)
docker run -d --name web-app \
  --network my-microservices \  # 指定加入 my-microservices 网络
  -p 8080:80 \
  nginx:alpine
启动后端容器(api-server)
docker run -d --name api-server --network my-microservices -p 3000:3000 python:3.9-alpine python -m http.server 3000
3. 容器间通信测试
  • 进入 web-app 容器
    docker exec -it web-app sh
    
  • 在容器内访问 api-server(直接通过容器名通信)
    curl http://api-server:3000  # 容器名自动解析为 IP
    # 输出:Hello from API
    
  • 原理:同一网络内的容器可通过容器名直接通信,Docker 自动实现 DNS 解析。

二、动态连接/断开容器与网络(实战场景:临时组网)
假设你有一个已运行的容器 legacy-app,需要临时加入 my-microservices 网络与其他容器通信。

1. 查看当前容器连接的网络

docker inspect legacy-app | grep -A 10 "Networks"

2. 将容器连接到新网络

docker network connect my-microservices legacy-app

3. 验证连接结果

docker exec -it legacy-app sh
curl http://api-server:3000  # 现在可以访问同一网络的 api-server

4. 断开容器与网络

docker network disconnect my-microservices legacy-app
curl http://api-server:3000  # 再次访问会失败,已断开网络连接

三、查看网络详细信息(实战场景:排查网络问题)
1. 查看 my-microservices 网络的详细配置

docker network inspect my-microservices

关键输出

[
  {
    "Name": "my-microservices",
    "Id": "xxx",
    "Driver": "bridge",  // 网络类型为桥接
    "IPAM": {  // IP 地址管理
      "Config": [
        {
          "Subnet": "172.18.0.0/16"  // 子网段,容器IP从这里分配
        }
      ]
    },
    "Containers": {  // 连接到该网络的容器列表
      "api-server": {
        "IPv4Address": "172.18.0.2/16"
      },
      "web-app": {
        "IPv4Address": "172.18.0.3/16"
      }
    }
  }
]

2. 分析容器 IP 地址

  • 同一网络内的容器共享 172.18.0.0/16 子网,可通过 IP 直接通信:
    docker exec -it web-app sh
    curl http://172.18.0.2:3000  # 用 IP 访问 api-server
    

四、清理无用网络(实战场景:释放磁盘空间)
1. 查看所有网络

docker network ls
  • 假设存在未使用的网络 old-network

2. 删除指定网络

docker network rm old-network

3. 批量删除所有未使用的网络

docker network prune
  • 执行后会提示确认,按 y 确认删除。

常用命令速查表

场景 命令 说明
创建桥接网络 docker network create my-bridge 默认类型为 bridge
创建 overlay 网络 docker network create --driver overlay my-overlay 用于跨主机集群通信
查看容器网络连接 `docker inspect <容器名> grep Networks`
动态连接网络 docker network connect my-network <容器名> 将运行中的容器加入网络
清理无用网络 docker network prune 删除所有未被容器使用的网络
删除指定网络 docker network rm my-network 删除单个网络(需先断开所有容器)

通过以上实战操作,你可以灵活管理 Docker 容器的网络连接,实现容器间通信、跨主机组网和资源清理等功能。

你可能感兴趣的:(中间组件,docker,网络,容器)