
刚入行那阵子我接过一个老项目光把环境跑起来就折腾了两天。JDK版本不对、Redis本地没装、MySQL编码集不一致、Nginx配置在mac上和Linux上行为还不一样。后来项目里引入Docker新同事入职一条docker compose up -d整套环境十分钟拉齐。如果你现在还没用上Docker或者装了Docker但基本靠到处复制粘贴命令、出了问题完全不知道从哪里下手这篇东西就是给你写的。我会把Docker到底是什么、安装时要避开的坑、最常用的几个操作、一个完整的MySQL实战以及入门阶段一定会遇到的几个报错一次性讲清楚。1. 先搞清楚Docker到底解决了什么问题1.1 环境不一致每个程序员都遇到过的“配置地狱”说一个很经典的场景你在自己电脑上写好的代码提交到服务器上跑起来直接崩。看一眼报错依赖版本对不上再一看Windows下的路径分隔符和Linux下的不一样继续排查数据库连不上因为服务器上压根没装MySQL。这套问题在行业内有个叫法works on my machine在我机器上能跑啊。根子在于代码运行的环境不是一个单一的东西而是操作系统、运行时、依赖库、配置文件、中间件共同作用的结果。任何一环不一样行为就可能不一样。Docker解决的就是这个问题。它把应用连同它需要的运行环境一起打包成一个标准化的镜像任何装了Docker的机器上都能以完全一致的方式运行。我不需要关心目标服务器是CentOS还是Ubuntu不需要手动装依赖不需要担心某个依赖的版本跟系统的包管理器冲突。1.2 镜像、容器、仓库这三个概念必须彻底理解很多人初学Docker会卡在术语上其实这三个词用生活类比一下就通了。**镜像Image**就是一个打包好的模板里面包含运行应用所需的全部内容代码、运行时、系统库、配置文件。它只读不能直接运行就像安装包或者光盘里的系统镜像。**容器Container**是由镜像创建出来的运行实例。同一个镜像可以创建多个容器它们之间相互隔离。容器可以启动、停止、删除容器里的数据在容器删除后通常会消失。**仓库Registry**是存放和分发镜像的地方最知名的是Docker Hub。你从仓库拉取pull一个镜像再用它启动容器run。一个容易混淆的点镜像和虚拟机镜像不是一回事。虚拟机镜像包含一个完整的操作系统虚拟机管理程序Hypervisor直接虚拟化硬件Docker镜像则共享宿主机的操作系统内核只包含应用层以及它依赖的库和文件。所以Docker容器比虚拟机轻得多启动通常是秒级而不是分钟级。1.3 什么时候不该用DockerDocker不是万能的。这一点我想在前言就讲清楚免得你走弯路。需要图形界面的桌面应用用容器跑会很别扭虽然可以折腾X11转发、VNC方案但远不如原生安装流畅。对硬件延时极其敏感的应用比如高性能计算场景Docker的网络和存储层会带来额外开销需要谨慎评估。另外如果你只是写个脚本也不一定要容器化直接跑可能更快。适合用Docker的场景非常明确Web应用、微服务、中间件MySQL、Redis、Nginx这类有状态或无状态服务、CI/CD构建环境、临时测试环境。现在你看到一个GitHub项目如果它提供Dockerfile或者docker-compose.yml基本可以确定这个项目想在几分钟内让你跑起来。2. 安装Docker两条主流路径和一个常见报错安装Docker这件事看起来简单实际踩坑的人特别多。Windows和Linux环境差异巨大我就分开说。2.1 Linux环境安装Ubuntu和CentOS都有各自的门道如果是在云服务器或者自己的Linux机器上Docker官方推荐用apt或yum直接安装。Ubuntu / Debian系sudo apt update sudo apt install -y docker.io这条命令装完后docker服务会自动启动并设置开机自启。部分Ubuntu版本里docker.io这个包是系统自带的旧版本。如果你希望装最新版需要添加Docker官方的APT仓库。我个人建议生产环境用官方仓库安装只是本地玩一下系统源里的docker.io其实够用。CentOS / RHEL系sudo yum install -y yum-utils sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo sudo yum install -y docker-ce docker-ce-cli containerd.io sudo systemctl start docker sudo systemctl enable dockerCentOS 7的坑比较多主要在于内核版本和iptables的兼容性。老内核3.10跑新版Docker偶尔会有莫名其妙的问题一些人会选择升级内核但更稳妥的做法是用系统自带的docker包sudo yum install -y docker sudo systemctl start docker sudo systemctl enable docker在没有systemd的老系统上用service docker start代替即可。安装完成后关键验证命令是docker version能看到Client和Server两端信息才算装成功。如果只看到Client而Server端报错说明守护进程没起来先看服务状态再用journalctl -u docker查日志。2.2 Windows下Docker Desktop与WSL2的配合Windows上现在统一是用Docker Desktop它默认依赖WSL2Windows Subsystem for Linux来运行Linux容器。现代Windows 10/11系统全新安装的话一般缺的不是Docker Desktop本身而是WSL2和虚拟化平台。大致步骤是以管理员身份打开PowerShell执行wsl --install重启电脑。下载Docker Desktop安装包docker.com/products/docker-desktop/直接安装一路下一步。启动Docker Desktop进入Settings - General确保Use the WSL 2 based engine是勾选状态。还有一个很影响体验的操作Docker Desktop默认镜像目录在C盘用久了C盘会爆。可以在Settings - Resources - Advanced里把Disk image location改到D盘或别的数据盘建议装完第一件事就做。2.3 “Virtualization support not detected”这类报错到底怎么解很多人在Windows上第一次启动Docker Desktop会看到类似这样的报错Virtualization support was not detected. Docker Desktop failed to start because virtualization support wasnt detected.看到这个先别慌。这句提示说的是硬件虚拟化没有被系统正确识别。排查顺序是这样第一进BIOS/UEFI找虚拟化开关。Intel处理器叫VT-x或Intel Virtualization TechnologyAMD叫SVM Mode。把它从Disabled改成Enabled保存重启。第二在Windows里打开“启用或关闭Windows功能”确认Hyper-V和“虚拟机平台”两个项处于勾选状态。如果没Hyper-V选项那多半是Windows版本是家庭版家庭版默认没有Hyper-V此时要么升级专业版要么依赖WSL2模式跑容器。第三如果BIOS和Windows功能都开了还是报错在管理员PowerShell里执行systeminfo看最下面Hyper-V要求那一栏。如果显示“检测到虚拟机监控程序。将不显示Hyper-V所需的功能”说明Hyper-V还在跑如果显示未检测继续往上排查BIOS。很多人的问题不在BIOS而是电脑上装了VMware或VirtualBox这类虚拟机软件它们和Hyper-V存在竞争关系。装了VMware的同时又想用Docker Desktop两个平台对虚拟化的状态要求不同会出现各种冲突。我遇到过的案例里最后是把VMware的CPU虚拟化引擎设置改了一次设备开机自检才顺利。如果同时需要多个虚拟机平台建议在Docker Desktop设置里切换为WSL2后端别用Hyper-V后端兼容性好很多。3. 第一次跑起来从一个镜像到运行中的容器3.1 hello-world背后到底发生了什么装好Docker第一个命令几乎永远是docker run hello-world这条命令会经历这些步骤本地找不到名为hello-world的镜像Docker自动去Docker Hub远程仓库拉取镜像下载完成后根据镜像内容创建并启动一个容器容器运行程序向终端输出一行问候信息程序执行完毕容器退出但容器本身还存在于磁盘上状态为exited。这个流程虽然简单但藏着Docker最核心的“客户端-守护进程”架构你敲的docker命令是客户端真正干活的是后台的docker daemon守护进程。客户端不直接跟内核打交道它把所有指令发给daemondaemon负责拉镜像、启动容器、管理网络和存储。验证一下跑完hello-world后执行docker ps -a会看到一个状态为exited的hello-world容器。它没有被自动清理只是因为任务完成退出了。这种“退出但存在”的机制是理解Docker生命周期管理的起点。3.2 镜像下载慢配置镜像加速器新装的Docker默认从Docker Hub拉镜像。在国内直连Docker Hub的下载速度经常让人崩溃几百MB的镜像能拖十几分钟。这时候需要配置镜像加速器。我自己用的是阿里云容器镜像服务的加速器地址需要登录阿里云容器服务控制台在“镜像加速器”页面获取专属地址每个人的地址不一样格式是https://xxxxxx.mirror.aliyuncs.com。拿到地址后配置Linux环境编辑/etc/docker/daemon.json{ registry-mirrors: [https://你的专属地址.mirror.aliyuncs.com] }保存后重启Dockersudo systemctl daemon-reload sudo systemctl restart dockerDocker Desktop用户则是Settings - Docker Engine把同样的配置追加进JSON点Apply Restart。除了阿里云中科大、网易等也提供镜像加速服务选一个稳定可用的就行。配置完成后再跑docker pull速度会有天壤之别。3.3 容器生命周期启动、停止、删除的完整闭环入门阶段必须搞清楚一组命令它们对应容器的生命周期docker ps # 查看运行中的容器 docker ps -a # 查看所有容器包括已退出的 docker stop 容器ID # 优雅停止容器 docker start 容器ID # 启动一个已存在的停止状态的容器 docker restart 容器ID # 重启容器 docker rm 容器ID # 删除容器只能删已停止的 docker rm -f 容器ID # 强制删除运行中的容器一个常见的误区是docker stop 容器后容器并没有消失它只是从running变成exited。下次docker start同一个容器ID会恢复原来的配置再启动。而docker rm会把容器文件删除这个操作不可逆。容器ID可以不用写全写前几位即可比如docker stop 9d2e。如果容器很多也可以给容器起名字docker run --name my-nginx -d nginx加上--name参数后后续所有命令都可以用my-nginx代替容器ID好记也好排查。4. 新手最该掌握的五个核心操作入门阶段这几个操作占了我日常使用Docker的八成。每个都配合一个实际场景来讲解顺带说明为什么这么用。4.1 端口映射让容器外的主机能访问容器内的服务容器默认在一个独立的网络命名空间里宿主机没法直接访问容器内的端口。比如容器里跑着一个Nginx监听80端口你在宿主机浏览器打开http://localhost:80大概率连不上。需要做端口映射docker run -d -p 8080:80 nginx-p参数格式是“宿主机端口:容器端口”。上面命令把宿主机的8080端口映射到容器的80端口访问宿主机8080就相当于访问容器的80。理解这个方向很重要左边是宿主机端口右边是容器端口不能反过来写。如果你宿主机端口写成了80:8080会把宿主机80端口转发到容器8080而容器8080上什么都没监听请求就会失败。多端口或者端口段也支持docker run -d -p 8080:80 -p 8443:443 nginx生产环境通常不会把端口映射暴露到外网而是通过Nginx反向代理统一收口。本地开发和测试时-p怎么方便怎么来。4.2 数据持久化容器删了数据不能跟着删容器一删它内部写入的文件全部消失。对于MySQL、Redis这种数据库服务来说这几乎是灾难。解决方法是用卷volume或者绑定挂载bind mount。最简单的使用是绑定挂载把宿主机的一个目录直接映射到容器的某个路径docker run -d -v /home/myuser/mysql-data:/var/lib/mysql mysql:8.0容器往/var/lib/mysql写入的数据实际落在宿主机的/home/myuser/mysql-data里即使容器被删除数据还在。另一种做法是命名卷docker volume create mysql-data docker run -d -v mysql-data:/var/lib/mysql mysql:8.0命名卷由Docker管理实际存储在宿主机的一个特定目录下不容易被误删但不如绑定挂载直观想进目录看文件需要docker volume inspect。我自己的习惯是本地开发用绑定挂载好调试、好查看生产环境用命名卷可移植性更好。4.3 环境变量给容器传配置的标准方式镜像通常是通用的不同的应用场景靠环境变量区分。最典型的例子是MySQL镜像的root密码docker run -d -e MYSQL_ROOT_PASSWORD123456 -p 3306:3306 mysql:8.0-e参数可以重复使用传多个变量。不同镜像需要哪些环境变量在Docker Hub的镜像详情页都能看到。比如MySQL镜像常用的还有MYSQL_DATABASE创建数据库、MYSQL_USER和MYSQL_PASSWORD创建普通用户。如果你有大量需要传递的配置把这些变量整理到.env文件里然后docker run --env-file .env ...维护起来更干净。注意.env文件里不要有引号格式类似KEYvalue。4.4 进入容器内部docker exec -it的完整用法容器不是黑盒排查问题的时候需要进去看进程、看日志、跑命令。登录容器的命令是docker exec -it 容器ID /bin/bash如果容器里没有bash用docker exec -it 容器ID /bin/sh-it是两个参数组合-i让标准输入保持打开-t分配一个伪终端。没有-t的话命令能执行但不会有交互式界面没有-i的话一些需要输入的命令会直接退出。两个一起用才能获得一个能交互的终端。进去之后你看到的是一套精简的Linux文件系统。多数官方镜像基于Alpine或Debian Slim里面可能没有vim、没有curl、没有ps这是正常现象。需要装的话Alpine系用apk addDebian系用apt update apt install。不过我更推荐的方式是尽量不在容器里额外装东西需要什么命令直接在外面用docker exec配合宿主机工具执行保持容器精简。检查容器里运行的进程docker exec 容器ID ps aux看容器监听的端口docker exec 容器ID netstat -tlnp如果容器里连netstat都没有也可以退出来用宿主机命令配合容器IP排查。4.5 日志与资源占用排错的两大利器容器输出到标准输出的日志Docker会统一捕获docker logs 容器ID持续跟踪新日志类似tail -fdocker logs -f 容器ID只看最后200行docker logs --tail 200 容器ID这些日志来自容器内主进程的stdout/stderr直接跑在后台的程序如果写文件不写stdoutdocker logs是看不到的需要进容器查看日志文件。查资源占用docker stats实时显示每个容器的CPU、内存、网络、磁盘I/O。我遇到过内存泄漏的应用就是靠docker stats发现容器内存一直涨才定位到问题的。5. 实战用Docker部署一个MySQL 8.0并跑通光说不练假把式。我挑MySQL 8.0来演示是因为它是数据类服务的典型代表涉及端口、数据卷、环境变量、重启策略、验证连接几个关键点一套流程跑通Docker的核心套路基本就掌握了一半。5.1 直接跑MySQL会遇到的坑第一次有人让我看为什么MySQL容器起不来了我拉日志一看[ERROR] [MY-010457] [Server] --initialize specified but the data directory has files in it.原因很简单前一次运行没有指定数据目录容器初始化后数据写在容器内部第二次启动又用了同一个容器IDDocker试图在已有数据的目录上重新初始化直接报错。这是没有数据持久化意识导致的第一个坑。第二个坑是密码设置。MySQL官方镜像强制要求设置环境变量MYSQL_ROOT_PASSWORD、MYSQL_ALLOW_EMPTY_PASSWORD和MYSQL_RANDOM_ROOT_PASSWORD三者之一否则初始化失败。具体行为在不同版本里稍有差别但无论如何建议始终显式指定MYSQL_ROOT_PASSWORD。第三个坑是编码。MySQL 8.0默认字符集是utf8mb4其实已经比老版本好很多。但你用客户端连上去执行一些中文写入操作如果乱码多半是连接层没指定charset不是容器配置问题。这一点在写程序连接串时尤其注意。5.2 一份完整的docker run命令逐段拆解用下面这条命令部署MySQL 8.0docker run -d \ --name mysql8 \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORDYourPassword0! \ -v mysql-data:/var/lib/mysql \ --restartalways \ mysql:8.0逐段解释-d后台运行不在终端占住屏幕。不加-d会以当前终端绑定方式运行CtrlC就会停掉容器。--name mysql8给容器命名之后所有操作都可以用mysql8代替容器ID。-p 3306:3306宿主机3306映射容器3306。如果本机3306已被占用把左边改成3307试试。-e MYSQL_ROOT_PASSWORDYourPassword0!初始化root用户的密码。密码里有特殊字符时注意shell转义问题建议密码里避免用到$和单引号。-v mysql-data:/var/lib/mysql创建命名卷mysql-data并挂载到MySQL的数据目录。这一步保证了容器删了数据还在。--restartalways容器异常退出或宿主机重启时Docker会自动拉起这个容器。生产环境的服务几乎都会加这个参数。mysql:8.0镜像名和标签tag。8.0不是指“8.0最新版”而是一个浮动的标签默认指向8.0系列的最新版本。需要精确定位时可以用mysql:8.0.36这种具体版本标签。跑起来后验证docker ps看到mysql8容器状态是Up就算启动成功。接着用宿主机上的mysql客户端或者任意GUI工具连接mysql -h127.0.0.1 -P3306 -uroot -p输入密码后能看到MySQL 8.0的命令行提示符。5.3 容器启动失败时先看日志而不是反复重启我见过不少人容器起不来就一遍遍docker restart日志一眼不看。正确做法是docker logs mysql8常见错误无非几类端口占用Host port 3306 is already in use。放弃映射或者换宿主机端口。数据目录不匹配挂载了空目录但指定了初始化参数或者挂载了非空目录导致初始化失败日志里会指出initialization errors。权限问题volume目录权限不对MySQL进程无法写入日志会提示Permission denied。日志就是容器的病历先看日志再看其他能省下一大半排查时间。5.4 更进一步用docker compose管理一套环境单容器用docker run没问题但服务多了以后依赖关系、网络配置、环境变量、启动顺序都变复杂。这时候用Docker Compose用一个yaml文件描述整套服务。下面是一个MySQL Redis的compose文件示例version: 3.8 services: mysql: image: mysql:8.0 container_name: mysql8 ports: - 3306:3306 environment: MYSQL_ROOT_PASSWORD: YourPassword0! volumes: - mysql-data:/var/lib/mysql restart: always redis: image: redis:7 container_name: redis7 ports: - 6379:6379 restart: always volumes: mysql-data:启动docker compose up -d停止并删除docker compose downCompose的好处是配置可读、可版本管理同事之间共享一套环境定义文件比口头传命令靠谱太多。项目里的基础设施只要是小团队规模我都是优先用Compose描述。6. 入门之后必然遇到的几个问题排查清单Docker本身不算复杂但出问题的时候如果不懂原理会感觉无从下手。我把入门阶段最高频的几个问题整理出来分别说清原因和处理思路。6.1 docker权限错误Got permission denied while trying to connect刚装完Docker直接执行docker ps如果遇到Got permission denied while trying to connect to the Docker daemon socket说明当前用户没有访问Docker守护进程套接字的权限。原因Docker的客户端和守护进程通过/var/run/docker.sock通信这个套接字默认属于root用户和docker组。两种解决办法临时用sudo docker ps把用户加入docker组sudo usermod -aG docker $USER newgrp docker重新登录终端后docker命令就能直接执行。注意把普通用户加入docker组等同于授予了root级别的权限因为docker组用户可以通过挂载宿主机目录等方式操作宿主机文件。个人开发机无所谓共享的服务器上要谨慎管理加入docker组的用户名单。6.2 Docker Desktop一直startingWindows环境常见Linux下如果服务一直启动不了多半另有原因。Docker Desktop长期卡在starting优先排查路径确认WSL2可用PowerShell里执行wsl --status如果显示WSL未安装或内核过期执行wsl --update。检查Windows下的“虚拟机平台”功能有没有被第三方软件误关。进入Docker Desktop的Troubleshoot选项卡点击Restart。如果一直失败查看日志%LOCALAPPDATA%\Docker\log\。一个很隐蔽的原因装了旧版本的WSL内核Windows更新后没有同步更新WSL。在管理员PowerShell执行wsl --shutdown然后重新启动Docker Desktop能解决不少问题。6.3 镜像下载慢或者一直超时配置了加速器但还慢先验证加速器是否生效docker info看Registry Mirrors字段是否列出了你配置的加速地址。如果没生效检查daemon.json格式是否正确、路径是否对Linux是/etc/docker/daemon.json不要放错位置、修改后有没有重启Docker。如果最终还是要直连Docker Hub可以明显感受到文件分层的下载进度条不过耐心一点也还能用。说白了镜像加速这件事不同时段、不同网络环境差异很大多备几个源没有坏处。另外一些大型基础镜像比如node:20、python:3.12有alpine、slim这类精简变体体积会小很多网络条件不好的时候尽量选这类标签。6.4 磁盘占用越来越大怎么清理Docker用久了磁盘蹭蹭往上涨。常见占用来源是镜像含悬空镜像、停止的容器、构建缓存、日志文件。先看整体占用docker system df会列出IMAGES、CONTAINERS、LOCAL VOLUMES、BUILD CACHE各自的占用情况。清理未使用的资源docker system prune加上-a清理所有未使用的镜像docker system prune -a这个命令非常激进会删除所有没有被运行中容器引用的镜像和缓存恢复能力很低执行前确认环境确实不需要这些内容。容器日志也可能悄悄占满磁盘。Docker默认的日志驱动不会自动轮转日志文件会一直增长。对日志有要求的服务需要配置json-file的max-size和max-file或者接管到外部日志系统。6.5 技术求助的正确姿势遇到Docker问题搜索引擎上能找到大量答案但直接复制粘贴往往不能解决。正确做法是把下面四样东西放到一起搜索你执行的那条命令完整的报错信息Docker和操作系统的版本你正在使用的镜像名称。比如“docker run mysql 8.0 Host port is already in use”这样的组合能精准定位到端口冲突问题。Stack Overflow上按时间排序看答案比按赞同数排序更靠谱因为Docker发展快老答案很可能已经过时。Docker官方文档docs.docker.com里的reference章节质量很高每个子命令的参数都列得很清楚。遇到不确定的参数直接查reference比到处搜博客更准确。用Docker这几年我最大的感受是这东西的上手曲线被很多人高估了。它不要求你具备多深的底层知识核心概念就那么几个常用命令不超过二十个。真正需要花时间理解的不是命令本身而是容器与宿主机的边界在哪里、数据放在哪里、出了问题到哪里看日志。这几个问题想明白了Docker对你来说就不再是一个“会跑但不懂”的黑盒。最后分享一个很多人容易忽略的小技巧在Dockerfile里每一层指令都会产生一层中间镜像缓存调整Dockerfile中指令顺序会极大影响构建速度。把不常变的依赖安装放在前面把频繁改动的源码COPY放在后面能充分利用缓存让每次构建只编译真正变化的层。这条经验在你写完第一个Dockerfile之后立刻就能用上。如果你照着上面的过程装好了Docker并且成功跑起来一个MySQL容器恭喜你Docker的核心流程你基本已经掌握了。接下来可以拿自己手头的项目试试写Dockerfile从最简单的Python或Node应用开始把它容器化跑起来整个过程会比你想象的快很多。