拓冰建站拓冰建站
首页 / 资讯中心 / 正文

Docker入门:彻底搞懂镜像与容器的区别、原理与实战

很多人第一次接触 Docker最先被绕晕的就是 Images 和 Container 这两个概念。镜像到底是个什么东西容器又是什么为什么我拉了一个镜像然后 run 一下就有了一个容器这两个词总是一起出现好像是一码事但仔细想想又不太对。我当年学 Docker 也卡在这里好几天。后来发现想真正理解 Docker光把命令背下来没有意义关键是把三者的分工搞清楚Docker 是一个容器平台镜像是静态的交付包容器是镜像运行起来的动态实例。打个比方你买了一份软件安装包安装包是镜像双击运行后出现在进程列表里的那个程序就是容器。这篇文章按这个思路展开主要面向刚接触 Docker 的读者也适合那些已经能用 docker run 拉镜像、但还说不清镜像层、数据卷、网络模式的“半新手”。我会把原理、命令、踩坑经验混在一起讲。看完之后你不但会操作还能跟别人解释清楚为什么容器启动这么快、为什么删掉容器以后数据就没了、为什么一台机器能跑几十个容器还不卡。1. 先搞明白Docker到底解决了什么问题1.1 环境一致性和交付格式的统一我们先说痛点。开发人员最常见的一句话就是“我本地跑得好好的怎么到你那就报错”。问题往往出在环境上系统版本不同、依赖库版本不同、配置文件不同甚至环境变量不一样。传统办法是写一堆部署文档让运维照着一步一步来但文档和现实总是有出入。Docker 提供了一种新的交付方式把应用和它运行所需的操作系统依赖、库、配置全部打包进镜像里。镜像在哪儿构建就带到哪儿运行只要目标机器上有 Docker就能保证运行环境一致。这就把“环境差异”问题从部署环节里抹掉了。你在本地 build 出来的镜像和测试环境、生产环境拉到的镜像是同一个东西这就是“一次构建到处运行”的含义。1.2 从虚拟机到容器资源利用率的进化在 Docker 之前隔离环境最流行的是虚拟机。开发机上装个 VMware 或 VirtualBox创建一个虚拟的硬件环境再在虚拟硬件里装一个完整的操作系统。隔离性确实好但代价也高一个虚拟机动不动几个 GB 的磁盘启动要等几十秒甚至几分钟CPU 和内存都要预留。容器则完全换了一条路。容器不是虚拟出一套硬件也不是在容器里再装一个 Linux而是直接复用宿主机的操作系统内核把进程、文件系统、网络、进程间通信等资源通过内核机制隔离成一个个彼此独立的“空间”。在这个空间里进程以为自己独占整台机器实际上大家共享同一个内核。于是容器镜像只需要装应用和它依赖的库体积小启动就是起进程几秒钟就能完成。一台物理机跑十几个容器是很正常的事一个容器的内存占用可以低到几十 MB。用生活类比的话虚拟机是每个人分一套独门独户的房子容器是你在同一栋楼里给自己租了不同房间墙是隔开的但水电燃气还是同一套管道。隔离级别不同代价也不同。1.3 Docker的整体组成不只是那个鲸鱼图标Docker 不是一个单一的可执行文件它其实是一整套工具链。最底层是容器的运行时比如 containerd 和 runc真正负责创建容器、管理容器进程再往上是 Docker daemon也就是 dockerd它向外提供 API负责镜像管理、容器生命周期、网络、数据卷这些编排能力用户平时敲的 docker 命令是 CLI 客户端它把命令翻译成 API 请求发给 daemon。中间还有镜像仓库 RegistryDocker Hub 是最出名的一个存着大量官方和社区镜像。另外在 Windows 和 macOS 上Docker Desktop 还内置了一个 Linux 虚拟机因为 Docker 容器依赖 Linux 内核功能这层虚拟机相当于让 Windows/macOS 也能跑 Linux 容器。知道这些之后再看到 docker version 输出里分 Client 和 Server 两段就很好理解了Client 对应你敲的 CLIServer 对应后台的 daemon。如果 Server 连不上说明 daemon 没起来你敲的 docker ps 自然只会报错。2. Docker镜像只读的“交付包”2.1 镜像到底是什么一个分层文件系统镜像可以简单理解成一个只读的模板里面装着运行某个应用所需要的所有文件。注意“所有”这个词包括操作系统的基础库、运行环境、应用代码、配置文件、环境变量默认值。但它不是一个系统盘镜像那种“整盘克隆”而是一个分层的文件系统集合。一个 Docker 镜像由若干层Layer组成每一层都记录了一部分文件变更。比如最底层是基础 Linux 发行版上面再加一层装好的 JDK再加一层复制进去的 jar 包。这就像叠千层饼每一层做完之后不会再变。为什么设计成只读因为镜像要作为交付包被反复使用只有只读才能保证任何人拿到这个镜像看到的都是同一个东西。运行一个容器时Docker 会在镜像这些只读层之上加一层临时的可写层容器内的修改都发生在这一层。如果容器删了这层也就没了。这句话就是后面解释“容器里为什么不能存数据”的关键。2.2 镜像分层带来的复用与缓存分层带来的直接好处是复用和缓存。实际部署时你会频繁拉取不同应用镜像但很多应用用的基础层是一样的比如都基于某个版本的 Ubuntu 或 Alpine这些公共层在本地只要存一份就能被多个镜像共享。从仓库拉镜像时如果本地已经有某些层Docker 会只下载缺失的层这就是为什么更新镜像通常比第一次拉取快很多。构建镜像时也一样Dockerfile 的每一条指令会生成一个新层如果从上一条指令到某条指令的内容没有变化构建时会直接引用缓存层不用重新执行。所以 Dockerfile 里把不常变化的依赖安装命令写在前面把经常变化的代码复制写在后面能大幅加快构建速度。这个细节在 CI/CD 流水线里尤其重要能帮团队省下不少时间。2.3 镜像操作实操拉取、查看、删除、打标签先来过一遍跟镜像相关的常用命令每个我都标注了使用场景。拉镜像docker pull nginx:1.27-alpine其中 nginx 是镜像名1.27-alpine是 tag默认不写 tag 时是 latest。查看本地镜像docker images输出里有仓库名、tag、镜像 ID、大小。删除镜像docker rmi 镜像名或ID如果有容器正在使用该镜像会提示冲突需要先停用并删掉容器。打标签docker tag nginx:1.27-alpine myregistry.example.com/nginx:1.27这相当于给镜像起个别名常用于准备推到私有仓库前。查看镜像细节docker inspect nginx:1.27-alpine能看到 Cmd、入口点、环境变量、架构、层数等元数据。查看每一层docker history nginx:1.27-alpine。最后给镜像瘦身时经常用到docker system df它能列出镜像、容器、卷、构建缓存占了多少磁盘。2.4 自己构建镜像Dockerfile 与构建上下文光拉别人做好的镜像不够生产上更多是把自己应用打成镜像。这里给一个最简单的 Node.js 例子。新建目录里面放一个 app.js内容随便写个 HTTP 服务然后写 DockerfileFROM node:18-alpine WORKDIR /app COPY package.json ./ RUN npm install COPY . . EXPOSE 3000 CMD [node, app.js]构建命令是docker build -t my-app:v1 .注意这个点不能少它表示构建上下文目录。Docker 会把该目录所有文件发给 daemon作为 COPY 等指令的输入。所以应该在项目目录里加 .dockerignore把 node_modules、.git、日志文件排除掉否则构建发送的数据量会大得吓人。镜像构建完成后用docker run -p 3000:3000 my-app:v1启动。理解 Dockerfile 的时候可以把每一行指令和镜像的一层对应起来。如果以后要往私有仓库推先docker login你的仓库地址再docker tag成仓库允许的命名最后docker push镜像名。2.5 镜像层与容器层的关系读写层从哪来上面提到了容器层这里单独展开。启动一个容器时Docker 不会改动镜像本身而是把镜像所有只读层挂载为一个统一目录然后在最顶层加一个可写层这个可写层就是我们说的容器层。容器里读写文件看似正常实际读文件时Docker 会从上往下找到匹配的层修改文件时则把文件从只读层复制到可写层再修改这称为 copy-on-write。这个机制让多个容器可以共享同一个镜像每个容器的可写层相互隔离互不影响。代价是容器被删除后可写层连着文件一起被删掉所以容器内产生的数据如果不做持久化就会随容器消失。这也是为什么给别人讲 Docker 时我会反复强调容器是有状态的但这个状态默认是临时的想要持久保存必须把数据写到数据卷或宿主机目录。3. Docker容器运行的“实例”3.1 容器和镜像的关系类与对象镜像和容器是“类与对象”的关系。类是定义对象是根据类创建出来的实例镜像是模板容器是按模板启动起来的运行实例。一个镜像可以同时跑多个容器就像同一个安装包能安装多次每次安装后的运行状态完全独立。比如你有一个 nginx 镜像可以同时启动两个容器一个叫 web1映射到宿主机 8080另一个叫 web2映射到 8081。它们代码一样但端口、配置、运行状态互不干扰。理解这层关系后很多命令就顺其自然了。docker run其实是“创建启动”的合并命令docker create只创建不启动docker stop是停止容器进程docker rm是把容器这个实例彻底删除。注意rm 删除的是容器而不是镜像。如果镜像还在你依然可以再用 docker run 创建出全新的容器。3.2 容器的生命周期创建、启动、停止、删除容器的整个生命周期可以用几个命令串起来。先用docker create创建这个阶段容器处于 created 状态用docker start启动进入 runningdocker stop会先给容器内 PID 1 进程发送 SIGTERM给它优雅退出的机会等默认 10 秒超时后再发 SIGKILL。这个超时时间可以用--stop-timeout调整。docker restart是停止后重新启动docker pause只是挂起进程不会停掉容器docker unpause恢复。如果容器只剩临死状态用docker rm -f强制删除。平时用的最多的是docker run -d它会创建并启动容器-d 表示后台运行。交互式调试则常用docker run -it --rm 镜像名 bash--rm 表示退出后自动删除容器非常适合临时容器。想清理所有已停止的容器执行docker container prune用之前要看清提示。3.3 容器的隔离原理Namespace 和 Cgroups容器之所以能实现“轻量隔离”靠的是 Linux 内核两大机制Namespace 和 Cgroups。Namespace 负责抽象资源视图让一个容器里的进程只能看到自己的 PID、网络、文件系统、用户等信息。最常见的几个命名空间包括 PID进程号从 1 开始、Mount挂载点独立、Network独立的网卡、路由表、UTS独立主机名、IPC进程间通信、User用户映射。Cgroups 负责限制资源使用可以给容器设置 CPU 份额、内存上限、磁盘 IO 权重等。两者配合容器里的进程才会“以为自己独占了一台机器”但实际上所有容器都跑在宿主机同一个内核上。换句话说容器隔离的是“看到的视图”不是硬件。这也是容器安全边界不如虚拟机的原因之一。如果某个容器里的进程拿到了宿主机内核漏洞的利用能力它有可能影响宿主机和其他容器所以生产环境不建议用 root 用户跑容器也不建议把特权设备随意挂给容器。3.4 容器数据持久化数据卷与挂载容器删除后数据会丢前面已经解释过原因。解决办法是把数据放到容器外面。Docker 提供了两种主要方式数据卷 Volume 和绑定挂载 Bind Mount。数据卷是 Docker 管理的目录默认放在宿主机的 /var/lib/docker/volumes/ 下通过 docker volume create 或 docker run -v 直接创建绑定挂载则是把宿主机任意目录直接映射进容器比如 -v /home/user/app:/app。两者命令形式上很像-v 卷名:容器目录是数据卷-v 宿主机目录:容器目录是绑定挂载。生产环境一般优先用数据卷因为它由 Docker 统一管理备份迁移都方便开发调试时绑定挂载更适合因为改代码不用重新构建镜像。以 MySQL 为例docker run -d --name mysql8 -v mysql_data:/var/lib/mysql -e MYSQL_ROOT_PASSWORD123 mysql:8.0即使容器删了mysql_data 卷还在重新启动新容器只要挂载同名卷数据就回来了。卷的备份也可以用容器来做docker run --rm -v mysql_data:/volume -v $(pwd):/backup alpine tar czf /backup/mysql_data.tar.gz -C /volume .3.5 容器网络模式与端口映射默认情况下Docker 创建一个名为 bridge 的虚拟网桥容器创建后会自动接入这个网络得到一个 172.17.x.x 的地址宿主机和容器之间通过这个网桥通信。要让外部访问容器里的服务必须做端口映射docker run -p 8080:80意思是把宿主机的 8080 端口转发到容器的 80 端口。注意 -p 前面的端口是宿主机端口后面是容器端口顺序写反的坑我见过不止一次。容器默认也能通过 NAT 访问外网常常有人在容器里 ping 不通外网先检查 DNS 和宿主机网络。需要多个容器互相通信时推荐自建 bridge 网络docker network create mynet然后启动容器时加--network mynet。这样同一网络里的容器可以用容器名直接互相访问比如 web 容器访问数据库容器的地址可以直接写 mysql8:3306Docker 内置 DNS 会解析到对应 IP。自定义网络还有个好处不用按 IP 配置容器重启 IP 变了也不会影响服务。至于 host 模式容器直接使用宿主机网络栈性能好但隔离性变弱none 模式则是完全隔离网络适合测试专用场景。3.6 容器日志与调试容器排障基本就是三板斧logs、exec、inspect。docker logs -f 容器名可以实时查看容器内进程输出到 stdout/stderr 的日志注意一定要让应用把日志写到标准输出而不是写到文件里否则 docker logs 什么都看不到。docker exec -it 容器名 bash可以进入容器交互适合检查文件、装工具、人工验证服务。docker inspect 容器名可以查看容器的完整元数据包括网络 IP、挂载点、环境变量、状态。我用得最多的是docker inspect --format {{json .NetworkSettings}} 容器名快速拿到容器网络配置。如果容器启动后立刻退出先 docker logs 看最后几条输出多半能直接看到原因。还有一个容易被忽略的技巧docker top 容器名能列出容器内运行的进程确认主进程是否还活着。如果容器是交互式进程需要 -it 才能维持运行只加 -d 有些镜像会因为前台没有进程而退出这个问题在调试时经常遇到。4. 实操过程用一次部署把整套概念串起来4.1 安装 DockerLinux、Windows、macOS 三种场景安装 Docker不同系统差异很大。Linux 上最方便的是用官方安装脚本我在国内的服务器上安装时会多加一个镜像参数加快下载跑完脚本后执行 systemctl enable --now docker 让服务随开机自启。也可以用发行版自带的包管理器Ubuntu 上 apt install docker.ioCentOS 上 yum install docker-ce后者需要配置 Docker 官方仓库步骤多一些。Windows 装的是 Docker Desktop它会要求开启 WSL2 并启用虚拟化。如果启动时提示 Virtualization support not detected基本就是 BIOS 里没开 Intel VT-x 或 AMD-V开机进 BIOS 开启后重启就好了另外旧版 Docker Desktop 还可能依赖 Hyper-V需要在“启用或关闭 Windows 功能”里把 Hyper-V 勾上。macOS 装 Docker Desktop 相对省心但苹果芯片和 Intel 芯片的安装包不能混用。装完统一验证docker version 看 Client 和 Server 都能显示版本docker run hello-world 能打印出欢迎信息说明整套链路是通的。别小看 hello-world它同时验证了 CLI 连接 daemon、拉取镜像、创建容器、运行进程四个环节。4.2 解决镜像下载慢配置镜像加速源Docker Hub 镜像下载慢的问题在国内环境几乎绕不开。解决办法是配置 registry mirror。Linux 上编辑 /etc/docker/daemon.json写这样的内容{ registry-mirrors: [https://docker.m.daocloud.io, https://dockerproxy.com] }写完后 sudo systemctl restart docker再 docker info 看 Registry Mirrors 字段是否生效。Windows/macOS 上的 Docker Desktop 则在 Settings - Docker Engine 里编辑同一个 JSON 文件。需要提醒的是镜像加速源并不保证所有镜像都稳定大型镜像如 GitLab、Hadoop 镜像加速效果也可能一般必要时可以换多个源或直接在高峰时段避开拉取。还有一个无法避免的情况某些仓库本身在国内没有权威镜像只能通过专用内网仓库拉取这时你可能需要在 daemon.json 里配置 insecure-registries把私有仓库地址加进去否则访问自签证书的仓库会被拒绝。4.3 部署 MySQL 8.0镜像、容器、端口、数据卷一起串起来下面用一个完整场景把前面所有概念过一遍部署 MySQL 8.0。第一步拉镜像docker pull mysql:8.0看到多层下载完成本地镜像列表里就有 mysql:8.0 了。第二步启动容器docker run -d \ --name mysql8 \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORDroot123 \ -e TZAsia/Shanghai \ -v mysql8_data:/var/lib/mysql \ mysql:8.0这里 --name 给容器命名-p 做端口映射-e 传环境变量。MYSQL_ROOT_PASSWORD 是 MySQL 镜像规定必须设置的变量TZ 解决时区问题-v 挂载命名卷到容器内 MySQL 数据目录。执行后 docker ps 能看到运行状态再 docker exec -it mysql8 mysql -uroot -proot123 进入 MySQL 命令行执行 show databases 验证。如果之前没有单独创建 volumeDocker 会在第一次执行时自动创建命名卷 mysql8_data。我刚开始学的时候漏了 -e容器就会一直重启或直接退出日志里明确写着缺少环境变量这也算是一个经典的“容器启动失败先看日志”案例。完成后可以试试 docker stop mysql8 停掉容器再 docker start mysql8 启动你会发现数据都在因为数据在卷里不在容器层。4.4 拆解 docker run 命令里的常用参数刚才那条 docker run 看着长拆开看就不难了。每个参数都有它的目的。我整理成表格参数作用典型场景-d后台运行容器常驻服务-it交互式运行分配终端并保持标准输入调试、进容器执行命令--name指定容器名便于管理、日志定位-p宿主机端口:容器端口外部访问容器服务-v卷或目录挂载持久化数据、同步代码-e环境变量传数据库密码、时区等配置--restart容器退出时重启策略生产环境常用 always--network指定网络自定义 bridge、host 等--rm退出后自动删除容器临时任务、调试这里单独说 --restart。生产环境建议给常驻服务加上 --restartalways这样机器重启后 Docker 会自动把容器拉起来能少跑很多次“服务器重启后服务没起来”的现场。但要注意如果应用的启动是有依赖顺序的比如先数据库后业务最好用 Docker Compose 或编排系统处理否则一台机器上几十个容器同时重启顺序不可控。docker run 不是唯一创建容器的方式但所有参数在 docker create 上同样适用。理解这一套之后以后再看 K8s 的 Pod 定义中的容器字段会发现很多概念是相通的。4.5 用 Docker Compose 管理多容器单条 docker run 适合单个容器真实项目往往需要多个服务一起跑。这时用 Docker Compose 更合适它通过一个 YAML 文件描述服务之间的依赖、网络、卷、端口。举个例子一个最简单的 MySQL 加 Redis 组合services: mysql8: image: mysql:8.0 restart: always environment: MYSQL_ROOT_PASSWORD: root123 volumes: - mysql8_data:/var/lib/mysql ports: - 3306:3306 redis: image: redis:7-alpine restart: always ports: - 6379:6379 volumes: mysql8_data:文件放在项目目录执行 docker compose up -dDocker 会自动创建默认网络和命名的卷。关键点在于服务名可以当作主机名互相访问业务容器里连接数据库直接写 mysql8:3306 就行。docker compose down 会停止容器但不会删除数据卷docker compose down -v 才会把卷一起删掉这个 -v 要谨慎我见过有人图省事加 -v结果数据库数据没了。2023 年之后的 Docker Compose 已经不需要写 version 字段写了也不影响但老项目里经常能看到。新学的话直接把 version 行忽略掉也没关系。5. 常见问题与排查技巧实录5.1 Docker 服务启动失败和权限错误Docker 最常见的权限报错是 Got permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock。原因是当前用户不在 docker 用户组里。Linux 执行 sudo usermod -aG docker $USER然后退出重新登录再执行 docker ps 验证。注意这里用的是 -aG不是 -G写错会把用户原有附加组覆盖掉虽然不至于出大事但很容易搞出权限问题。另一个问题是 docker version 里 Client 正常Server 报 Cannot connect to the Docker daemon多半是 dockerd 没起来。systemctl status docker 看一下journalctl -u docker 看日志。Windows 上如果 Docker Desktop 一直起不来常见错误是 failed to connect to the docker api at npipe:////./pipe/dockerdesktoplinuxengine通常是 WSL2 后端没正常启动。试着重启 Docker Desktop或者 wsl --shutdown 后重开再不行就检查 Windows 功能里的虚拟机平台是否开启。5.2 镜像下载慢、超时怎么办镜像下载慢或超时先看有没有配置镜像加速源再试试 docker pull 一个超大镜像确认网络稳定性。如果是纯网络问题报错最常见是 TLS handshake timeout这时候靠重试解决不了本质问题必须换路。配置加速源的方法前面讲过不再重复。还有一个现象docker pull 显示一层下载完成下一层一直卡住不一定是最外层网络问题可能是镜像层太大比如 GitLab 社区版镜像有 1GB 以上。这种情况下不要反复 docker pull 重试可考虑用镜像仓库的 API 配合 skopeo 之类的工具直接把镜像同步到本地仓库再 docker pull 本地地址。如果公司内网有 Harbor、Nexus 之类的私有仓库建议从私有仓库拉取而不是直接连公网尤其是微服务数量多的时候能显著降低网络压力和拉取时间。5.3 容器网络不通从哪下手排查容器网络不通先判断是哪个方向不通。宿主机访问容器服务不通检查 -p 端口映射是否写反容器内服务是否监听 0.0.0.0 而不是 127.0.0.1。容器访问外网不通看宿主机网络再看容器 DNS容器内 cat /etc/resolv.conf 是否正常如果 DNS 失效可以在启动时加 --dns 8.8.8.8。容器 A 访问容器 B 不通确认它们是否在同一个自定义网络默认 bridge 网络下容器名 DNS 不保证可用只能用 IP使用 docker network connect 把容器加入同一个网络再互相访问。还有一个容易忽略的点防火墙。容器做了端口映射后宿主机防火墙如果拦截了对应端口外部还是访问不了docker 本身不会自动改宿主机防火墙规则。排查顺序建议先 docker inspect 看网络和端口绑定再 docker exec 进容器确认服务进程最后看宿主机防火墙和端口状态。5.4 容器一启动就退出Exited容器启动后立刻退出这个问题新手每天都会遇到。原因通常就几类前台进程结束、启动命令错误、缺少环境变量或文件、内存不足。排查第一步永远是 docker logs 容器名日志会告诉你有没有 command not found、error loading config、password environment variable not set 之类。如果没有日志用 docker run -it --rm 镜像名 bash 进入容器手动执行启动命令看到报错就明白。再有一种常见情况镜像的默认 CMD 是长期运行的进程但你自己在 docker run 后面追加了命令把默认 CMD 覆盖了比如 docker run -it ubuntu sleep 5050 秒后容器退出这其实是正常行为。还有像 MySQL 这类初始化时要消耗资源如果 -m 内存限制设太小会直接被 Cgroups 杀掉容器状态显示 OOMKilleddocker inspect 里能看到。遇到这种问题先把内存限制调大确认业务没问题后再逐步收紧资源。5.5 数据丢了怎么办卷备份与恢复数据丢失最典型的原因是删容器时把数据卷也一起删了。如果用的是命名卷docker volume ls 能看到卷还在容器重新挂载同一卷就能找回数据。如果不想下次冒险可以定期用容器做备份。备份 MySQL 数据卷的命令docker run --rm \ -v mysql8_data:/volume \ -v $(pwd):/backup \ alpine tar czf /backup/mysql8_data.tar.gz -C /volume .恢复时先把现有容器停掉再运行docker run --rm \ -v mysql8_data:/volume \ -v $(pwd):/backup \ alpine tar xzf /backup/mysql8_data.tar.gz -C /volume注意这里 /volume 是容器内临时目录前面挂载的命名卷的内容备份和恢复都是针对它。用 tar 是因为它能保留权限和目录结构。如果数据库在运行状态下备份必须先把容器停掉或使用 mysqldump 这类逻辑备份直接用 tar 拷数据文件大概率会备份出一份不一致的数据这个坑我踩过不止一次。5.6 镜像体积太大怎么瘦身镜像体积直接影响部署速度和磁盘占用尤其是微服务场景。最实用的三个手段选体积小的基础镜像、用多阶段构建、写 .dockerignore。基础镜像从 ubuntu 换到 alpine经常能把体积砍掉一半以上因为 alpine 本身只有几 MB。多阶段构建的意思是Dockerfile 里先用一个包含工具链的镜像完成编译再把编译产物复制到干净的运行镜像里这样最终镜像只有运行环境和二进制。比如 Java 应用先在 maven:3.9-eclipse-temurin-17 里构建最后 COPY --frombuild /app/target/app.jar 到 temurin:17-jre-alpine 里。检查镜像每一层大小可以用 docker history 或 docker system df -v看到异常大的层再针对性优化。另外 .dockerignore 千万别写漏node_modules、target、.git 这些进不了构建上下文也会缓存成层浪费空间还拉低构建速度。瘦身不是越瘦越好还要考虑镜像构建时间和运行稳定性alpine 虽然小但有些二进制依赖 glibc 的特性运行时反而要额外处理这个要按场景取舍。6. 从入门到进阶还需要了解什么6.1 Docker Desktop 与 WSL2 背后的机制Windows 和 macOS 上使用 Docker Desktop底层不是凭空跑的而是借助了一个轻量 Linux 虚拟机。Windows 下最常见的是 WSL2 后端Docker 引擎实际运行在一个 WSL2 发行版里终端里敲 docker 命令其实是跨越了 Windows 和 Linux 之间的边界。这带来几个实用性结论文件挂载性能比纯 Linux 差尤其是大量小文件读写场景开发时建议把项目放 WSL2 内不要放在 Windows 文件系统再挂进去Docker Desktop 的资源占用和 WSL2 的虚拟磁盘大小有关磁盘占用越来越大时可以用 wsl --shutdown 停止再 compact 虚拟磁盘。理解这一层很多“Docker Desktop 慢”“网络时好时坏”的问题就能定位到具体环节了。至于 Linux 服务器上没有这层虚拟机Docker 引擎直接跑在系统里性能最好。生产环境几乎都在 Linux 上跑 DockerWindows 服务器上跑 Windows 容器的场景相对小众。6.2 容器安全隔离边界与最小权限容器不是安全堡垒。它和宿主机共享内核隔离的是视图不是完整的隔离边界。如果容器以 root 用户运行一旦进程有内核漏洞攻击者可能直接控制宿主。所以生产环境要遵循最小权限原则镜像里创建非 root 用户Dockerfile 里写 USER app运行容器时去掉多余权限--cap-drop ALL --cap-add NET_BIND_SERVICE不要随便挂载宿主敏感目录不要用 --privileged。还有文件系统只读启动--read-only 之后再配合 tmpfs 挂载临时目录能防止容器内写入恶意文件。容器安全是一个独立的大话题但如果你是新手先记住一条别为了省事用特权模式跑业务容器。我见过不少团队为了方便所有容器都加 --privileged一旦某个容器被攻破整个宿主机等于裸奔。6.3 从单机 Docker 到集群编排单机 Docker 能满足开发和中小项目部署但当你有多台机器、几十个服务、需要自动伸缩时就会碰到编排问题容器挂在哪台机器、怎么重启、怎么滚动更新、怎么服务发现。这些是 Kubernetes 解决的领域。K8s 里的核心概念 Pod、Deployment、Service你可以粗略理解成容器和容器的组合、副本管理、访问入口。学习路径上Docker 基础是必须的因为镜像和容器是所有云端原生的地基但不建议死磕 Docker 的 swarm 模式现在绝大多数团队直接用 K8s。如果暂时没条件搭集群先用单机 Docker 和 Compose 把手里的项目管理好理解镜像、容器、数据卷、网络这些概念等需要横向扩展时再把这些知识平移到 K8s 上会发现迁移并没有想象中那么难。微服务部署、GitLab 这类重应用的容器化也都是从“先跑起来”开始的。最后再讲一个我自己的体会。学了这么多年容器技术回头看最关键的倒不是记住多少命令而是建立正确的思维模型镜像管交付容器管运行数据卷管持久网络管互联。只要这四个概念立得住Docker 世界里再复杂的操作拆到最后都是这几件事的排列组合。我也是从“docker run 到底在干嘛”一路踩坑踩过来的。现在遇到问题我很少去死搜命令而是先想这属于镜像问题、容器问题、网络问题还是数据卷问题。方向对了解决方案自然就出来了。希望这篇能把你的思维模型也立起来。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门