
先交代一件事我对 Docker 的第一印象其实很差。这东西安装包大、概念又多第一次在 Windows 上装完 Desktop 还报了个虚拟化没开当时差点就放弃了。真正让我改观的是一次帮同事跑一个老项目——Java 版本不对、Redis 没装、MySQL 密码死活对不上折腾了快两个小时最后发现是配置文件里的时区问题。那一刻我才意识到环境配置要耗费的精力和代码本身根本不成比例。后来我把所有项目都塞进 Docker 容器换电脑、换系统、给同事复现问题全部变成一条命令的事。这篇文章就把我这几年的实操经验梳理成一套可以直接照抄的方案从“为什么 Docker 能解决环境问题”到“如何用一个容器把项目带走”尽量说清楚每一层原理和每一步操作背后的逻辑希望能帮你彻底告别环境地狱。1. 环境配置的痛点为什么换个电脑项目就跑不起来1.1 一个典型故障JDK 版本、系统位数和编码问题叠加先还原一个我已经遇到不止五次的场景。同事发来一个 Spring Boot 项目说“本地跑得好好的”。你拉下来代码执行mvn spring-boot:run结果控制台先提示UnsupportedClassVersionError再提示Failed to configure a DataSource。查了一圈发现对方用的是 JDK 17你本地装的是 JDK 8他配置的 MySQL 密码是root/123456你本地 MySQL 密码是root/root。这些全都属于“环境差异”而不是代码 Bug。更恶心的是隐形差异。我曾经在一台 Windows 机器上开发一个生成 ZIP 文件的功能代码里硬编码了/data/temp路径Windows 上直接报No such file or directory。还有人遇到过文件编码问题在 Linux 上默认 UTF-8在 Windows 中文版上默认 GBK同一个文件读出来的字符串直接乱码。这种问题一旦出现排查链路极长而且你很难短时间判断是代码问题还是环境问题。业内普遍认可的解决方案是“环境标准化”用配置文件锁死 JDK 版本、数据库版本、中间件版本、系统依赖。但问题是你连操作系统都不一样怎么锁Docker 的思路就是把操作系统底层的差异也一并吸收了——容器内运行的是一个完整的、精简的 Linux 用户空间应用跑在里面根本感知不到宿主机是 Windows 还是 macOS。1.2 为什么传统配置环境的方式注定低效传统方式下每台电脑配置环境的路径完全不同Windows 用户下载 .exe 安装包点下一步改环境变量macOS 用户用 Homebrew 安装还要处理zsh或bash的 PATH 冲突Linux 用户用 apt 或 yum但不同发行版的包名、版本库、编译方式又不一样。就算是一样的操作系统不同开发者之间的本地环境也可能有差异有的用nvm管理 Node 版本有的用sdkman管理 Java 版本有的干脆直接改/usr/local下的目录。这些管理工具本身又会互相干扰。Docker 的做法简单粗暴把所有运行时、依赖、配置、启动命令全部写进一个文本文件Dockerfile构建成镜像之后无论在哪个机器上运行容器内看到的环境永远是一样的。1.3 沙盒的核心价值隔离的职责边界“沙盒”这个概念最早来自安全领域用于隔离不可信程序的执行环境。Docker 把这种思路用在了开发环境上每个容器就是一个独立的文件系统、网络栈、进程空间。你在容器里装 A 项目需要的旧版 OpenSSL完全不影响宿主机上的新版 OpenSSL你在容器里跑一个占用 8080 端口的服务宿主机上其他服务除非显式映射端口否则不会冲突。隔离的副作用是“干干净净”项目不跑了把容器删掉宿主机上不会残留任何配置。这对我这种不喜欢把系统搞得乱七八糟的人来说简直救赎。2. 容器与镜像的分工先理清这两个概念再动手2.1 镜像打包好的只读模板镜像本质上是一个分层存储的只读文件集合。每一层对应 Dockerfile 里的一条指令。比如FROM node:20-alpine WORKDIR /app COPY package*.json ./ RUN npm install COPY . . CMD [node, server.js]每一行都会产生一个新层。FROM是基础镜像层COPY package*.json又盖一层RUN npm install又盖一层。这样的好处是当你修改了业务代码重新构建时只要COPY . .之前的层没有变化Docker 会直接复用缓存层构建速度极快。这种分层机制还和你拉取镜像的体验挂钩。大多数人应该都遇到过docker pull卡住的情况镜像下载是逐层进行的每层都有独立的 SHA256 摘要本地已有的层会被跳过只下载缺失的层。这就是为什么你更新一个基于同一个基础镜像的新镜像时速度往往比第一次拉取快很多。2.2 容器镜像的运行实例容器是镜像在运行时的一个可写层。你可以在这里创建文件、安装临时软件、启动进程。一旦容器删除可写层也就没了——这点务必记牢不然数据丢了会以为遇上了什么玄学 Bug。容器启动时有两个关键开关交互模式和端口映射。docker run -it --rm -p 3000:3000 my-app-it的意思是打开一个交互式终端--rm表示容器退出后自动删除可写层-p 3000:3000表示把宿主机的 3000 端口映射到容器内的 3000 端口。如果你不加-p容器内的服务虽然跑着但宿主机访问不到。2.3 镜像和容器的关系类比“程序安装包”和“正在运行的程序”镜像就是安装包容器就是安装包运行后的进程。安装包不会因为运行次数的改变而变化但运行中的程序会产生临时数据。所以容器可以被反复创建和删除而镜像一旦构建好它就是那份“不会跑偏的配置”。提示容器适合跑无状态应用。凡是有状态的数据数据库文件、上传文件、日志都要通过数据卷volume或绑定挂载bind mount持久化到宿主机。3. Docker 与虚拟机的本质差异别再拿它当轻量级虚拟机3.1 共享内核还是自带内核这是 Docker 和虚拟机最核心的区别。虚拟机本质上是一个完整操作系统ESXi 或 VirtualBox 这类软件用 Hypervisor 在硬件层做虚拟化每个虚拟机会自带一个完整的 Guest OS 内核。Docker 则完全跳过硬件虚拟化所有容器直接共享宿主机的 Linux 内核只是在用户空间做了隔离。这意味着什么如果你的宿主机是 WindowsDocker Desktop 会先在 Hyper-V 或 WSL2 里创建一个最小的 Linux 虚拟机然后在这个虚拟机的内核上跑容器。所以 Windows 上的 Docker 其实还是打了“虚拟机”的底子但只是因为 Windows 内核无法直接运行 Linux 容器不代表容器本身付出了完整的虚拟化代价。3.2 启动时间和资源占用虚拟机启用一个操作系统从 BIOS 到内核初始化再到 systemd 拉起少说几十秒。容器启动基本是秒级我在实测中执行docker run hello-world从命令行按下回车到输出提示通常不超过三秒。资源占用上一台 4GB 内存的 MacBook 同时跑五六个容器没什么压力而同一台机器开三台虚拟机基本就卡死了。3.3 为什么“容器逃逸”不用过度恐慌我经常被问到“容器里跑的东西会不会影响宿主机”。“容器逃逸”在安全圈确实是个被反复研究的攻击面但普通开发者开发场景里只要不从 Docker Hub 随便拉来源不明的镜像、不以 root 用户运行容器、不向容器挂载/根目录风险远低于随便下载一个来路不明的可执行文件安装到本机。这一点想明白就不会因噎废食地把 Docker 雪藏了。4. 从零开始写一个可移植的后端项目Dockerfile 实测4.1 项目准备一个极端依赖本地环境的 Spring Boot 应用为了演示“换个电脑跑不起来”的场景我建了一个最简单的 Spring Boot 项目它依赖JDK 17本机只有 JDK 8 的电脑就会直接失败Maven 3.8本机没装 Maven 就完全无法构建MySQL 8.0本机 MySQL 版本是 5.7 时部分语法和行为会有差异一个application.yml里面写着数据库连接串jdbc:mysql://mysql:3306/demo后面会解释为什么用mysql而不是localhost然后在这台电脑上执行项目启动一切正常。现在把这个项目复制到另一台只有 Python 的电脑上想跑起来第一步 Maven 就过不去。4.2 多阶段构建不浪费时间在镜像里留编译工具这里要强调“多阶段构建”的思路。很多人写 Dockerfile 就是一个FROM maven:3.8-jdk-17然后RUN mvn package最后跑java -jar。这会让构建镜像里残留 Maven、依赖缓存、编译产物等一堆用不到的东西镜像体积可能超过 500MB。多阶段构建分两步# 第一阶段构建 FROM maven:3.8-openjdk-17 AS builder WORKDIR /build COPY pom.xml . RUN mvn dependency:go-offline COPY src ./src RUN mvn clean package -DskipTests # 第二阶段运行 FROM openjdk:17-jre-slim WORKDIR /app COPY --frombuilder /build/target/demo-0.0.1-SNAPSHOT.jar app.jar EXPOSE 8080 ENTRYPOINT [java, -jar, app.jar]这样最终镜像只包含 JRE 和最终 jar 包体积能压缩到 200MB 左右。第一阶段那种几GB的 Maven 镜像不会出现在你的交付产物里。4.3 编排 MySQL 和应用的依赖关系docker-compose 的设置应用依赖 MySQL所以你需要手动先启动 MySQL 容器再启动应用容器顺序错了应用就连不上数据库。为了彻底解决这个问题引入docker-compose.yml编排version: 3.8 services: mysql: image: mysql:8.0 container_name: demo-mysql environment: MYSQL_ROOT_PASSWORD: root MYSQL_DATABASE: demo ports: - 3306:3306 volumes: - mysql-data:/var/lib/mysql healthcheck: test: [CMD, mysqladmin, ping, -h, localhost, -proot] interval: 5s timeout: 3s retries: 10 app: build: . container_name: demo-app depends_on: mysql: condition: service_healthy ports: - 8080:8080 volumes: mysql-data:这里的关键点depends_on配合condition: service_healthy确保 MySQL 就绪后才启动应用healthcheck用mysqladmin ping判断 MySQL 是否真的可以接受连接volumes把 MySQL 数据持久化到命名卷mysql-data防止容器删除后数据库丢失。4.4 实测“一条命令启动全部”在项目根目录执行docker compose up -dDocker 会自动完成镜像构建、网络创建、依赖启动、应用启动。等十几秒后访问http://localhost:8080接口正常返回。再换一台全新的电脑只需要安装 Docker Desktop然后在这个目录下执行同一行命令。两个台电脑的构建产物、环境、依赖完全一致。5. 镜像构建过程中的常见坑与实测解法5.1 Dockerfile 指令顺序缓存策略直接影响构建速度同样一个项目Dockerfile 里的COPY package*.json和COPY . .顺序写反可能导致每次改代码都重新执行npm install白等几分钟。原则很简单变化频率低的指令放前面变化频率高的放后面。比如pom.xml/package.json比源码文件稳定得多所以先复制依赖描述文件再执行依赖安装最后才复制源码。这一步是优化构建速度最有效的手段没有之一。5.2 Windows 上换行符导致的 “exec user process caused exec format error”这个坑我已经看到无数人在社区里问过了。在 Windows 上写 Dockerfile 或 shell 脚本默认换行符是 CRLF回车换行而 Linux 容器内期望的是 LF换行。当你在ENTRYPOINT或CMD中直接执行一个.sh脚本且该脚本是 CRLF 换行容器启动时会报类似standard_init_linux.go:228: exec user process caused: exec format error解决办法是在项目里加一个.gitattributes文件强制指定 shell 脚本使用 LF*.sh text eollf *.dockerfile text eollf或者在 Dockerfile 里用RUN sed -i s/\r$// /app/entrypoint.sh清理。实测下来配置.gitattributes是从源头根治的方案。5.3 时区问题容器默认 UTC数据库时间差 8 小时这是相当坑爹的一个问题——本地测试时时间正常容器一部署就少了 8 小时。原因很简单openjdk基础镜像默认时区是 UTC而你的业务代码在new Date()时用的是系统时区。解决办法是在 Dockerfile 中显式设置时区ENV TZAsia/Shanghai RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime echo $TZ /etc/timezonedebian系镜像需要先安装tzdataRUN apt-get update apt-get install -y tzdata一次踩坑终身受用。以后再也不用在业务代码里写“8”这种硬编码。5.4 镜像体积优化基础镜像选型直接决定交付成本我实测过同一个 Spring Boot 应用openjdk:17-jdk镜像构建后约 400MBopenjdk:17-jre-slim约 200MBeclipse-temurin:17-jre-alpine约 150MB基于bellsoft/liberica-runtime-container:jre-17-slim约 180MB。选型时要注意alpine虽然体积小但底层是 musl libc个别依赖原生编译库的项目会报“No such file or directory”。遇到这种情况回到debian系镜像通常能解决。如果追求极致还可以用jlink裁剪 JREjlink --module-path $JAVA_HOME/jmods --add-modules java.base,java.sql,java.naming,java.management --output /opt/jre-mini这个在大型微服务场景下收益明显单个镜像可以压到 100MB 以内。但如果你刚开始接触 Docker不建议一上来就折腾 jlink先把标准镜像用熟练再说。6. 数据卷与网络容器删了数据不能丢服务之间不能靠 IP6.1 为什么数据卷是必须学会的东西你第一次用 Docker 跑 MySQL大概率是这个流程启动容器、建表、灌数据、一切正常。然后你执行了docker rm -f mysql再次启动一个一样的容器发现所有数据都没了。原因我刚才提过容器删除时容器内的可写层一并删除。数据库文件写在了可写层自然随容器一起消失。正确做法是使用数据卷docker run -d \ --name mysql \ -e MYSQL_ROOT_PASSWORDroot \ -p 3306:3306 \ -v mysql-data:/var/lib/mysql \ mysql:8.0-v mysql-data:/var/lib/mysql中mysql-data是命名卷/var/lib/mysql是容器内 MySQL 的数据目录。之后即使删掉容器重新创建一个新容器并挂载同一个卷数据依然完好。6.2 绑定挂载开发环境下热更新的关键命名卷之外还有一种挂载方式叫绑定挂载路径直接指向宿主机目录docker run -d \ -v /home/user/project:/app \ my-app开发场景中这种模式非常有用。比如前端项目你改了源码宿主机目录里的文件会实时同步到容器内配合热更新插件体验就像本地运行一样。生产环境不建议这样做因为宿主机文件的权限和隔离性都不如命名卷。6.3 容器间通信为什么数据库地址写mysql而不是localhost在 docker-compose 编排的同一个网络里服务名就是主机名。应用容器里连接数据库时jdbc:mysql://mysql:3306/demo中的mysql会被解析到 MySQL 容器的 IP。这个 IP 可能会变化但你不需要关心因为 compose 内部的 DNS 会自动更新。这一点绝对不要在容器内用localhost连接数据库因为localhost指向的是应用容器自身里面根本没有 MySQL。这也是不少新手在从本地环境迁到 Docker 环境时最容易犯的错误。6.4 端口映射策略生产环境别把数据库暴露到宿主机端口映射只是给宿主机访问容器开一个口子。开发时把 MySQL 的 3306 映射出来方便本地用 Navicat 连接这是合理的。但生产环境下数据库容器完全不需要映射端口只需要让它在 compose 内部网络里被应用容器访问即可。services: mysql: image: mysql:8.0 expose: - 3306 # 没有 ports 段这样 MySQL 只有容器网络内的服务能访问宿主机和公网一律访问不到。这是纵深防御思想里很基础但很多人忽略的一环。7. compose 与多容器项目别再一个个docker run7.1 docker-compose 是什么一个容器编排的“乐高说明书”当你需要同时启动 MySQL、Redis、Nginx、应用容器一个个docker run不仅麻烦参数还容易写错。docker-compose 实际上是 Docker 官方提供的容器编排工具用来定义和运行多容器应用。它用一个 YAML 文件描述所有服务的镜像、端口、卷、依赖关系然后docker compose up一建启动。7.2 完整示例前后端分离项目的一键启动以我手上的一个 Vue Spring Boot MySQL 项目为例version: 3.8 services: frontend: build: ./frontend ports: - 80:80 depends_on: - backend backend: build: ./backend ports: - 8080:8080 environment: SPRING_DATASOURCE_URL: jdbc:mysql://mysql:3306/demo depends_on: mysql: condition: service_healthy mysql: image: mysql:8.0 environment: MYSQL_ROOT_PASSWORD: root MYSQL_DATABASE: demo volumes: - mysql-data:/var/lib/mysql volumes: mysql-data:执行docker compose up -d --buildDocker 会先构建前端镜像和后端镜像启动 MySQL 并等待健康检查通过最后启动后端和前端。三分钟后直接访问http://localhost就是完整系统。7.3 配置个性化.env文件让同一份 compose 配置适配不同环境一个常见的需求是“测试环境用这套账号密码生产环境换一套”。你不需要维护两份 compose 文件使用.envMYSQL_ROOT_PASSWORDprod123456 MYSQL_DATABASEdemocompose 文件里用变量引用environment: MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD} MYSQL_DATABASE: ${MYSQL_DATABASE}启动时指定环境文件docker compose --env-file .env.prod up -d同一份配置不同环境只需要替换一个文件不会再出现“测试没问题、生产连不上”的鬼故事。8. Docker 镜像“搬家”的几种方式没有网络也能跑8.1 场景内网服务器没有外网怎么办很多企业内网环境不能直接访问 Docker Hub但你需要在服务器上运行镜像。方法不止一种我按实用程度排个序方法一Docker 镜像导出导入在能联网的机器上docker save -o my-app.tar my-app:latest把 tar 文件拷贝到目标机器然后docker load -i my-app.tar方法二docker compose 服务编排打包好的项目整个目录移动本身就是“项目搬家”的核心场景。目录里包含 Dockerfile、compose 文件、源码、.env目标机器上只要执行docker compose up -d但在内网环境构建时的基础镜像node、openjdk、mysql这些需要提前拉好否则构建过程会卡住。所以完整做法是先在有网的机器上docker pull用到的所有基础镜像然后用docker save打包。方法三自建 Registry如果频繁在几台服务器之间同步镜像自建 Registry 比较高效docker run -d -p 5000:5000 --restartalways --name registry registry:2然后给镜像打标签docker tag my-app:latest localhost:5000/my-app:latest docker push localhost:5000/my-app:latest另外一台机器docker pull registry-server:5000/my-app:latest实测下来小团队内部用 Registry 是最舒服的方案更新镜像不用来回拷贝文件。8.2 镜像拉取慢的根因与解决方案“镜像下载慢”是热搜词也是每个 Docker 新手都绕不过的坎。根因是 Docker Hub 的服务器在国外国内直连速度不稳定。解决方案主要是配置镜像加速器常见的国内可用加速地址包括https://docker.m.daocloud.iohttps://dockerproxy.comhttps://docker.mirrors.ustc.edu.cn配置方式很简单编辑 Docker 配置文件{ registry-mirrors: [ https://docker.m.daocloud.io ] }Linux 上路径通常是/etc/docker/daemon.jsonWindows 的 Docker Desktop 可以在设置界面直接配置。配置后重启 Dockersudo systemctl restart docker我这里要提醒一句镜像加速器是“加速”不是“代{理}”。如果拉取某个镜像还是失败可能是该镜像的 Manifest 列表不支持你的架构或者该镜像仓库本身不支持镜像加速拉取此时可以尝试换一个docker pull mcr.microsoft.com/...这类独立仓库地址或直接换镜像源。8.3 Docker Desktop 启动失败的常见原因Windows 用户安装 Docker Desktop 后卡得最多的就是在启动时提示Docker Desktop failed to start because virtualisation support wasnt detected.这个问题几乎都是因为 CPU 虚拟化未开启或未启用正确的虚拟化平台。处理思路重启进 BIOS确认 Intel VT-x或 AMD SVM已打开以管理员身份运行bcdedit /set hypervisorlaunchtype auto在“启用或关闭 Windows 功能”里勾选“适用于 Linux 的 Windows 子系统”和“虚拟机平台”完全重启 Windows不要选“关机再开机”的快速启动。如果你是在 Windows 11 家庭版没有 Hyper-V也问题不大。Docker Desktop 当前版本都支持基于 WSL2 的后端装好 WSL2 之后直接用 WSL2 模式跑容器实测很稳定。9. 从“开发环境可用”到“生产环境可用”还要处理哪些细节9.1 容器内日志管理别让 /var/lib/docker 被撑爆开发时你不会感觉到日志量有多恐怖生产环境跑一天/var/lib/docker可能直接增长几个 GB。每个容器默认会把 stdout 输出写到宿主机磁盘时间长了会撑爆磁盘。比较好的做法是在 Docker 配置里为所有容器设置日志轮转{ log-driver: json-file, log-opts: { max-size: 10m, max-file: 3 } }这段配置让每个日志文件最大 10MB最多保留 3 个文件。配置后重启 Docker 服务新建的容器自动生效。已经存在的容器不会自动应用需要重建。9.2 容器自启动策略服务器重启后容器是否自动拉起由restart策略控制。我的习惯是services: app: image: my-app:latest restart: unless-stoppedunless-stopped比always更人性化如果管理员手动停掉容器重启系统后它不会自动启动但如果是系统启动导致的容器退出会自动拉起。9.3 配置敏感信息不要在镜像里留密码很多人图省事直接在 Dockerfile 里写ENV MYSQL_PASSWORD123456这样镜像里会永久留存这个密码。别人拿到你的镜像docker history就能看到。生产环境应该用 docker-compose 的env_file或 Docker 原生的--env-file方式在运行时注入。services: app: env_file: - .env注意.env文件不要提交到 Git。在.gitignore里加上它避免密码跟着代码仓库泄露。9.4 容器内的安全基线不要用 root 跑业务进程默认情况下容器内进程以 root 身份运行。测试环境无所谓生产环境建议在 Dockerfile 里建一个普通用户RUN useradd -m appuser USER appuser实测这个步骤对系统安全性有很大提升攻击者即使进到容器里也没有默认的 root 权限。10. 实测复盘把项目交给同事从克隆到运行的全流程10.1 交接检查清单现在我带你完整体验一遍“把项目交给一个完全没有环境配置经验的人”看看到底需要准备什么。我建了一个公开共享文件夹里面包含这些文件demo-project/ ├── backend/ │ ├── Dockerfile │ ├── pom.xml │ └── src/ ├── frontend/ │ ├── Dockerfile │ ├── package.json │ └── src/ ├── docker-compose.yml ├── .env.example └── README.md目标机器是一台只装了 Docker Desktop、没有任何 JDK/Node/MySQL 的 Windows 电脑。下载压缩包、解压、打开终端依次执行docker compose --env-file .env.example up -d --build等待镜像构建完成刷新浏览器应用已经可以访问。10.2 从克隆到运行全流程实测执行命令后Docker Compose 会输出如下阶段拉取 MySQL 镜像如果本地没有构建前端镜像在 node:20-alpine 容器里执行npm install和npm run build生成静态文件构建后端镜像在多阶段构建里编译 Spring Boot 项目启动 MySQL等待健康检查通过启动后端等待 Java 进程就绪启动 Nginx前端容器开始对外提供访问。整个过程在第一次执行时可能需要 5-10 分钟之后再次启动只需要 10-30 秒因为依赖层已经固化在镜像里不会再重新下载。10.3 交接时最容易遗漏的东西.dockerignore文件让COPY . .不把本地的node_modules、target、.git这些大目录打进构建上下文。忘记这一步构建上下文可能达到几百MB拖慢构建速度。.env.example示例环境变量让接手的人知道需要配置哪些变量而不是打开配置文件一脸懵。README 文档把启动命令、端口号、默认账号写清楚。实际交接时README 的约束力比你想象的更大。10.4 为什么这套方案对团队协作影响很大把这个流程跑通之后团队协作方式会自然发生变化新同事入职不用再花一天时间配置环境代码评审时可以快速起一个同环境容器来验证行为线上问题反馈“本地复现不了”这个借口彻底失效了不同服务使用不同的依赖版本互不干扰。我也不是一开始就全盘接受 Docker 的所有理念比如有些网络配置、卷权限问题在实际使用中确实会困扰人。但论解决“环境配置”这个老难题Docker 是目前我见过最可靠、最值得投入学习成本的方案。11. 扩展Windows 沙盒、WSL2 与 Docker 的开发机组合11.1 Docker Desktop 在 Windows 上的两种后端模式很多人第一次接触 Docker 都是在 Windows 上。Docker Desktop 有两种后端模式Hyper-V 和 WSL2。如果 BIOS 虚拟化没开或 Windows 功能没启用完整启动就会失败。我的实测结论是Windows 11 上首选 WSL2。原因有三WSL2 启动更快内存占用更可控你可以在 WSL2 里直接跑 Linux 命令、脚本和 Docker 容器的交互更自然Docker Desktop 的资源设置可以限制 RAM 和 CPU避免占满整机资源。11.2 开发机里的“沙盒级隔离”组合如果你不想每个项目都用 Docker 跑所有依赖也可以采用混合模式日常开发工具IDE、Git 客户端留在宿主机项目运行时的依赖数据库、Redis、消息队列、编译环境全部放 Docker 容器Node/Java/Python 的运行时版本用容器镜像控制宿主机只保留一个 Docker。这样宿主机系统始终保持干净卸载项目时连着容器和卷一起删掉不留残留。11.3 一个常见问题端口冲突项目多了端口冲突是必然会遇到的。比如两个项目都用到 8080。解法就是在 compose 文件里把宿主机端口映射改掉services: app: ports: - 8081:8080宿主机只通过 8081 访问容器的 8080项目内部依然用 8080不需要改代码。这比在本地用各种进程管理工具去抢占端口要省心得多。12. 常用命令速查与排错思路12.1 高频命令清单# 构建镜像 docker build -t my-app:latest . # 运行容器前台 docker run -it --rm -p 8080:8080 my-app:latest # 运行容器后台 docker run -d --name my-app -p 8080:8080 my-app:latest # 查看容器列表 docker ps docker ps -a # 查看日志 docker logs -f my-app # 进入容器 docker exec -it my-app bash # 停止/启动/重启 docker stop my-app docker start my-app docker restart my-app # 删除容器 docker rm -f my-app # 删除无用镜像 docker image prune -f # 查看磁盘占用 docker system df12.2 排错思路从“日志”到“进入容器”遇到容器启动失败先看日志docker logs -f container_name如果日志没输出有效信息就检查容器状态docker inspect container_nameState.ExitCode和State.Error字段会给出不少线索。如果退出很快又没有任何日志大概率是ENTRYPOINT的执行有问题。这时可以覆盖入口点进入容器手动排查docker run -it --entrypoint bash my-app:latest注意必须覆盖--entrypoint否则容器启动又会走原来的命令直接退出。12.3 Docker 磁盘清理别再让 overlay2 占满整个磁盘/var/lib/docker体积增长通常由三种数据组成镜像分层、容器可写层、卷数据。清理方式docker system prune -a这个命令会删除所有停止的容器、没有任何容器使用的网络、没有标签的镜像和所有构建缓存。注意-a会删除所有不被容器使用的镜像如果你想保留某个镜像建议先确认再执行或者去掉-a只清理悬空镜像docker system prune另外构建缓存占用是很多人的知识盲区。你在开发时反复docker build每层缓存都会留在磁盘里。docker builder prune可以单独清理构建缓存。我一般每周执行一次docker system df看看不会让磁盘爆掉。13. 最后一个建议不要为了 Docker 而 Docker到这里整套方案已经可以落地的状态了。但最后我想泼一盆冷水Docker 不是银弹不要逢项目就容器化。如果你的项目只有一个单体服务依赖也就一个 MySQL本机开发时确实没用 Docker 的必要。容器化的适用场景是“环境差异导致大量时间浪费”或者“多人协作时环境不一致影响交付”。强制容器化一个简单项目只会增加 Dockerfile 维护成本。我的实际体会是从“能用 Docker 跑”到“用 Docker 跑得舒服”需要至少两三个项目的磨炼。第一次踩坑很正常镜像体积大、构建慢、数据卷丢失都是必经之路。但只要把前面这些问题都理清一次从第二个项目开始你就能体会到“换电脑、换同事、换服务器三分钟搞定环境”的舒爽感。如果你正在被环境配置折磨先别急着在宿主机上装一堆运行环境。装个 Docker Desktop拿一个小项目练手按照我上面写的多阶段构建和 compose 编排走一遍。等跑通之后再回来按需深入学习网络、数据持久化和安全加固。Docker 这套体系的精髓在于把一次性的环境配置成本转变成可重复、可共享、可版本化的资产。这才是它真正值得投入时间的原因。