
一年多以前我开始在项目里用 Docker 的时候其实心里挺抵触的总觉得又多了一个要伺候的东西。那时候团队里每次新同事入职光搭开发环境就要折腾大半天——JDK 版本不对、MySQL 连不上、Redis 没装、配置文件路径对不上各种问题能把人逼疯。后来我认真把 Docker 这套东西梳理了一遍才发现它解决的不只是“装个环境”这么简单。这篇是 Docker 系列的第一篇我重点讲清楚三件事Docker 是什么、它凭什么能解决环境问题、以及怎么把它装起来跑通第一个容器。文章会结合目前大家搜索最多的场景来展开比如 Windows 下 Docker Desktop 常见启动报错、Ubuntu 里怎么安装 Docker、用 Docker 部署 MySQL 8.0、Redis 主从和 GitLab这些我都会在后面几篇里持续深入。不管你是在自己的 Windows 笔记本上捣鼓还是有一台 Linux 服务器或者只是在学校、公司项目里听说过这玩意儿这篇内容都适合你先通读一遍。我尽量不堆概念用实际操作的思路来讲把那些文档里不会写清楚的细节和坑都点出来。1. 从“环境地狱”说起Docker 到底解决了什么1.1 一个真实的开发环境故事先说说我自己的经历。之前负责一个老项目用的是 Java 8 MySQL 5.7 Redis 3.2这三个基础组件各自的安装方式还不一样。Java 要配环境变量MySQL 要初始化数据目录、设置 root 密码Redis 要改配置文件才能后台运行。新来的同事照着文档装每一步都能碰到新问题——有人 MySQL 装成了 8.0密码加密方式变了项目连不上有人 Redis 没设密码结果被人扫到端口爆破还有人 Windows 和 Mac 的路径写法都不一样配置文件里一个斜杠方向不对就启动失败。这个现象有个很形象的名字叫“环境地狱”。你写的代码在本地跑得好好的一到同事电脑上就各种报错测试环境明明和正式环境用的同一个项目表现却完全不一样。原因很简单代码只决定逻辑运行环境决定了逻辑能不能跑起来。而环境包含的东西太多了——操作系统版本、语言运行时、数据库、中间件、系统依赖库、环境变量……任何一环不一致结果就可能不一致。Docker 的思路就是把这整个运行环境打包起来连同代码一起分发和运行。它打包的不是一个安装包而是一个“干净的、预设好的环境快照”。别人拿到这个快照不管自己的机器是什么系统、装没装过依赖都能以几乎一模一样的方式把它跑起来。1.2 容器和虚拟机的本质区别很多人第一次接触 Docker会把它跟虚拟机Virtual Machine混在一起。它们确实都做隔离但原理完全不同。虚拟机是在宿主机上模拟出一整套硬件设备然后在上面跑一个完整的操作系统。比如你用 VMware 开一个 Ubuntu 虚拟机它会占用好几个 G 的内存和磁盘因为里面有一个完整的内核、完整的系统服务开机过程也是完整的系统引导流程。好处是隔离得彻底坏处是资源占用大、启动慢。Docker 容器则要轻量得多。它不模拟硬件也不包含完整的操作系统而是直接共享宿主机的内核只把自己需要的用户空间文件比如某个命令、某个运行库、某个配置打包进镜像里。容器里的进程本质上是宿主机上的一个进程只是通过 Linux 的命名空间Namespace和控制组Cgroups进行了隔离和资源限制。我常用一个类比来解释虚拟机像搬家把整套房子都搬过去连墙和地基都带着容器像住酒店房间里的家具、床品、洗护用品都是按标准配好的但你用的是酒店大楼的水电和承重结构。所以容器启动通常是毫秒到秒级而虚拟机启动至少要几十秒甚至几分钟。下面的对比表可以更直观地看出差异对比项虚拟机Docker 容器隔离级别硬件级虚拟化操作系统进程级隔离每个实例的操作系统有完整 Guest OS共享宿主机内核启动速度慢秒到分钟级快毫秒到秒级资源占用高几 GB 起低几十 MB 到几百 MB镜像大小几个 GB 到几十 GB通常几十 MB 到几百 MB分发效率差传一个虚拟机文件很痛苦好镜像仓库直接 pull1.3 Docker 能做什么、不能做什么Docker 的典型应用场景非常广泛我这里按自己的经验排个序开发环境统一这是大多数人入门的第一个场景。团队约定好一个 Docker Compose 文件所有人一键启动 MySQL、Redis、消息队列等依赖环境一致性从根上解决了。应用打包和交付把应用连同依赖打进镜像推到镜像仓库服务器上只需要 pull 下来 run。升级就是换一个 tag回滚也是换 tag非常干净。微服务架构每个服务独立容器运行单独伸缩互不干扰。这也是 Docker 和容器编排如 Kubernetes最常被联系到一起的原因。CI/CD流水线的每个阶段在独立容器里执行保证构建环境和测试环境一致避免“在我机器上能过”的尴尬。自动化测试测试用例需要各种中间件时不用手工装临时起一个容器用完就删。但 Docker 不是万能的有几点要提前认识清楚它不适合需要极致性能的计算场景。容器共享宿主机内核在 CPU 密集型的数值计算上性能损耗虽然很小但不如裸机部署极致而在某些涉及大量系统调用或需要特定内核特性的场景容器就不合适。它不适合 GUI 桌面应用的日常使用。容器里跑图形界面很别扭虽然有 X11/Wayland 转发之类的方案但体验远不如原生。Windows 容器和 Linux 容器有差异。Docker 最初是为 Linux 设计的在 Windows 上跑 Linux 容器需要一个轻量虚拟机WSL2 或 Hyper-V做支撑。很多人卡在这一步后面我会专门说。数据持久化需要额外设计。容器天生是“无状态”的容器删除后内部数据就没了。数据库这类有状态服务必须使用数据卷Volume或绑定挂载把数据存到宿主机上。2. Docker 的四个核心概念镜像、容器、仓库、数据卷2.1 镜像只读模板镜像Image是 Docker 里最重要的概念你可以把它理解成一个“文件的快照启动命令的集合”。它由一层一层的只读文件系统组成每一层代表一个 Dockerfile 指令产生的变化。比如一个 MySQL 8.0 镜像最底层可能是一个精简的 Linux 基础系统往上一层是 MySQL 的运行目录和二进制文件再往上一层可能是初始化的 SQL 脚本和默认配置文件。这种分层设计带来了两个巨大好处一是多个镜像可以共享底层数据不用重复存储二是构建镜像时只有发生变化的层需要重新生成没有变化的层可以直接用缓存的构建速度非常快。镜像有一个唯一的标识叫镜像 ID是一串哈希值。我们平时用docker images可以看到本机所有的镜像包括镜像名、标签tag、镜像 ID、创建时间和大小。tag 是镜像的版本标记比如mysql:8.0和mysql:5.7就是两个不同版本的标签。tag 可以省略默认是latest但我个人强烈不建议在生产环境用 latest因为它的指向会变容易导致“昨天还能跑今天 pull 完就出问题”。2.2 容器一个运行的镜像容器Container是镜像的运行实例。镜像是一套静态的模板容器则是这个模板被加载到内存里、进程跑起来之后的状态。同一个镜像可以启动出任意多个容器它们之间相互隔离互不影响。打个比方镜像好比是程序安装包解压后的目录容器好比是你把程序启动起来的进程。你可以同时开好几个同样的程序每个进程都有自己的内存空间和状态。容器有自己的可写层你在容器里改文件、写日志、装东西都会写在这一层但容器一旦被删除这个可写层也就没了。这也是为什么我说容器是“无状态”的——如果想要数据持久化必须配合数据卷。2.3 仓库镜像的中央存储地仓库Registry是存放和分发镜像的地方。官方默认用的是 Docker Hub里面有很多官方维护的镜像比如mysql、redis、nginx、ubuntu等等。你可以把镜像 push 到自己的私有仓库比如 Harbor也可以直接用各大云厂商提供的镜像加速服务。因为 Docker Hub 在国内访问速度不稳定很多人会选择使用镜像加速器。这里我不展开讲加速的具体配置只提醒一句国内可用的加速地址经常变动配置前先确认当前是否可用配置完成后记得systemctl daemon-reload和重启 Docker 服务否则可能不生效。docker pull是从仓库拉取镜像的命令docker push是上传镜像的命令。如果不指定仓库地址默认就是 Docker Hub。2.4 数据卷让数据活下来用 Docker 跑 MySQL 这种有状态服务数据卷Volume是必须掌握的。数据卷的作用是把容器里的某个目录映射到宿主机上这样即使容器被删了宿主机上的数据还在下次起新容器时重新挂载同一个目录数据就回来了。数据卷有两种常见的挂载方式。第一种是绑定挂载Bind Mount直接指定宿主机的某个路径比如/my/mysql/data:/var/lib/mysql路径是固定的方便你直接去宿主机上看文件。第二种是命名卷Named Volume由 Docker 管理路径通常隐藏在/var/lib/docker/volumes/下面你只需要给卷起个名字比如mysql-data:/var/lib/mysql不用关心它的具体物理位置。实际使用中如果只是要持久化命名卷更省心如果你需要频繁查看或备份宿主机上的数据文件绑定挂载更直观。两种方式在docker run命令里都用-v或--mount参数来指定我的习惯是简单场景用-v复杂场景用--mount因为后者的语义更清晰。3. 装好 DockerWindows 和 Ubuntu 的安装实操3.1 Windows 下安装 Docker Desktop 的完整过程Windows 上最推荐的方式是安装 Docker Desktop它是 Docker 官方提供的图形化客户端集成了 Docker 引擎、命令行工具、镜像管理界面和一键配置功能。Docker Desktop 在 Windows 上底层依赖 WSL2 或 Hyper-V所以安装前需要满足几个前置条件。我用的是 WSL2 方案因为它比 Hyper-V 更轻量而且启动速度更快。首先要确保 Windows 10 版本在 2004 以上或者 Windows 11。打开 PowerShell管理员权限运行下面两条命令wsl --install这条命令会安装 WSL2 以及默认的 Linux 发行版通常是 Ubuntu。如果你已经装过 WSL可以执行wsl --set-default-version 2把默认版本设为 2。安装完成后重启系统。接着去 Docker 官网下载 Docker Desktop for Windows 安装包。安装过程中会让你选择是否使用 WSL2勾选上就行。装完启动 Docker Desktop正常情况下右下角鲸鱼图标会变成绿色表示 Docker 引擎已经跑起来了。在命令行里执行docker version能看到 Client 和 Server 两段信息就说明安装成功了。注意很多初学者看到一个docker: error during connect的报错就以为是安装失败其实多半只是 Docker Desktop 还没起来或者是在没有管理员权限的终端里执行命令。先确认鲸鱼图标状态再排查终端权限。3.2 Ubuntu 下安装 Docker Engine 的三种方式Ubuntu 服务器上装的是 Docker Engine没有图形界面官方文档提供了 apt 仓库、离线安装包和脚本安装三种方式我最推荐的是 apt 仓库方式因为后续升级方便。先把老版本清理干净如果有的话sudo apt remove docker docker-engine docker.io containerd runc然后更新软件源并安装依赖sudo apt update sudo apt install ca-certificates curl gnupg lsb-release添加 Docker 官方 GPG 密钥sudo mkdir -p /etc/apt/keyrings curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg添加软件源。注意不同版本 Ubuntu 的代号不一样用lsb_release -cs可以取到代号但国内网络环境下这一步可能非常慢如果卡住建议确认网络状态或考虑使用镜像源。echo \ deb [arch$(dpkg --print-architecture) signed-by/etc/apt/keyrings/docker.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-compose-plugin安装完成后执行sudo systemctl enable docker sudo systemctl start docker这时执行sudo docker run hello-world如果能看到 “Hello from Docker!” 的输出说明安装成功。默认情况下普通用户执行 docker 命令需要 sudo为了省事可以把当前用户加入 docker 组sudo usermod -aG docker $USER退出并重新登录后docker ps就能直接执行了。这里要提醒一句把用户加入 docker 组实际上等于把这个用户提升到了 root 权限因为 docker 进程可以通过挂载宿主目录等方式访问宿主机文件。所以在多人共用的服务器上要谨慎处理。3.3 踩坑重点Virtualization support not detected 和 Docker Desktop 启动失败搜索热词里有一条 “virtualization support not detected docker desktop failed to start because v”这是 Windows 下用 Docker Desktop 最常见也最让人头疼的报错。报错全文大致是“Docker Desktop failed to start because virtualization support is not detected” 或 “Virtualization support not detected”。这个报错的意思是 Docker Desktop 没有检测到 CPU 的虚拟化功能。排除步骤按顺序来第一步检查 BIOS/UEFI 里是否开启了虚拟化技术。重启电脑进入 BIOS/UEFI 设置找 “Intel VT-x”、“Intel Virtualization Technology” 或 “AMD-V” 相关选项把它设为 Enabled。不同品牌的主板菜单位置不一样一般在 Advanced 或 CPU Configuration 下。这一步看着简单但很多人漏了而且改完 BIOS 要保存并重启别直接关页面。第二步在 Windows 功能里启用“虚拟机平台”和“适用于 Linux 的 Windows 子系统”。打开“控制面板 - 程序 - 启用或关闭 Windows 功能”把 “Hyper-V”如果要用 Hyper-V 模式、“虚拟机平台”、“适用于 Linux 的 Windows 子系统” 都勾上然后重启。第三步确认 WSL2 已启用。管理员 PowerShell 执行wsl --status看输出里的默认版本是不是 2。如果不是执行wsl --set-default-version 2。第四步检查 Docker Desktop 的设置。打开 Docker Desktop 的 Settings - General确认 “Use the WSL 2 based engine” 是勾选状态。如果之前用的是 Hyper-V而你的 Windows 版本是家庭版Hyper-V 默认不可用那就要切到 WSL2 模式。我碰到过一种很隐蔽的情况有个同事的电脑在 BIOS 里明明开了虚拟化任务管理器里也显示“虚拟化: 已启用”但 Docker Desktop 还是报同样的错。最后发现是因为 Windows 的“内核隔离 - 内存完整性”跟虚拟机监控程序冲突导致 Hyper-V 无法正常加载。把“内核隔离”关掉后问题就解决了。这只是其中一种可能遇到类似情况可以往这个方向查一查。4. 跑通第一个容器镜像、启动、进入、删除4.1 拉取镜像docker pull 的细节安装好 Docker 后第一件事就是拉一个镜像试试。这里我用 nginx 做例子因为镜像小、启动快不容易因为资源问题出岔子。docker pull nginx:1.25这个命令会从 Docker Hub 拉取 nginx 的 1.25 版本镜像。如果你不写版本号会默认拉latest。拉取过程会显示分层下载的进度条每一层对应镜像的一个文件系统层。拉完之后用docker images查看本机镜像docker images输出里能看 REPOSITORY镜像名、TAG标签、IMAGE ID镜像 ID、CREATED创建时间和 SIZE大小。你会发现 nginx:1.25 其实只有一百多 MB远小于一个虚拟机的大小。这里有个容易踩的坑如果你在国内网络环境下拉取官方镜像速度可能很慢甚至超时。解决办法是配置镜像加速器在 Docker Desktop 的 Settings - Docker Engine 里加一段 json或者在 Linux 的/etc/docker/daemon.json里写{ registry-mirrors: [https://xxxx.mirror.aliyuncs.com] }需要注意的是daemon.json 改完必须重启 Docker 才能生效重启命令是sudo systemctl daemon-reload sudo systemctl restart docker4.2 启动容器docker run 的每个参数都要搞懂拉完镜像就可以启动容器了。docker run -d --name my-nginx -p 8080:80 nginx:1.25这条命令里的每个参数都值得解释清楚-d表示后台运行容器在后台跑不占用当前终端。--name my-nginx给容器起个名字方便以后用docker stop my-nginx直接操作。-p 8080:80是端口映射把宿主机的 8080 端口映射到容器的 80 端口。nginx 默认监听 80 端口但你宿主机上可能已经有别的程序占着 8080所以要指定一个当前空闲的端口。nginx:1.25指定要基于哪个镜像启动容器。启动后执行docker ps可以看到正在运行的容器列表。docker ps -a则能看到所有容器包括已经退出的这在排查问题的时候非常有用。接着在浏览器里访问http://localhost:8080如果能看到 nginx 的默认欢迎页说明容器已经成功跑起来了。4.3 进入容器、查看日志、清理容器进入容器最常用的命令是docker exec -it my-nginx bash。-it表示交互式终端这样你就能进入容器内部看到里面的文件结构执行命令。比如docker exec -it my-nginx bash ls /usr/share/nginx/html注意不是所有镜像都自带 bash有些精简镜像只有 sh所以进不去 bash 时可以试试docker exec -it my-nginx sh。查看容器日志用docker logsdocker logs my-nginxdocker logs -f my-nginx是持续跟踪日志输出和tail -f的效果类似。排查问题的时候这个命令几乎必用。停止容器用docker stop my-nginx删除容器用docker rm my-nginx。删除镜像用docker rmi nginx:1.25。实际操作里有个很容易犯的错误先删镜像结果提示“image is being used by stopped container”这是因为容器虽然停了但还存在。所以顺序应该是先docker rm把容器删掉再docker rmi删镜像。这里有一个小技巧可以用一条命令把没用的容器全部清掉docker container prune清理悬空镜像docker image prune这两个命令在使用 Docker 一段时间后会非常常用因为开发过程中会产生大量停止的容器和没有 tag 的中间镜像占着磁盘空间。5. 实战预热用 Docker 部署 MySQL 8.0、Redis 主从与 GitLab5.1 MySQL 8.0 容器化部署的完整命令先说明一下这一节是给后面几篇打一个“预告基础”的作用更深入的配置比如字符集、主从复制、性能调优我会在后续文章里单独展开。但既然搜索热词里大家都在找 MySQL 8.0我就把最常用的一套命令先放出来并解释每条命令背后的考虑。docker run -d \ --name mysql8 \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORD123456 \ -e MYSQL_DATABASEtestdb \ -e MYSQL_USERtestuser \ -e MYSQL_PASSWORDtestpass \ -v mysql-data:/var/lib/mysql \ --restart always \ mysql:8.0这里的关键点-e是环境变量。MySQL 官方镜像在首次启动时会读取这些环境变量来初始化数据库。MYSQL_ROOT_PASSWORD必填否则容器会启动失败。MYSQL_DATABASE会自动创建一个数据库MYSQL_USER和MYSQL_PASSWORD会创建一个普通用户并授权访问这个数据库。-v mysql-data:/var/lib/mysql是数据卷挂载把 MySQL 的数据文件存到命名卷 mysql-data 里。容器删除后数据不丢。--restart always表示 Docker 服务重启或 Docker 守护进程重启时这个容器会自动跟着启动。生产环境和服务类容器建议加上但要注意如果容器是因为代码 bug 反复崩溃always 策略会导致容器反复重启。-p 3306:3306把宿主机的 3306 端口映射到容器的 3306。如果你的宿主机 3306 已经被占用了可以改成-p 3307:3306。启动后可以用docker exec -it mysql8 mysql -uroot -p进入 MySQL 命令行。输入密码后执行show databases;能看到初始化的 testdb 数据库。这里有个值得注意的点MySQL 8.0 默认使用caching_sha2_password加密方式如果客户端用的驱动比较老比如一些旧的 JDBC 驱动或 Python 库可能连接时报错提示认证方式不支持。解决办法是新建用户时指定用mysql_native_passwordCREATE USER appuser% IDENTIFIED WITH mysql_native_password BY apppass; GRANT ALL PRIVILEGES ON testdb.* TO appuser%;5.2 Redis 主从从零开始理解复制机制搜索热词里也有 “docker 安装 redis 主从”我先给出一个最简的主从部署方案后面有机会再单独写一篇深入讲 Redis 集群的。Redis 主从复制的作用简单说就是一个主节点负责写多个从节点负责读实现读写分离和数据备份。用 Docker 实现起来非常清爽不需要在一台机器上装多个 Redis 实例每个实例一个容器就行。先起主节点docker run -d \ --name redis-master \ -p 6379:6379 \ -v redis-master-data:/data \ redis:7.0 redis-server --appendonly yes--appendonly yes开启 AOF 持久化数据会在容器里追加保存数据卷挂载到宿主机。之后起从节点docker run -d \ --name redis-slave \ -p 6380:6379 \ -v redis-slave-data:/data \ redis:7.0 redis-server --appendonly yes --slaveof redis-master 6379这里的核心是--slaveof redis-master 6379但在使用--name的时候需要注意容器之间要能互相通信最直接的方式是让它们处于同一个自定义网络中。Docker 会为每个容器分配 IP但用容器名作为主机名更方便。所以更规范的做法是先创建一个自定义网络docker network create redis-net然后启动主节点时加--network redis-net启动从节点时也加--network redis-net并且在从节点的--slaveof里用redis-master作为主机名docker run -d \ --name redis-slave \ --network redis-net \ -p 6380:6379 \ -v redis-slave-data:/data \ redis:7.0 redis-server --appendonly yes --slaveof redis-master 6379这里有个经验分享--slaveof后面跟的必须是主节点容器的名称或 IP而且从节点启动时如果主节点还没就绪可能会导致连接失败。我的建议是先把主节点跑起来等几秒确认日志正常再启动从节点。检查主从是否建立成功可以进入从节点执行redis-cli info replication看到role:slave并且master_link_status:up就说明复制链路正常。另一个快速验证方式是主节点set foo bar从节点get foo能取到值就行。5.3 GitLab企业级代码托管平台的容器化安装GitLab 官方提供了 Docker 镜像部署起来不算复杂但要注意几个配置项。先给命令sudo docker run -d \ --name gitlab \ --restart always \ -p 8022:22 \ -p 8088:80 \ -v /srv/gitlab/config:/etc/gitlab \ -v /srv/gitlab/logs:/var/log/gitlab \ -v /srv/gitlab/data:/var/opt/gitlab \ gitlab/gitlab-ce:16.6.2-ce.0这里关键的是端口映射和本地目录映射。默认 GitLab 的 SSH 端口是 22但如果你的宿主机 22 端口已经被系统 SSH 占用了这是最常见的情况就要映射到其他端口比如8022:22。Web 端口默认是 80如果被占用就映射到别的端口比如8088:80。另一个容易忽略的地方是 external_url 的配置。GitLab 启动后如果你用http://localhost:8088访问页面上的克隆地址可能会显示http://localhost而不是带端口的形式这是因为 GitLab 默认生成的克隆地址是从 external_url 读取的。所以要在/srv/gitlab/config/gitlab.rb里配置external_url http://gitlab.example.com:8088 gitlab_rails[gitlab_shell_ssh_port] 8022改完配置后执行docker exec gitlab gitlab-ctl reconfigure让配置生效。GitLab 首次启动比较慢要等几分钟甚至十几分钟可以通过docker logs -f gitlab观察初始化日志。初始 root 密码在/etc/gitlab/initial_root_password文件里但是注意这个文件会在首次配置完成后 24 小时被删除所以最好立刻登录并修改密码。6. 常见问题排查实录Docker 使用中的高频坑6.1 镜像拉取失败的常见原因场景一Docker Hub 拉取超时或连接被拒绝。这是国内用户最常见的问题解决方案就是前面提过的配置镜像加速器。配置完如果还是慢可以试试多配置几个加速地址Docker 会依次尝试。场景二权限不足“permission denied while trying to connect to the Docker daemon socket”。这是因为当前用户不在 docker 组里。执行sudo usermod -aG docker $USER退出重新登录。如果着急用也可以先sudo docker但长远的做法还是加组。场景三拉取镜像时提示“no matching manifest for linux/amd64”。这通常是在 ARM 架构的机器比如 Apple Silicon Mac上拉取了一个只支持 amd64 的镜像。解决办法是拉取支持多架构的镜像或者在命令里指定平台参数比如--platform linux/amd64但这样会走模拟器性能会有损耗。6.2 容器启动失败的排查思路容器启动失败时第一反应应该是查看日志而不是反复重启。比如 MySQL 容器启动失败执行docker logs mysql8会直接告诉你原因——可能是密码环境变量没设置可能是端口被占可能是数据目录权限不对。端口被占的情况很常见启动时提示 “bind: address already in use”。解决方式有两个一是换一个宿主机端口二是先找出占用端口的进程并处理它。在 Linux 上可以用ss -lntp | grep 3306查看占用进程。数据库容器还经常碰到一个问题数据卷里的数据文件权限不对。比如你把宿主机的/my/mysql/data挂载到容器但是宿主机的这个目录没有给 mysql 用户足够的权限MySQL 就会报 “Cant open file” 或 “Permission denied”。解决办法是让容器使用一个已存在的、有正确权限的目录或者在第一次启动前先设置目录属主mkdir -p /my/mysql/data chown -R 1000:1000 /my/mysql/dataMySQL 官方镜像里 mysql 用户的 UID 通常是 999 或 1000不同版本略有不同如果改完还是不行就直接看日志里的具体报错信息来调整。6.3 Docker 磁盘占用过大和日志清理Docker 用久了磁盘占用会越来越大主要来源是停止的容器、悬空镜像、构建缓存和容器日志。常用的清理命令docker system df这条命令会清楚地显示镜像、容器、数据卷、构建缓存分别占了多少空间。然后docker system prune -a这会清理停止的容器、没有使用的网络、悬空镜像和构建缓存。注意-a会连带清理没有容器使用的所有镜像如果某些镜像你之后还要用最好先确认一下。如果只想清理构建缓存单独执行docker builder prune日志清理也是一个容易忽略的点。容器默认的日志驱动会无限增长如果不限制一个长期运行的容器日志文件能占掉好几个 G。建议在/etc/docker/daemon.json里加上日志轮转配置{ log-driver: json-file, log-opts: { max-size: 10m, max-file: 3 } }这样每个容器最多保留 3 个 10MB 的日志文件超过就轮转删除。这个配置对生产环境尤其重要。6.4 常见错误速查表报错信息原因解决方法Cannot connect to the Docker daemonDocker 引擎没启动启动 Docker Desktop 或systemctl start dockervirtualizaion support not detectedBIOS 虚拟化未开启或 WSL2 未启用开启 VT-x/AMD-V启用 WSL2port is already allocated宿主机端口被占用换端口或停掉占用端口的进程permission denied while trying to connect当前用户不在 docker 组sudo usermod -aG docker $USER后重新登录no matching manifest for linux/amd64架构不匹配换多架构镜像或指定--platformdata directory has invalid permissions数据卷目录权限不对检查并修改宿主机挂载目录权限Container is using volume, cannot delete有容器还在用数据卷先删除相应容器OCI runtime exec failed容器内没有目标命令换用sh或安装必要工具7. 从新手到能用我给初学者的几点心法7.1 不要一开始就追求编排工具很多新人学 Docker 的时候看到 docker-compose 和 Kubernetes 就焦虑觉得自己是不是也得马上学会。我的经验是先把单个容器的原理和常用命令搞懂再接触编排工具会轻松很多。docker-compose 本质上是把多个docker run参数写进一个 YAML 文件里方便批量管理Kubernetes 则是在多台机器上调度容器的系统。这些都是在掌握单机 Docker 之后自然延伸的。我自己带过的几个人踩得最多的坑不是命令不会而是对“容器是无状态”这件事没有建立起直觉。一删容器发现数据库数据没了、配置文件改了没生效才回头理解数据卷的意义。所以我建议你上手时重点去感受“容器生命周期”和“数据卷持久化”这两个机制理解它们之后很多问题都能自己推断出来。7.2 学会看文档比背命令更重要Docker 的命令数量并不多常用的大概二十个不到。但是每个镜像的用法差异很大最可靠的信息来源是镜像仓库里的说明页。比如 MySQL 镜像的 Docker Hub 页面会详细写明有哪些环境变量、数据目录在哪里、初始化脚本放在哪个目录。很多人遇到问题就上网搜搜到的答案过时不说还可能把你带偏。学会看官方镜像文档是一种能够长期受用的能力。7.3 让 Docker 成为你的第一道防线我现在的工作习惯是项目一开始就把所有外部依赖都容器化代码在本地跑之前统一用一条命令把 MySQL、Redis、RabbitMQ 等服务拉起来。团队成员不再需要各自在自己电脑上装这些中间件文档里只需写清楚“先执行 docker compose up -d”。这样的好处不仅是一致性还大幅降低了新同事的上手门槛。这套思路是 Docker 带给我最大的工作方式改变。后面在这个系列里我会继续写镜像构建与 Dockerfile 的最佳实践、docker-compose 管理多容器应用、MySQL 8.0 的深入部署与主从方案、Redis 集群搭建等内容。先花点时间把这篇的基础打好后面的路会顺很多。