类比

镜像(Image) ≈ 类(模板、静态的)
容器(Container) ≈ 对象(实例、运行中的)

镜像是静态的文件,包含运行程序所需的一切:代码、运行时、库、环境变量、配置文件。

容器是镜像的运行实例——一个被隔离的进程。

镜像的结构

分层(Layer)

Docker 镜像由多个只读层叠加而成。

每一层对应 Dockerfile 中的一条指令(如 RUN、COPY)。

优势:

一、共享与复用

多个镜像如果共享相同的层(如相同的基础镜像),这些层只存储一份。

这大幅节省了磁盘空间和镜像拉取的带宽。

二、构建缓存

如果某条指令及其上下文没变,Docker 可以复用之前构建的层,跳过重新构建。

实践含义:Dockerfile 中,把变化频率低的指令放前面(如安装依赖),变化频率高的放后面(如复制源码)——这样能最大化利用缓存。

联合文件系统(Union FS)

多层通过联合文件系统(如 overlay2)合并成一个统一的文件视图。

上层的文件会"遮蔽"下层同路径的文件。

容器的可写层

容器启动时,Docker 在镜像的只读层之上添加一个可写层(容器层)。

写时复制(Copy-on-Write):

  • 读取文件:从下面的只读层读;
  • 修改文件:先把文件复制到可写层,再修改;
  • 删除文件:在可写层标记一个"whiteout",遮蔽下层文件。

关键含义:

容器内的文件修改不会写入镜像。 多个容器可以共享同一个镜像,各自的可写层独立。

容器删除后,可写层也删除了——因此容器内的数据默认不持久。

持久化:Volume

要在容器重启后保留数据,需要卷(Volume):

一、命名卷(Named Volume)

由 Docker 管理的存储位置,与容器生命周期解耦。

docker run -v mydata:/var/lib/mysql mysql

二、绑定挂载(Bind Mount)

把宿主机的某个目录挂载到容器中。

docker run -v /host/path:/container/path myapp

开发时常用(挂载源码目录,实现热重载)。

三、tmpfs 挂载:只存在于内存中。

常用命令

# 构建镜像
docker build -t myapp:1.0 .

# 查看镜像
docker images

# 运行容器
docker run -d -p 8080:80 --name mycontainer myapp:1.0

# 查看运行中的容器
docker ps

# 停止/启动/删除容器
docker stop mycontainer
docker start mycontainer
docker rm mycontainer

# 删除镜像
docker rmi myapp:1.0

# 清理未使用的资源
docker system prune

几个实践要点

一、镜像要小

  • 使用精简基础镜像(如 Alpine、slim 版本、distroless);
  • 多阶段构建(Multi-stage Build):在一个阶段编译,在另一个阶段只复制产物——避免把编译工具链打包进最终镜像;
  • 清理包管理器缓存。

二、一个容器一个进程(推荐)

虽然技术上可以在一个容器里跑多个进程,但这违背了容器的设计初衷(不利于隔离、扩缩容、日志处理)。

三、注意层缓存失效

COPY . . 会把整个上下文复制进去,任何文件变化都会导致后续层的缓存失效。用 .dockerignore 排除无关文件。

四、不要在容器里保存状态

容器应该是无状态、可随时销毁重建的。状态放在卷或外部服务中。

说明:本文为容器技术的知识整理。