1. 先搞清楚 Docker 到底解决了什么问题
1.1 从“环境不一致”这个老大难说起
你肯定遇到过这种情况:代码在自己电脑上跑得好好的,一到同事的机器或者测试服务器上就报错。排查半天,发现是环境变量没配上、依赖库版本对不上,甚至操作系统本身的差异就把你干趴下了。这种“我这没问题啊”的经典甩锅现场,本质上是环境漂移——开发、测试、生产三套环境永远无法保持一致。
Docker 解决的就是这个问题。它把程序连同运行环境一起打包成一个个轻量级、可移植的单元,让应用在哪儿跑表现都一样。这种打包出来的东西就叫镜像,镜像跑起来就叫容器。你不用再手工去服务器上一行行装依赖、配环境了,直接把镜像扔上去启动就完事。
我当时第一次真正感受到 Docker 的价值,是在接手一个老项目的时候。那个项目的部署文档写了十几页,每一步都有各种隐坑。我用 Docker 把整套环境固化下来之后,部署时间从半天缩短到几分钟,而且还把人工操作出错的概率直接清零了。这就是容器化最朴素也最核心的价值。
1.2 镜像、容器、仓库:三个最重要的概念
这三个概念是 Docker 的基石,理解它们,后面的操作基本就是套公式了。
镜像(Image)相当于一个模板。它把程序代码、运行时、依赖库、配置文件全打包在一起,是一个只读的文件集合。你可以在一个镜像基础上叠加新的配置生成新镜像,这就叫分层结构。
容器(Container)是镜像运行起来后的实例。你可以同时从同一个镜像启动多个容器,它们之间互不干扰,每个容器有自己的文件系统、网络栈和进程空间。这跟面向对象里“类与实例”的关系非常像:镜像是类,容器是对象。
仓库(Registry)则是存放镜像的地方。公共的有 Docker Hub,企业内部通常搭私有的镜像仓库。你可以从仓库拉取别人做好的现成镜像,也可以把自己做的镜像推上去共享给团队。
这里有个很容易踩的坑:很多人以为容器是一个精简版的虚拟机,其实完全不是。容器并不是把整个操作系统都跑起来,它只是复用了宿主机的内核,通过命名空间和控制组来实现资源隔离与限制。所以容器启动快、占用小,单个镜像几百MB算是大的了,而虚拟机动辄几个GB起步。
1.3 Docker 和虚拟机到底差在哪
要说清楚容器的优势,绕不开和虚拟机的对比。虚拟机的思路是虚拟出一套硬件,然后在上面跑一个完整的操作系统,这个操作系统里再跑应用。中间隔着一层 Hypervisor,所以资源开销很大,启动通常要按分钟算。
容器的思路则是直接在宿主机内核之上跑进程,用机制隔离进程,让每个容器感觉自己是独立系统。没有硬件虚拟化那一层,也不用内置完整操作系统,只带应用真正需要的那部分运行库就够了。所以容器启动是毫秒到秒级,资源占用小得多,单台物理机能跑的容器数量远多于虚拟机数量。
做个直观对比:
| 对比项 | 容器 | 虚拟机 |
|---|---|---|
| 启动速度 | 秒级甚至毫秒级 | 分钟级 |
| 镜像大小 | 通常几百MB到几GB | 几GB到几十GB |
| 资源利用率 | 高,共享宿主机内核 | 低,每台都要完整系统 |
| 隔离级别 | 进程级隔离 | 硬件级虚拟化隔离 |
| 跨平台能力 | 依赖宿主机内核兼容 | 几乎任意平台 |
需要注意的是:容器和虚拟机并不是替代关系,而是互补关系。需要强隔离、跑不同内核的场景,虚拟机依然是首选;追求轻量、快速交付和弹性伸缩的场景,容器则更有优势。我见过不少团队一上来就把所有东西全容器化,结果迁到老旧的 Windows Server 上跑不起来,这就是没搞清楚两者边界闹的。
1.4 为什么 Docker 突然就火了
Docker 能在众多容器技术里脱颖而出,成为事实标准,关键不是因为它第一个提出容器概念,而是它把原本复杂到只有内核工程师才能玩的容器技术,做成了普通开发者也能轻松上手的工具。
它建立了一整套好用的标准:镜像格式、镜像构建语法、仓库协议。这些标准让工具链迅速丰富起来。配合上容器编排领域的 Kubernetes 异军突起,容器和微服务、DevOps、云原生这些概念牢牢绑定在一起。现在不管你用哪个云平台,几乎都默认支持容器部署,这套标准就是整个云原生体系的基石。
还有一个很重要的原因是社区生态。你想用的绝大多数开源软件——数据库、缓存、消息队列、编程语言运行时——官方都维护了现成的镜像,直接拉下来就能用。不用折腾安装包和依赖,这对开发效率的提升是很夸张的。
2. 环境搭建与基础配置
2.1 安装 Docker 并不难,但要注意平台的坑
Docker 的安装本身不算复杂,官方文档写得也比较清楚。但这几年安装方式迭代了好几次,很多网上看到的老教程已经不适用了。
在 Linux 上,主流发行版一般直接用包管理器安装就行,比如 Debian/Ubuntu 上用apt install docker.io,CentOS/RHEL 上用yum install docker-ce。这里有一个容易踩的坑:各个发行版打包的 Docker 版本可能比较旧,甚至会缺少一些关键组件。如果需要新特性,还是按官方推荐的方式添加软件源后再装,这样能确保拿到最新版和完整的配套工具。
Windows 和 macOS 上则必须安装 Docker Desktop。需要注意的是,Docker Desktop 在 Windows 上默认使用 WSL 2 后端,老旧版本的 Windows 系统可能不支持,需要提前确认系统版本和 WSL 2 是否启用。安装完之后如果提示要重启系统,不要嫌麻烦,该重启就重启。WSL 2 没启用好的话,Docker 会一直处于一种“起不来但又不报明白错误”的诡异状态,很折磨人。
Linux 上还有一个时常被忽略的点:普通用户操作 Docker 需要加 sudo,否则会报权限不足的错误。官方推荐把当前用户加入 docker 用户组来免掉这层麻烦,但这里存在一个安全隐患——docker 组内的用户等同于拥有宿主机 root 权限,因为在隔离环境里做任何事都只是操作被映射到宿主机的真实文件系统。所以在生产环境上要慎重做这一步,如果实在需要,记得衡量好风险。
2.2 配置镜像加速器,这一步真能救命
Docker 安装好之后,第一件事不是急着拉镜像,而是先把镜像源配置好。因为没有配置加速的话,从 Docker Hub 拉取镜像的速度在国外源不稳定的情况下,经常会出现超时或极慢的情况,尤其当你需要拉几十个 GB 的镜像或者在断断续续的网络环境下工作时,那种等待的感觉相当煎熬。
镜像加速的原理其实很简单。Docker 拉镜像走的是标准 HTTP 仓库协议,你把仓库地址指向一个离你更近的加速器节点,这个节点会缓存大量常用镜像,拉取速度自然就上去了。配置方法是在 Docker 守护进程的配置文件里加一行registry-mirrors字段,填上可用的镜像加速地址,然后重启 Docker 服务。
配置多个加速地址是更稳妥的做法,因为单个加速服务有时会抽风,多备一个就多一重保障。改完后可以用docker info查看配置是否生效。顺便提醒一句:加速地址属于公共服务,稳定性没法保证,重要项目还是尽量用公司的私有仓库,公共加速只适合个人开发和实验场景。
2.3 安装完最好做这几个验证
好多人装完之后迫不及待就拉镜像跑容器,结果出问题了才回头排查,往往绕了不少弯路。我建议装完先顺手做几个快速验证,心里有底再动手。
第一个是看版本信息:docker version。这条命令会输出客户端和服务端的版本号,如果服务端没启动,会直接报错,这时就能立刻定位到是服务没起来,而不是别的问题。
第二个是看系统信息:docker info。这里能看出容器运行时类型、存储驱动、镜像加速配置是否生效、内核是否支持关键特性等信息。很多环境问题都能在这里找到线索。
第三个是最经典的验证命令:docker run hello-world。如果这一步能顺利跑通,说明整个链路没问题。这个镜像本身特别小,里面的程序只会打印一段提示信息告诉你环境是正常的。
注意:验证完 hello-world 之后,记得顺手清理一下,
docker rm $(docker ps -aq)和docker rmi把测试用的容器和镜像删掉,养成好习惯,后面才不会被一堆无用的测试残留占用空间。
2.4 理解上下文和常用配置
有几个基础概念提前理清,操作起来会顺很多。一个是“守护进程”,它负责管理镜像、容器、网络等所有核心资源。你在命令行敲的大部分命令,本质都是向守护进程发请求。
另一个是“上下文”。构建镜像时,Docker 会把指定目录的所有内容作为构建上下文传给守护进程,所以不要随便把构建目录指向根目录,否则所有文件都被打包发送,构建会慢到怀疑人生。合理的做法是每个项目放一个独立的构建目录,里面只放构建需要的东西,不相关的大文件该忽略就忽略。
还有几个常用配置值得一开始就设好:容器日志的大小、数量上限;默认的磁盘占用上限。这些配置虽然不起眼,但在长时间运行之后,都可能会导致磁盘被慢慢塞满,到时候再排查就麻烦了。趁早配置好,后面省一大块心。
3. 镜像与容器的日常操作
3.1 镜像的拉取、查看、删除
镜像操作是 Docker 使用频率最高的部分。拉取镜像用docker pull加仓库地址和标签,如果不指定标签,默认拉取 latest 标签。这里有一个容易踩的坑:latest 并不代表“最新版”,它只是一个特殊的标签,很多项目的 latest 可能是很久之前打的,代码仓库的更新并不会自动同步到镜像标签上。所以明确指定版本号是一个好习惯,方便追溯和回滚。
查看本地已经拉取下来的镜像用docker images,这条命令会列出所有镜像的仓库名、标签、镜像 ID、创建时间和大小。镜像很多的时候,可以用grep过滤,比如docker images | grep mysql只看 MySQL 相关的镜像。
删除镜像用docker rmi。有几个细节值得注意:如果镜像已被某个容器占用,删除会报错,需要先把用到它的容器删掉才能删镜像。另外,批量删除不用的镜像,可以配合过滤条件,比如docker image prune可以清理所有悬空镜像——那些没有标签、不再被任何容器引用的镜像。我见过很多人的磁盘被这玩意儿塞满的,定期清理是必要的。
3.2 容器生命周期:创建、启动、停止、删除
容器运行的几个核心命令必须记熟。创建并启动一个容器最常用的是docker run,它会先检查本地有没有镜像,没有的话自动去仓库拉取,然后创建容器并启动。停止容器用docker stop,它会发信号给容器内的主进程,给它一个优雅退出的机会;如果超时仍未退出,再强制终止。删除容器用docker rm,容器停止后并不会自动消失,需要手动删。
查看容器状态用docker ps,默认只显示运行中的容器,加-a参数可以连同已经退出的容器一起列出来。这个命令能看到容器的 ID、镜像、创建时间、状态、端口映射和名字,排查问题全靠它。
容器命名是很多新手容易忽略的地方。启动容器时不指定--name,Docker 会随机起一个名字,虽然不碍事,但操作起来很不方便。建议每次启动都用--name指定一个有业务含义的名字,比如docker run --name mysql-dev ...,后续进入容器、看日志、启停,用名字操作就好,不需要记那一长串乱码一样的容器 ID。
3.3 端口映射与数据卷:日常用最多的两个参数
如果容器里的服务要被外部访问,需要做端口映射。容器内部有自己的网络空间,宿主机的端口不会自动映射到容器里,必须通过-p参数把宿主机的某个端口绑定到容器内的端口。比如-p 3306:3306,意思是访问宿主机的 3306 端口就等于访问容器的 3306 端口。写反了或者漏写,就会出现“容器明明在运行,但外部连不上”这种问题。
端口映射有几种写法需要掌握:-p 8080:80是宿主机 8080 映射容器 80;-p 80是随机分配宿主机端口映射到容器 80;-p 127.0.0.1:8080:80只允许宿主机本机访问,外部无法访问。第三种子写可用于数据库这种敏感服务,防止端口直接被暴露到外网。这是个值得养成习惯的做法。
数据卷用于解决容器数据持久化问题。容器是临时性的,删除之后容器内部产生的所有数据都跟着没了。你在容器里跑了 MySQL,存了一堆业务数据,容器一删,全部丢光,这种事故我见过不止一次。解决方式是把宿主机的目录挂载到容器内目录,比如-v /my/own/datadir:/var/lib/mysql,容器写数据直接写到宿主机对应目录,容器删了数据还在。
数据卷还有另一种用法:具名卷。-v mysql-data:/var/lib/mysql,不用关心具体存在宿主机哪个目录,由 Docker 统一管理。具名卷的好处是可移植性好,通过docker volume命令就能管理,适合跨环境迁移,缺点是容器的宿主机目录由 Docker 托管,用户直接找文件不太方便。
关于已经创建好的容器能否动态加端口映射和数据卷,答案是:不能。必须在创建时指定。所以启动容器之前想清楚哪些数据需要持久化、哪些端口要暴露,一锤定音。如果中途要改,只能重新创建容器。
3.4 进入容器与查看日志
调试容器时,最常用的两个操作是进入容器内部和查看日志。进入容器用docker exec -it <容器名> /bin/bash。这里要注意,有些精简镜像里并没有 bash,只有 sh,因此在报错找不到 bash 的时候,改用/bin/sh就能进去了。
进入容器后可以查看文件、执行命令、排查问题。注意在容器内部做的修改,容器一删除就全部丢失。所以在容器里调试出来的临时修复,最后要落实到镜像构建里,才算真正的修复。
日志是排查问题的主要手段。docker logs <容器名>可以查看容器的标准输出和标准错误。加-f参数可以实时跟踪日志,类似 tail -f,服务出问题的时候挂上这个命令看动态输出非常直观。加--tail 100可以只看最近 100 行日志,避免一次刷出一大堆旧日志干扰判断。
3.5 用 Dockerfile 构建属于你自己的镜像
拉起现成镜像只是 Docker 的第一步,真正好用之处在于构建自定义镜像。构建镜像的入口是一个 Dockerfile 文件,里面写了构建的每一步操作指令。
一个最基础的 Dockerfile 大概长这样:
FROM python:3.11-slim WORKDIR /app COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt COPY . . EXPOSE 8000 CMD ["python", "app.py"]每个指令理解起来都不难。FROM指定基础镜像,后面所有操作都在这个基础上叠加;WORKDIR设置工作目录;COPY把文件复制进镜像;RUN在构建阶段执行命令,通常用来装依赖;CMD指定容器启动时执行的默认命令。
构建命令是docker build -t myapp:1.0 .,最后这个点代表构建上下文目录。构建好后可以用docker images看到这个新镜像,直接用docker run myapp:1.0启动。
这里有几个实战经验值得说。第一,基础镜像尽量选官方镜像,因为官方镜像经过了更充分的测试,安全性和兼容性更有保障。第二,apt-get或pip install之后要清理缓存文件,尽量让每一层都保持精简,不要让镜像体积膨胀得厉害。第三,.dockerignore文件一定要写,类似于 .gitignore,排除掉不需要打进镜像的文件,既能减小镜像体积,也能避免一些敏感文件被带进镜像。
有个看起来比较绕但很关键的机制是:Dockerfile 的每一行指令都会生成一个中间镜像层,构建如果有缓存,后续重建时没有变动的步骤会自动复用缓存,速度非常快。所以把不太变动的指令(比如安装依赖)放在前面,把经常变动的指令(比如拷贝业务代码)放在后面,能最大化利用缓存,加速构建流程。
4. Docker Compose:多容器编排的入门神器
4.1 一个命令拉起整套环境
当你只需要跑一两个容器的时候,直接敲docker run还能应付。但只要应用稍微复杂一点,要同时跑数据库、后端服务、消息队列,还要管理它们之间的网络关系和启动顺序,一个个敲命令就非常痛苦了。
Docker Compose 就是干这个的。它是 Docker 官方提供的容器编排工具,用一个 YAML 文件描述整套服务的配置,然后一行命令全部启动。它不用学任何额外概念,就是把docker run的命令参数翻译成 YAML 格式,声明式地写好,后面改起来也很方便。
我之前在某公司做模拟项目时,一个功能要同时依赖 MySQL、Redis 和另一个内部服务,本地环境每次都要手动启动三个终端,启动顺序不对还会报各种连接错误。改用 Compose 之后,一个docker compose up全部搞定,不再有配置混乱的烦恼。这东西一旦习惯,就再也回不去了。
4.2 核心配置:services、image、ports、volumes
Compose 文件的核心是 services 字段,下面声明每一个服务的配置。举一个最常见的例子,一个 Web 应用配一个 MySQL:
version: "3" services: web: build: . ports: - "8080:80" volumes: - ./src:/app environment: - DB_HOST=db - DB_PORT=3306 db: image: mysql:8.0 environment: - MYSQL_ROOT_PASSWORD=root123 - MYSQL_DATABASE=myapp volumes: - db_data:/var/lib/mysql restart: always volumes: db_data:这里面有几个很关键的配置。build表示这个服务需要本地构建镜像,构建目录是当前目录;image表示直接拉取现成镜像;ports就是端口映射,"8080:80"跟-p 8080:80一个意思;volumes是数据卷或目录挂载,下面的 web 服务把宿主机当前目录挂到容器 /app,这样代码改了容器内立刻生效,开发时很实用;environment是环境变量。
depends_on这个配置值得单独说明一下。它确实能控制服务启动顺序,让 web 服务在 db 启动之后再启动,但需要注意,它只保证 db 容器“已启动”,并不保证数据库服务“已就绪”。也就是说,db 容器还在初始化数据库的时候,web 可能就已经连接上来了,然后照样报连接失败。针对这种情况,通常的做法是在应用启动脚本里加等待重试逻辑,而不是单纯依赖depends_on。很多新手在这里栽过跟头,值得多提一句。
启动命令是docker compose up -d,-d表示后台运行。停止并用docker compose down,如果要顺便把数据卷也清掉,加-v参数。不过down -v是很危险的操作,数据卷里的数据会全部抹掉,做这个操作之前务必确认备份到位。
4.3 环境变量与配置管理的最佳实践
Compose 文件里直接写死密码这种方式,在自己本机开发还凑合,一旦要共享给团队或者部署到生产环境,就是个明显的坑。更合理的做法是用环境变量替换。
Compose 原生支持${VAR_NAME}这样的语法,变量值从.env文件中读取,或者从宿主机环境里获取。比如:
environment: - MYSQL_ROOT_PASSWORD=${DB_PASSWORD}然后在同目录下放一个.env文件:
DB_PASSWORD=my-secret-password建议把.env文件加进.gitignore,避免把敏感信息提交到代码仓库,在编写.env.example模板文件时,写入项目所需的所有变量模板,让团队成员自己复制修改。这个习惯能避免很多因配置引发的事故和争吵,是我用得很顺手的实践方式。
4.4 网络与依赖管理进阶
Compose 默认会为你的项目创建独立网络,所有服务都在这张网络里,相互之间可以直接用服务名互相访问。比如 web 服务里要连数据库,直接连接host=db而不是localhost或具体 IP。这个“服务名即主机名”的机制很省心,容器重启后 IP 变了也不影响。
配置跨多个 Compose 项目共享网络时,需要用external: true引用已存在的网络。另一个很常见的需求是给容器设置资源限制,防止某个容器把宿主机资源吃满,配置方式如下:
services: web: deploy: resources: limits: cpus: "0.5" memory: 512Mcpus表示最多使用 0.5 个 CPU 核心,memory表示最大使用 512MB 内存。在本地开发环境用这个参数,能有效防止某个内存泄漏的服务把整个电脑拖垮,我很推荐平时就跑起来。
5. 高频问题的排查技巧
5.1 镜像拉取失败或超时
docker pull经常遇到超时的情况,尤其是在某些网络环境比较不稳定的时期。排查思路按顺序来:先确认网络是否正常,比如直接 ping 一下外网地址;再确认镜像加速器是否配置正确,用docker info查看当前配置;最后确认镜像仓库地址是否写错,很多私有仓库的地址区分大小写和端口号,细节出错很隐蔽。
如果加速器本身也拉不动,还有一个临时办法:换一个镜像仓库地址,或者使用代理方式(注意这里说的代理是正经的 HTTP 代理,配置在 Docker 守护进程层面)。不过换源意味着要依赖三方服务,对企业项目来说,还是自建私有仓库更可靠。
5.2 端口冲突导致容器起不来
最常见的中断场景之一,是宿主机端口已被占用,容器启动直接报“port is already allocated”。代码排错思路有两种:第一,用docker ps看看是不是自己之前启动了同名容器没删,端口还没释放;第二,用netstat -tlnp | grep 端口号或lsof -i:端口号查宿主机的占用情况。
解决方式:要么换一个宿主机的映射端口,要么把占用的进程停掉,要么把之前残留的容器删掉。这里值得养成的好习惯是启动容器前先用docker ps -a看看有没有同名的旧容器,直接把它删了再启动,就不会被莫名其妙的各种冲突折腾。
5.3 容器里的数据在同一台机器上反复丢
数据丢失是生产环境最恶劣的事故之一。排查时先确认启动命令是否挂载了数据卷,很多人在启动时忘了-v参数,默认容器写入的是临时层,容器删除后数据全无。另一个重大隐患是容器被down -v清理过,数据卷被一并抹掉,如果数据卷上没有备份机制,这往往意味着数据直接无法找回。
解决办法是:启动数据库类容器时务必挂载具名卷或宿主机目录;备份机制要独立于 Docker 之外,定期导出备份文件到独立的存储位置。最重要的是,做任何数据删除操作之前,先确认数据是否已备份——这不只是 Docker 的经验,是所有运维工作的通用原则。
5.4 容器内网络连接异常,主机名不通
多个容器之间要互通时,报莫名的连接失败,首先检查它们是否在同一个 Docker 网络里。用docker inspect看容器网络配置,或直接进容器里执行ping 对方服务名测试连通性。如果不在同一个自定义网络中,需要创建网络并把容器加入。
另一个很常见的坑是容器内部访问宿主机服务。容器里的localhost是容器自身,不是宿主机。如果你在容器里要连宿主机上跑的另一个服务,在 Linux 上可以用宿主机 IP,在 macOS 和 Windows 的 Docker Desktop 上则用host.docker.internal这个特殊域名。这个细节我在早期也吃过亏,折腾了好久才发现是访问地址的问题。
5.5 磁盘空间被镜像撑爆
Docker运行时间一长,磁盘占用会越来越夸张。主要占用来源是:无用的镜像层、悬空镜像、日志文件、容器临时层、数据卷。清理思路分几种:悬空镜像用docker image prune清理;停止的容器用docker container prune;日志文件如果已经玩命暴涨,检查 Docker 的日志驱动配置,给它设置一个最大上限,防止某个服务狂打日志把磁盘打满。
用一条命令来查看 Docker 整体的磁盘占用情况:docker system df。它会分门别类列出镜像、容器、数据卷、构建缓存各自的占用情况,比你自己瞎猜强得多。必要时配合docker system df -v查看更详细的块级明细。整个 Docker 占用状况都能可视化地呈现在眼前,排查方向一下就清晰起来了。
6. 写在最后的一些实操体会
6.1 从自己动手开始,别只停留在“看会了”的阶段
Docker 门槛并不高,但前提是你真的动手操作过。我见过很多人看了一堆教程,命令背得滚瓜烂熟,一遇到实际问题就慌了。原因很简单,教程里的环境是别人准备好的,而真实环境永远充满了各种意想不到的坑:网络不通、权限缺失、依赖冲突、系统版本差异。这些东西只有反复实操才能形成直觉,知道了“大概原理”和“能排掉实际故障”之间隔着一整条经验之墙,这堵墙只能靠实操去填。
6.2 我踩过的几个印象深刻的大坑
第一次用数据卷时,我挂载了宿主机目录到容器里,结果容器内文件的权限全变成了 root,导致应用无法正常运行,查了半天才知道是宿主机目录的所有者权限跟容器内用户不匹配。处理方式是在容器启动时用--user指定用户 ID,或者调整宿主机目录的属主。
还有一次在生产环境遇到容器时区是 UTC,日志时间和业务时间对不上,排查问题时非常难受。解决方案是在启动容器时挂载宿主机/etc/localtime文件或者通过环境变量设置TZ让时区变成东八区。这种小细节书上不会写,只有在实际项目里被折磨过才会有印象。
另外,排查问题时养成用docker inspect和docker exec的习惯会省非常多的时间。前者能看到容器的完整配置,包括环境变量、挂载、网络等;后者能进容器直接看运行现场。比起盲猜,这两条命令真的让人安心很多。
6.3 现在可以推荐的学习路线了
进入实操阶段后,找一个网上现成的项目练手是很事半功倍的路径。找一个小型 Web 项目,部署到本地 Docker 环境里,然后逐步给它加上数据库、缓存,用 Compose 编排起来,之后尝试用 Dockerfile 把构建流程自动化。这套路线走完,日常开发中百分之八九十的场景都足够应付了。
再往后可以接触 Kubernetes 等容器编排平台,学习如何在多节点环境下做服务调度、弹性伸缩和滚动更新。那条路的复杂度会上一个大台阶,但只要 Docker 本身的基础打得够扎实,学起来就不会太吃力。容器编排平台的很多概念都是 Docker 概念的延伸,底层功底越扎,上层越不吃力。