
1. 项目概述这玩意儿到底解决什么问题先别急着敲命令咱们花两分钟把概念捋清楚。Docker 不是虚拟机这句话我几乎每次跟人聊都要强调一遍。虚拟机是把一整台电脑的资源切分出来每个虚拟机里跑一个完整的操作系统笨重、启动慢、吃内存而 Docker 是把你的应用和它运行所需要的一切——代码、运行时、系统工具、库文件、配置——全部打包进一个标准化的“盒子”里这个盒子叫镜像。盒子跑起来之后就叫容器。给没接触过的朋友举个生活化的例子你搬家的时候如果每次把锅碗瓢盆、被子衣服散着抱过去不仅累还容易丢三落四。但如果你把所有东西分门别类装进统一的整理箱贴上标签搬到新家直接拆箱摆好就能用。Docker 就是那个“整理箱”只不过它装的是整个软件运行环境。你在一台机器上调试好的环境换到另一台机器上只要拉取同一个镜像启动容器结果完全一致不会再出现“在我电脑上明明好好的”这种经典甩锅现场。这篇内容适合谁看一句话所有被环境问题折磨过的开发者、运维、测试以及刚接触云原生、微服务、CI/CD 的同学。它解决的核心痛点就三个环境不一致开发、测试、生产环境之间的差异用容器彻底抹平。资源利用率低一台物理机可以运行几十个容器比虚拟机省太多资源。交付部署慢镜像打好了一条命令启动整个服务告别繁琐的手工配置。Docker 的出现本质上是在“软件交付”这个环节做了一次标准化革命。以前交付的是源代码加一堆部署文档现在交付的是镜像镜像里面的东西打包时什么状态跑起来就什么状态。理解了这个核心逻辑后面所有操作都会变得顺理成章。2. 环境准备先把 Docker 装到你的机器上2.1 Windows 安装的坑与解Windows 上安装 Docker首选方案是Docker Desktop。但很多人第一次装就撞上一堵墙——启动时报错Virtualization support not detectedDocker Desktop failed to start because virtualisation support wasnt detected.这个报错基本可以锁定为两件事BIOS 里没开虚拟化或者Hyper-V / WSL2 功能没启用。解决办法按顺序来第一步打开任务管理器切到“性能”选项卡看右下角“虚拟化”是不是“已启用”。如果是“已禁用”需要重启进 BIOS找到 Intel VT-x 或 AMD SVM 选项打开后保存退出。不同品牌主板菜单位置不一样但搜“虚拟化”关键字基本都能找到。第二步确认 Windows 功能里开启了两样东西。打开“控制面板 - 程序和功能 - 启用或关闭 Windows 功能”勾选“适用于 Linux 的 Windows 子系统”和“虚拟机平台”确定后重启系统。第三步如果你用的是 Windows 11 或较新的 Windows 10建议把内核更新到最新。Docker Desktop 新版默认使用 WSL2 后端比旧版 Hyper-V 方案内存占用小、启动速度快得多。验证 WSL2 是否正确启用可以在 PowerShell 里执行wsl --status如果提示内核过期或者没有默认发行版执行一次wsl --update再装一个如 Ubuntu 的发行版就行。实测下来把 Docker Desktop 的 Settings 里 “Use WSL 2 based engine” 勾选上日常开发体验非常顺滑。2.2 Linux 安装一条命令还是手动配置Linux 上安装 Docker 相对省心多数现代发行版都走官方仓库。Ubuntu / Debian 系的推荐做法是直接用官方脚本但脚本方式不适合生产环境——你没法锁定版本。我的习惯是手动添加仓库这样后续apt upgrade时不会莫名其妙被升级到不兼容的版本。以 Ubuntu 为例核心步骤就四段sudo apt update sudo apt install apt-transport-https ca-certificates curl gnupg lsb-release curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg echo deb [arch$(dpkg --print-architecture) signed-by/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable | sudo tee /etc/apt/sources.list.d/docker.list /dev/null sudo apt update sudo apt install docker-ce docker-ce-cli containerd.io装完之后先别急着跑docker run两个事情必须先做第一把当前用户加入 docker 组否则每条命令都得加 sudosudo usermod -aG docker $USER改完用户组要重新登录才会生效。第二设置 Docker daemon 开机自启并立即启动sudo systemctl enable docker sudo systemctl start docker2.3 镜像下载慢的根治方案不管哪个平台装完 Docker 之后第一个糟心事就是从 Docker Hub 拉镜像跟挤早高峰地铁一样慢。解决办法就是配镜像源。Docker Desktop 用户直接在 Settings - Docker Engine 里编辑 JSON{ registry-mirrors: [ https://docker.1ms.run, https://docker.xuanyuan.me ] }Linux 用户则编辑/etc/docker/daemon.json没有这个文件就新建一个内容一样然后重启 Dockersudo systemctl restart docker需要提醒的是镜像源属于“加速器”不保证长期稳定建议多备几个主备切换。另外如果你用的是国内云厂商的服务器直接配对应厂商的加速器地址效果最好。3. 核心概念与常用命令一次搞懂镜像、容器、数据卷3.1 镜像与容器的关系很多人一上来就被镜像、容器、仓库三个概念绕晕。其实类比一下就通了镜像是“类”是模板是只读的。它定义了应用长什么样、需要什么依赖。容器是“实例”是镜像跑起来之后的一个进程可读可写。你可以docker start、docker stop、docker rm。仓库是“应用商店”镜像集中存放的地方最知名的就是 Docker Hub企业里经常用 Harbor 自建私有仓库。日常写代码时我的理解是镜像好比一个安装包容器就是安装包执行后的程序。你可以从一个安装包启动 N 个程序互不干扰。镜像还有一层设计值得注意——分层存储。每个镜像由多层只读层叠加最上层是一个可写容器层。因此两个镜像如果共享基础层磁盘里只需要存一份。这也是为什么很多镜像才几百 MB但拉取时能看到多层下载进度的原因。3.2 必须掌握的 10 个命令命令不用全背先记住下面这组覆盖 90% 的日常场景操作命令说明查看镜像docker images列出本地所有镜像拉取镜像docker pull nginx从仓库下载镜像不指定版本默认 latest运行容器docker run -d -p 8080:80 nginx-d 后台运行-p 端口映射宿主机8080映射容器80查看容器docker ps -a-a 显示所有容器包括已停止的进入容器docker exec -it 容器ID /bin/bash进入容器内部交互操作停止容器docker stop 容器ID发送 SIGTERM 信号优雅停止删除容器docker rm 容器ID删除已停止的容器删除镜像docker rmi 镜像名删除镜像查看日志docker logs -f 容器ID滚动查看容器输出日志构建镜像docker build -t 名字:版本 .根据 Dockerfile 构建镜像3.3 数据卷容器删了数据不能丢新手最容易踩的坑是容器一删数据全部蒸发。因为容器层是可写的但也是易失的。解决办法是使用数据卷volume或者绑定挂载bind mount。数据卷是 Docker 管理的独立存储区域容器删除后卷还在。绑定挂载则是把宿主机的一个目录直接映射进容器改宿主机的文件容器里立刻同步。以 MySQL 为例不挂载数据卷容器删了数据库就没了。挂载了数据落在宿主机容器随便重建docker run -d \ --name mysql8 \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORD123456 \ -v /opt/mysql-data:/var/lib/mysql \ mysql:8.0这里-v /opt/mysql-data:/var/lib/mysql就是把宿主机的/opt/mysql-data目录绑定到容器内的 MySQL 数据目录。这是一种最直接、最容易理解的持久化方案。数据卷还有一种写法用具名卷docker volume create mysql-data docker run -d -v mysql-data:/var/lib/mysql mysql:8.0两种方式各有优劣绑定挂载依赖宿主机的目录路径可读性好方便备份具名卷由 Docker 管理路径不用关心但对宿主机文件直接操作就没那么便捷。生产环境我更推荐具名卷配合docker volume系列命令管理维护更清晰。3.4 端口映射与网络模式容器之间是隔离的网络空间宿主机访问不到容器内部除非你把端口映射出来。-p参数就是这个作用-p 宿主机端口:容器端口。但生产环境里一个个端口手动映射不现实。一个服务集群几十个容器每个都要暴露端口管理成本高得吓人。更好的方案是让容器之间通过 Docker 网络直接互相访问。创建一个自定义网络docker network create my-network启动容器时指定加入该网络docker run -d --network my-network --name app1 my-app docker run -d --network my-network --name app2 my-app这样app1和app2之间直接通过容器名就能互通不需要记 IP也不依赖端口映射。内在原理是 Docker 内置的 DNS 解析功能容器名就是主机名。了解这个机制之后你会发现编排多容器应用比想象中简单很多。4. 实操从 Dockerfile 到 Compose 一键启动微服务4.1 编写 Dockerfile 的实战经验光会拉现成的镜像还不够部署自己的项目需要自己写 Dockerfile。拿一个简单的 Java Spring Boot 项目举例FROM openjdk:17-jdk-slim WORKDIR /app COPY target/demo.jar app.jar EXPOSE 8080 ENTRYPOINT [java, -jar, app.jar]看起来很简单对不对但要注意几个细节基础镜像为什么选slim版本因为它去掉了大量用不到的调试工具和文档体积能小一半以上。对线上环境来说镜像体积越小拉取越快攻击面越小。COPY和ADD的区别要弄清楚。ADD能自动解压 tar 包、能接 URL但在 Docker 官方的最佳实践里更推荐用COPY语义更明确不会出现意外行为。写 Dockerfile 时一定要记得加.dockerignore文件把target、.git、*.log这类文件排除在外不然构建上下文会把整个项目目录打包发送给 Docker daemon构建速度慢到怀疑人生。4.2 Compose 编排告别一条条 docker run当一次要跑 MySQL、Redis、Nacos、业务应用四个容器时再手动敲docker run就是灾难。这个时候应该用Docker Compose用声明式的 YAML 文件管理多容器应用。这里给一个 MySQL 8 主从复制的 Compose 配置片段网上问“docker安装redis主从”“docker安装mysql8”的特别多这类问题用 Compose 解决最优雅version: 3.8 services: mysql-master: image: mysql:8.0 container_name: mysql-master environment: MYSQL_ROOT_PASSWORD: root123 ports: - 3306:3306 volumes: - ./master-data:/var/lib/mysql - ./master-conf/my.cnf:/etc/mysql/conf.d/my.cnf mysql-slave: image: mysql:8.0 container_name: mysql-slave depends_on: - mysql-master environment: MYSQL_ROOT_PASSWORD: root123 ports: - 3307:3306 volumes: - ./slave-data:/var/lib/mysql - ./slave-conf/my.cnf:/etc/mysql/conf.d/my.cnf执行docker compose up -d两个容器同时启动。主从配置的my.cnf挂在宿主机对应目录下改配置后重启容器即可生效完全不用进容器操作。这里面有一个实战细节主从同步时要特别注意读写权限。Master 需要创建一个专门用于复制的账号并执行CHANGE MASTER TO语句指向 Master 的地址。在 Compose 网络里Slave 可以直接用服务名mysql-master:3306访问 Master不需要填 IP。这个体验比裸跑多容器舒心得多。4.3 IDEA 里直接构建 Docker 镜像Java 开发者天天问“idea 打包 docker 镜像”怎么搞。其实 IDEA 内置了 Docker 插件配一次之后一键构建打开 Settings - Build, Execution, Deployment - Docker添加一个 TCP 连接。如果你的 Docker 装在远程 Linux 服务器上可以配置远程 API 地址本地直接构建远程推送如果是本地 Docker Desktop选择默认 socket 就行。在pom.xml里加dockerfile-maven-plugin插件或者用spotify的旧版插件配置镜像仓库地址、镜像名、Dockerfile 路径。执行mvn package dockerfile:build一次搞定编译加打包镜像。这里分享一个心得本地开发环境不一定要把镜像推到远端仓库。很多团队用 IDEA 构建后直接加载到本地 Docker然后通过 IDE 的 Docker 面板启动容器调试改完代码重新 build 镜像再重启容器。这种做法省去了 registry 中转联调效率明显高出一截。真正要做发布时再接上 CI 流水线 push 到私有仓库。5. 进阶知识虚拟化报错、权限问题与镜像仓库5.1 Windows 下 Docker Desktop 启动失败全解刚才说了 virtualization support not detected 的排查步骤这里再补一个常见报错Failed to connect to the Docker API at npipe:////./pipe/dockerDesktopLinuxEngine这个报错八成是 Docker Desktop 的 Linux 引擎没起来或者 WSL2 后端通信异常。排查顺序先看 Docker Desktop 里 Linux engine 的图标是绿是红。如果是红的用 PowerShell 执行wsl --shutdown然后等一会儿重新启动 Docker Desktop。如果还不行把 Docker 的 WSL 内核升级彻底检查一遍或者干脆重置 WSL 网络栈wsl --update wsl --shutdown如果运行的是老版本系统且不支持 WSL2那就得切回 Hyper-V 后端。在 Docker Desktop 的 Settings 里勾选 “Use Hyper-V instead of WSL 2”前提是 Windows 功能里开了 Hyper-V。5.2 权限报错的常用解决套路Linux 机上经常碰到的权限坑有三类permission denied而 docker 命令加了 sudo 才正常当前用户没加进 docker 组执行sudo usermod -aG docker $USER然后重新登录。Got permission denied while trying to connect to the Docker daemon socket同上或者 docker daemon 没启动sudo systemctl status docker看一眼就知道了。Cannot connect to the Docker daemon at unix:///var/run/docker.sockdaemon 挂了sudo systemctl restart docker。5.3 私有仓库与常见镜像源问题前面提到用镜像源加速但企业生产环境不会从公网拉镜像风险不可控。自建私有仓库一般推荐Harbor它提供镜像的权限管理、漏洞扫描、复制策略。对个人学习或小型团队用 Docker 自带的 registry 镜像最简单docker run -d -p 5000:5000 --name registry registry:2推送方式docker tag my-app:latest localhost:5000/my-app:latest docker push localhost:5000/my-app:latest如果是远程仓库记得在内网环境里把localhost换成服务器 IP并且客户端配置 insecure-registries 或在 TLS 证书里做信任配置。还有一类情况是拉取特定架构的镜像比如在龙芯、ARM 这些非 x86 平台上装 Docker 跑镜像。常见做法是用--platform参数指定镜像架构。不过要注意跨架构镜像不一定都能直接跑依赖到具体指令集的二进制可能会报错或者性能很差。这种时候只能找对应架构的镜像源或者自己在目标机器上用源码构建。5.4 部署遗留系统与靶场类工具的特殊玩法有些项目直接用 Docker 部署异常省事比如 DVWA 靶场、GitLab、kodbox 这类集成度高的软件。基本套路一样拉镜像、起容器、配端口。以 GitLab 为例docker run -d \ --name gitlab \ --restart always \ -p 8086:80 \ -v /opt/gitlab/config:/etc/gitlab \ -v /opt/gitlab/logs:/var/log/gitlab \ -v /opt/gitlab/data:/var/opt/gitlab \ gitlab/gitlab-ce:latest这里有个很实用的参数--restart always容器意外退出或者机器重启后会自动拉起省去手工干预。跑 GitLab 这类重量应用时机器内存建议至少 8G项目仓库多了之后吃内存非常夸张。另外首次启动后的 root 密码会存放在容器内部要进去找或者在一开始的日志里翻别傻等。6. 常见问题排查与避坑手册6.1 七类高频问题速查表现象原因解决方案拉镜像超时网络问题或镜像源失效换镜像源或配置代理容器一启动就退出应用启动即崩或前台进程没有持续运行docker logs 容器ID查看报错端口被占用宿主机的端口已有进程监听换映射端口或停掉占用进程容器内时间不对镜像默认 UTC 时区运行时加-e TZAsia/Shanghai磁盘占用爆炸日志和悬空镜像积累docker system prune定期清理容器间访问不通不在同一网络创建自定义网络并统一加入构建镜像极慢上下文过大或网络受限写.dockerignore换基础镜像源6.2 日志与容器排障技巧线上出了问题第一件事永远是看日志。docker logs -f是最高频的操作但还有一些细节可以帮助快速定位如果应用是 Java 之类的多阶段日志先看最后 100 行再定位上下文docker logs --tail 100 容器ID如果日志量太大可以临时把日志输出到文件再分析docker logs 容器ID app.log 21还有一个定位思路容器里面和外面是对同一个文件系统隔离的如果你怀疑是时序问题可以docker exec -it进入容器手动执行应用启动命令看到报错信息会直观很多。进入容器之后先ps -ef看看进程是否在跑再netstat -tunlp看端口有没有监听基本能筛出来问题是不是出在应用自身。6.3 资源清理与性能优化Docker 用久了docker system df看一眼你会吓一跳——一堆不知道哪来的悬空镜像和 build cache。清理命令docker system prune -a --volumes这个命令会清掉所有未使用的镜像、容器、网络和数据卷慎用尤其是数据卷。生产环境最好只清悬空镜像docker image prune -f日志文件也是磁盘杀手。默认情况下 Docker 会将容器日志存到/var/lib/docker/containers下不设置上限的话一个大流量服务几天就能把磁盘吃满。建议在 daemon.json 里统一限制{ log-driver: json-file, log-opts: { max-size: 10m, max-file: 3 } }这样每个容器日志单文件最多 10MB最多保留 3 个超出自动滚动。设了之后重启 Docker 才生效适用于所有后续启动的容器。6.4 数据卷误删的教训最后分享一个我踩过的坑。早前用绑定挂载跑一个个人项目里面存了半年的数据库数据。某次兴致来了想在另一台服务器上复现环境想着直接清掉旧容器重来执行了docker compose down -v。-v参数会把 Compose 里定义的所有 volume 一并删除等反应过来数据已经没了。那次之后我对任何带-v的删除命令都会先确认三遍并在根目录下配置定时的数据库备份任务。所以重要数据务必做好宿主机侧备份不要因为容器化就以为数据是安全的。持久化的数据卷只是避免了容器删除带来的损失硬盘故障、误删命令依然可能造成不可逆的破坏。定时备份永远是基本功。7. 小结与下一步学习方向这篇文章聊到这儿Docker 的核心脉络已经完整了从环境安装到镜像与容器概念、从 Dockerfile 到 Compose 编排、从网络端口到数据持久化、从常见报错到资源治理你已经有能力用 Docker 独立部署一套完整的服务了。我个人在实际操作中的体会是Docker 真正提升效率的时刻不是第一次敲下docker run nginx时的那点新鲜感而是当你的项目要加一台新机器、要快速扩大实例数、要在一个干净环境中复现问题、要把一套应用从一台服务器完整搬到另一台服务器的时候——这些场景里容器化的价值会成倍放大。如果你接下来想深入学习建议按这个顺序走先熟练掌握 Dockerfile 的编写和多阶段构建把镜像体积控制好然后上手 Docker Compose把 MySQL、Redis、Nginx、业务应用串成一个编排之后再涉足服务编排方向的 Kubernetes理解 Pod、Deployment、Service 这些概念如何解决大规模调度问题。每一步都和前面这篇文章的内容衔接得上基础打牢了后面走多远都不会晃。