1. 项目概述:这玩意儿到底解决什么问题
先别急着敲命令,咱们花两分钟把概念捋清楚。Docker 不是虚拟机,这句话我几乎每次跟人聊都要强调一遍。虚拟机是把一整台电脑的资源切分出来,每个虚拟机里跑一个完整的操作系统,笨重、启动慢、吃内存;而 Docker 是把你的应用和它运行所需要的一切——代码、运行时、系统工具、库文件、配置——全部打包进一个标准化的“盒子”里,这个盒子叫镜像。盒子跑起来之后,就叫容器。
给没接触过的朋友举个生活化的例子:你搬家的时候,如果每次把锅碗瓢盆、被子衣服散着抱过去,不仅累,还容易丢三落四。但如果你把所有东西分门别类装进统一的整理箱,贴上标签,搬到新家直接拆箱摆好就能用。Docker 就是那个“整理箱”,只不过它装的是整个软件运行环境。你在一台机器上调试好的环境,换到另一台机器上,只要拉取同一个镜像启动容器,结果完全一致,不会再出现“在我电脑上明明好好的”这种经典甩锅现场。
这篇内容适合谁看?一句话:所有被环境问题折磨过的开发者、运维、测试,以及刚接触云原生、微服务、CI/CD 的同学。它解决的核心痛点就三个:
- 环境不一致:开发、测试、生产环境之间的差异,用容器彻底抹平。
- 资源利用率低:一台物理机可以运行几十个容器,比虚拟机省太多资源。
- 交付部署慢:镜像打好了,一条命令启动整个服务,告别繁琐的手工配置。
Docker 的出现,本质上是在“软件交付”这个环节做了一次标准化革命。以前交付的是源代码加一堆部署文档,现在交付的是镜像,镜像里面的东西打包时什么状态,跑起来就什么状态。理解了这个核心逻辑,后面所有操作都会变得顺理成章。
2. 环境准备:先把 Docker 装到你的机器上
2.1 Windows 安装的坑与解
Windows 上安装 Docker,首选方案是Docker Desktop。但很多人第一次装就撞上一堵墙——启动时报错:
Virtualization support not detected,Docker Desktop failed to start because virtualisation support wasn't 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 组,否则每条命令都得加 sudo:
sudo 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,没有这个文件就新建一个,内容一样,然后重启 Docker:
sudo 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_PASSWORD=123456 \ -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.sock:daemon 挂了,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 TZ=Asia/Shanghai |
| 磁盘占用爆炸 | 日志和悬空镜像积累 | docker system prune定期清理 |
| 容器间访问不通 | 不在同一网络 | 创建自定义网络并统一加入 |
| 构建镜像极慢 | 上下文过大或网络受限 | 写.dockerignore,换基础镜像源 |
6.2 日志与容器排障技巧
线上出了问题,第一件事永远是看日志。docker logs -f是最高频的操作,但还有一些细节可以帮助快速定位:
如果应用是 Java 之类的多阶段日志,先看最后 100 行再定位上下文:
docker logs --tail 100 容器ID如果日志量太大,可以临时把日志输出到文件再分析:
docker logs 容器ID > app.log 2>&1还有一个定位思路:容器里面和外面是对同一个文件系统隔离的,如果你怀疑是时序问题,可以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 这些概念如何解决大规模调度问题。每一步都和前面这篇文章的内容衔接得上,基础打牢了,后面走多远都不会晃。