搞Docker这几年,踩过的坑比吃过的盐还多。尤其是帮同事和群友排查问题的时候,发现大家问来问去翻来覆去就是那几类:装不上、起不来、网络不通、权限报错。这玩意儿上手其实不难,难的是你遇到的每一个报错都像一个“盲盒”,不开你不知道里面装的是什么。这篇我不打算写成一个面面俱到的官方文档,而是把日常用得最多、问得最狠的点串起来讲一遍,从安装到排障,从单容器到多容器编排,从MySQL、Redis到微服务部署,尽量把每一步背后的“为什么”也讲清楚。内容可能会有点长,但你按着走一遍,应该能少走很多弯路。
1. 先搞清楚Docker到底解决什么问题
1.1 用生活类比理解容器和镜像
很多人学Docker第一反应是“虚拟机”。这个类比能帮你理解大概方向,但容易把你带沟里去。虚拟机是模拟了一整台电脑,CPU、内存、硬盘、网卡全部虚拟出来,里面跑一个完整的操作系统。Docker不一样,它直接共享宿主机的操作系统内核,只是把应用程序及其依赖环境打包成一个“标准化集装箱”。
我常用一个类比:镜像就是“安装包”加“系统快照”的合体,你用它创建出来的容器就是跑起来的“实例”。镜像就像做蛋糕的模具,容器就是脱模出来的一块块蛋糕。模具可以反复使用,蛋糕吃掉了重新做一个,模具本身不会变。你改了容器里的东西,比如装了个软件、改了配置文件,镜像并不会跟着变,除非你专门commit一个新镜像。
这里有个新手最容易忽略的点:容器是“无状态”的,一删所有写入的文件全没了。所以凡是不能丢的数据,比如MySQL的数据文件、Redis的持久化文件、应用的日志,都必须通过挂载或卷的方式映射到宿主机上。很多人在容器里装完东西没搞两天被清了,就是没搞明白这个逻辑。
1.2 什么场景适合用Docker,什么场景不适合
Docker最适合的是“环境敏感型”应用。比如你本地跑Python 3.11,线上是3.7,一运行就崩;或者你同时要给两三个项目提供不同版本的Node、Java,用Docker可以一键起环境,隔离得干干净净。反过来,凡是需要高性能I/O裸设备操作、或者对内核模块有强依赖的场景,比如数据库集群、GPU直通、高性能计算,直接裸机可能更稳,硬上Docker反而增加排障难度。
还有一个场景是“快速体验”。比如你想试试某个开源项目,又不想把宿主机搞脏,直接docker run拉一个镜像跑起来,用完就删,干净利落。我在文章后面会专门讲几个这样的实战案例,比如用Docker装MySQL 8.0、搭Redis主从、部署GitLab,全部都是这个思路。
2. 不同平台的安装与首启排障
2.1 Windows上装Docker Desktop:WSL2与虚拟化前置检查
Windows装Docker,官方推荐的就是Docker Desktop。但很多人在这一步就卡住了,最常见的报错就是那句经典英文:
virtualization support not detected Docker Desktop failed to start because virtualization support is not enabled这句报错的字面意思是“检测不到虚拟化支持”。Docker Desktop在Windows上依赖两种后端之一:Hyper-V或者WSL2,而这两种都需要CPU的虚拟化功能(VT-x/AMD-V)在BIOS/UEFI中处于开启状态。所以遇到这个报错,第一件事不是重新安装,而是检查虚拟化开关。
检查路径按顺序来:
- 打开任务管理器,切到“性能”选项卡,看右下角“虚拟化”字段是不是“已启用”。
- 如果显示“已禁用”,重启电脑进BIOS(开机狂按Del或F2,各品牌按键不同),在CPU Configuration或Advanced下找到Intel Virtualization Technology / SVM Mode,设为Enabled。
- 打开控制面板,“启用或关闭Windows功能”,确保“虚拟机平台”和“适用于Linux的Windows子系统”两个选项被勾选。
如果用的老电脑不支持虚拟化,那就只能换Docker Toolbox这类老方案,但说实话体验很差,不如直接换Linux环境学习。
还有一个小坑经常被忽略:WSL2要求Windows 10 21H2以上,内核版本要够新才稳。如果docker desktop安装后反复要求更新WSL内核,去微软官网下最新的wsl_update_x64.msi手动装一遍即可。装完之后建议在PowerShell里跑一次wsl --set-default-version 2,让分发版跑在WSL2模式,性能比WSL1好太多。
2.2 Ubuntu和CentOS上装Docker引擎
Linux下安装相对清爽。Ubuntu这边我用的是官方apt源的方式:
sudo apt update sudo apt install -y ca-certificates curl gnupg sudo install -m 0755 -d /etc/apt/keyrings curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo $VERSION_CODENAME) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null sudo apt update sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin这里解释一下为什么每一条命令都值得认真写:gpg密钥和sources.list是两个独立的信任环节,不装keyrings直接添加源,apt会拒绝校验,装一半报错率极高。而docker-compose-plugin这个包是Compose v2的官方插件形式,以后写Compose编排少一个单独安装的麻烦。
CentOS 7 / 8那边稍微不一样,老系统还需要额外配置yum源。有些系统的默认源里没有docker-ce包,需要先装yum-utils,再用yum-config-manager --add-repo添加官方仓库。CentOS 7还有一个额外的坑:默认的iptables规则会和Docker的NAT规则冲突,偶尔会出现容器能起但网络不通的情况,解决办法通常是在/etc/sysconfig/docker里追加OPTIONS="--iptables=false",但副作用是所有端口映射都要自己手工处理,不推荐新手折腾,能升CentOS 8或换Ubuntu就换。
装完顺手把当前用户加进docker组,省得每条命令都要sudo:
sudo usermod -aG docker $USER newgrp docker注意退出重登一下才生效。
2.3 Docker服务启动失败的几种典型情况
Linux下启动Docker服务报错,很多都不是Docker本身的问题,而是cgroup驱动跟系统初始化系统不对付。最典型的一个报错是:
Failed to start docker.service: Unit docker.service not found.这个大概率是你安装的发行版仓库里根本没有docker-ce,或者安装不完整。本质上不是“启动失败”,而是“服务文件不存在”。优先检查apt list --installed | grep docker或rpm -qa | grep docker,确认docker-ce这个包在不在。没在,老老实实重新按2.2的步骤装一遍。
另一种很常见的情况是服务启动时报apply_credentials security_opt fails,或者failed to create NAT chain DOCKER。前者多半是系统开启了SELinux,与容器网络命名空间冲突,在/etc/selinux/config里把SELINUX改成permissive,或者干脆关掉;后者则是iptables规则被某个风控软件清掉导致DOCKER链丢失,重启dockerd或者手动iptables -t nat -F后systemctl restart docker能恢复。
Windows下还有那种failed to connect to the docker api at npipe:////./pipe/dockerdesktoplinuxen的报错,我跟你说,十有八九不是Docker坏了,而是Docker Desktop引擎根本没起来。点开右下角鲸鱼图标,等它变成稳定状态再说,命令连上去是真的需要引擎在跑。
3. 镜像加速配置与容器生命周期管理
3.1 镜像源为什么要配置,怎么配置
很多人在拉镜像的时候发现下载特别慢,甚至直接超时。这个问题的根源是官方镜像仓库Docker Hub的服务器在国外,默认情况下你拉一个几GB的镜像可能要等半小时甚至更久。解决思路就是给dockerd配置镜像加速器,本质上就是让它在拉镜像时走一条更快的路。
在Linux上,改这个文件:
sudo mkdir -p /etc/docker sudo tee /etc/docker/daemon.json <<-'EOF' { "registry-mirrors": ["https://docker.m.daocloud.io"] } EOF sudo systemctl daemon-reload sudo systemctl restart docker然后验证一下生效:
docker info | grep -A 5 "Registry Mirrors"补充说明一点:这个配置文件还有一个意外的好处,就是统一放>{ "registry-mirrors": ["https://docker.m.daocloud.io"], "data-root": "/data/docker", "log-driver": "json-file", "log-opts": { "max-size": "50m", "max-file": "3" } }
Docker Desktop的在图形界面配置路径是Settings -> Docker Engine,直接改里面的JSON就行。改完点Apply & Restart。
3.2 从拉镜像到跑容器的完整流程
下面用一个最简单的Nginx来演示完整流程。这串命令我几乎每天都会敲:
docker pull nginx:alpine docker run -d --name my-nginx -p 8080:80 nginx:alpine这里有两个细节值得解释。
-p 8080:80的含义是“宿主机的8080端口转发到容器内部的80端口”。8080可以随便改,但80是Nginx默认监听端口,改不了。访问http://localhost:8080能看到页面,说明容器正常跑起来了。
-d表示后台运行,不加的话,你的终端会直接挂在前台,Ctrl+C一按容器就停了。调试阶段可以不加-d,看日志方便,确认没问题再改成后台模式。
然后看看容器状态和日志:
docker ps -a docker logs my-nginxdocker ps -a和docker ps的区别,-a连已经停止的容器也列出来。新手经常遇到“我明明创建了容器但docker ps看不到”,就是漏了-a。
3.3 常用命令速查与容器日志查看
我整理了一张小表,按使用频率排的,背熟这张表基本能应付日常90%的操作:
| 命令 | 作用 | 备注 |
|---|---|---|
docker ps -a | 列出所有容器(含退出) | 不加-a只看运行中的 |
docker images | 列出本地镜像 | 不带仓库名就是全部 |
docker pull xxx | 拉取镜像 | 等价于 docker image pull |
docker run -d --name xxx -p 宿主机端口:容器端口 镜像 | 创建并启动容器 | -d后台、-it交互 |
docker exec -it xxx bash | 进入运行中容器的shell | 前提是容器里有bash |
docker logs -f xxx | 跟踪容器日志 | -f是follow模式 |
docker stop/start/restart xxx | 停止/启动/重启容器 | 容器id或name均可 |
docker rm -f xxx | 强制删除容器 | 正在运行的容器也能删掉 |
docker rmi xxx | 删除镜像 | 有容器引用时会报错 |
docker inspect xxx | 查看容器详细信息 | 网络、挂载、entrypoint都在这 |
docker logs -f这个命令我看好多人不会用。容器里如果跑的是Java等常驻进程,日志全打到stdout和stderr,不用进容器翻文件,直接在宿主机上就能看。这一点比传统进程管理舒服太多。
4. 实战:那些高频使用的容器化部署方案
4.1 MySQL 8.0:数据目录与初始化密码
MySQL在Docker里跑容易踩的坑挺有代表性的,值得单独拆开讲。先看最基础的一条命令:
docker run -d --name mysql8 \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORD=yourpassword \ -e MYSQL_DATABASE=testdb \ -v /data/mysql:/var/lib/mysql \ mysql:8.0关键环境变量是MYSQL_ROOT_PASSWORD,容器初始化时会自动设置root密码,这个密码在首次启动时生效。这里有个时间差问题:容器刚start的时候MySQL还没有初始化完成,如果你马上用客户端去连,大概率会报“Access denied”或者“Can't connect”,不是密码错,而是服务还没真正就绪。常见的做法是等待几秒,或者用docker logs mysql8 | grep "ready for connections"来确认初始化完毕。
音量挂载那一块要格外重视,-v /data/mysql:/var/lib/mysql的意思是宿主机上/data/mysql目录与容器内/var/lib/mysql目录共享,MySQL数据文件会落在宿主机上。容器一删数据还在,下次再跑一个新的mysql8容器指定同一个目录,数据自动恢复。这是Docker持久化的基本逻辑,不这么干的话,容器删了数据就没了,等于白跑。
还有一个我反复跟人强调的坑:MySQL 8.0的认证插件。8.0默认用的是caching_sha2_password,一些旧版本的客户端工具(比如Navicat 15以下)连不上,报错Authentication plugin 'caching_sha2_password' cannot be loaded。解决办法是启动时追加:
--default-authentication-plugin=mysql_native_password或者在容器里执行ALTER USER语句改认证方式,个人建议用启动参数,简单省事,升级镜像后配置也不会丢。
4.2 Redis:从单机到主从
Redis用Docker跑起来比MySQL简单多了,但做主从复制时要小心一个坑:容器内的Redis默认监听的是127.0.0.1,让它对外提供服务的核心参数是--bind 0.0.0.0和--appendonly yes。
先看单机版:
docker run -d --name redis \ -p 6379:6379 \ -v /data/redis:/data \ redis:7.0 --appendonly yes注意命令行的--appendonly yes是传给Redis进程的参数,不是Docker参数,位置要放在镜像名后面。Redis官方镜像的entrypoint会把镜像名后面的内容传给redis-server。很多人把参数写在-p前面,结果Docker完全不认识,直接报“unknown flag”。
主从模式下,主库命令不变,从库加一个--replicaof参数:
docker run -d --name redis-slave \ -p 6380:6379 \ --link redis:master \ redis:7.0 --replicaof master 6379不过--link是老式做法的残留,现在更推荐用自定义网络来保证容器间通信。我会在后面的网络章节专门讲。Redis主从模式下如果发现从库同步一直在重连,多半是master容器没有绑定0.0.0.0,从库连不上它的6379端口。用redis-cli进从库执行info replication看一下master_link_status就知道问题在哪。
4.3 GitLab社区版和其他“偏门”部署
GitLab社区版是我用Docker部署最重的应用之一。官方推荐用gitlab-omnibus镜像,一条命令就能拉全套,硬件配置要求不高但吃内存,2GB内存起步跑起来才算流畅。
docker run -d --name gitlab \ -p 8081:80 \ -p 8022:22 \ -v /data/gitlab/config:/etc/gitlab \ -v /data/gitlab/logs:/var/log/gitlab \ -v /data/gitlab/data:/var/opt/gitlab \ gitlab/gitlab-ce:latest启动之后用docker logs -f gitlab看进度,第一次启动要等个三五分钟,等出现gitlab Reconfigured!字样再去浏览器访问。这里有个非常容易踩的坑:如果你把80端口映射到别的端口,比如8081,GitLab默认生成的仓库clone链接还是http://IP/group/repo.git,不会自动带上端口。需要在/etc/gitlab/gitlab.rb里手动改external_url:
external_url 'http://IP:8081'改完重启容器让gitlab-ctl reconfigure生效。
再看几个偏门但有实际应用场景的。比如用KodBox做个人网盘,一条命令:
docker run -d --name kodbox \ -p 8088:80 \ -v /data/kodbox:/var/www/html \ kodbox/kodboxMetabase这种BI工具,数据分析内部用挺好使:
docker run -d --name metabase \ -p 3000:3000 \ -e MB_DB_TYPE=postgres \ -e MB_DB_DBNAME=metabase \ -e MB_DB_USER=xxx \ -e MB_DB_PASS=xxx \ metabase/metabaseMediamtx做流媒体转发,很多智能家居和监控项目里会用到,用Docker跑非常干净:
docker run -d --name mediamtx \ -p 8554:8554 \ -p 1935:1935 \ -p 8888:8888 \ bluenviron/mediamtx这类工具的共同特点是对宿主机污染极小,删掉容器就像没装过一样,环境变量、配置、数据全部通过参数和挂载管理,比直接装二进制在系统上干净太多。
4.4 用Docker跑Python环境的轻量方案
这个场景在日常开发里比你想的更常见。项目A要Python 3.9,项目B要3.11,直接装系统全局必然打架。用Docker可以做到互不干扰:
docker run -it --rm \ -v $(pwd):/workspace \ -w /workspace \ python:3.11-slim bash这条命令会拉一个精简版Python 3.11镜像,把当前目录挂载到容器内的/workspace,并预先把工作目录切过去。在容器里执行python --version就能看到3.11版本,跟宿主机环境完全隔离。
如果项目依赖比较复杂,建议直接用Dockerfile把依赖打包进镜像:
FROM python:3.11-slim WORKDIR /app COPY requirements.txt . RUN pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple COPY . . CMD ["python", "app.py"]构建命令是docker build -t my-python-app .,跑起来docker run -d --name my-app my-python-app。这种方式的好处是镜像里已经固定了依赖版本,拿到任何一台装了Docker的机器都能一键跑,彻底告别“在我电脑上是好的”这种问题。
5. 网络与权限:新手最容易卡住的环节
5.1 端口映射与容器间通信
Docker容器之间的通信,我建议所有新手都直接养成用自定义桥接网络的习惯,别用--link。Docker内置了三种网络模式:bridge(默认)、host、none。你手动创建的网络也属于bridge,但它自带了DNS解析能力,容器之间可以通过容器名直接互访,这一点是默认bridge网络做不到的。
创建网络的命令:
docker network create app-net跑容器的时候指定网络:
docker run -d --name mysql8 --network app-net -p 3306:3306 mysql:8.0 docker run -d --name backend --network app-net -p 8080:8080 my-backend这样backend容器里访问mysql8:3306就能直连数据库,不需要通过宿主机IP转发。如果走默认bridge网络,容器名解析会时不时失效,测试环境还好,生产环境你会被这个不稳定搞得怀疑人生。
5.2 网络不通的排查顺序
容器网络不通是我被问得最多的一类问题。有人直接说“Docker网络不通”,我通常会让对方按下面这个顺序排查:
- 容器能不能访问外网?
docker exec -it 容器名 ping baidu.com。不能,先看宿主机能不能上网,再看DNS配置,最后查iptables。 - 容器之间能不能互访?进入容器A ping容器B的容器名或IP。能通说明网络配置没问题,不通重点查自定义网络是否正常。
- 宿主机能不能访问容器端口?直接在宿主机上
curl localhost:映射端口。不通,检查映射端口是不是没写对,或者容器内应用实际监听的端口与你以为的不一致。 - 外部机器能不能访问?这一步往往是安全组、防火墙拦截,跟Docker本身关系不大。
顺便说一个特别隐蔽的坑:容器内应用监听地址是127.0.0.1。比如你在容器里跑了一个服务,它默认只监听容器自己的回环地址,宿主机转发过去的请求根本到不了它。排查方法是在容器里执行ss -tlnp看监听地址,如果都是127.0.0.1开头的,把应用配置改成0.0.0.0。
5.3 权限错误与挂载目录的坑
Docker权限错误最常见的就是这句:
Got permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock这个问题的根源是当前用户不在docker组里。不加sudo执行docker命令时,sock文件的权限校验就会失败。解决办法我在前面Linux安装章节已经给过:usermod -aG docker $USER然后重新登录。如果你不想重新登录,可以用newgrp docker让当前shell直接生效。
挂载目录的坑则更隐蔽。很多人在容器里看到挂载目录权限不对、文件不可写,报Permission denied。多数情况不是Docker的问题,而是挂载目录的属主和属组与容器内进程期望的不一致。比如MySQL容器内的mysql用户UID是999,你宿主机上/data/mysql的属主是root,容器内进程写不进去。解决办法是先把目录属主改成999:
chown -R 999:999 /data/mysql这一点在数据卷比较大的场景里特别重要,写进部署脚本里能避免很多次半夜被叫起来。
6. 进阶:Compose编排与微服务部署
6.1 Compose文件怎么写
单容器用docker run还能凑合,一旦涉及多容器关联,docker run敲得手酸,还容易写错参数。这种场景就该上Compose了。Compose的核心是YAML文件,下面用“后端+MySQL+Redis”的典型组合来举例:
version: '3.8' services: mysql: image: mysql:8.0 container_name: app-mysql restart: always environment: MYSQL_ROOT_PASSWORD: root123 MYSQL_DATABASE: appdb volumes: - /data/mysql:/var/lib/mysql ports: - "3306:3306" networks: - app-net redis: image: redis:7.0 container_name: app-redis restart: always command: ["--appendonly", "yes"] volumes: - /data/redis:/data networks: - app-net backend: image: my-backend:latest container_name: app-backend restart: always ports: - "8080:8080" depends_on: - mysql - redis networks: - app-net networks: app-net: driver: bridge启动方式一条命令:
docker compose up -d查看状态是docker compose ps,强制重建是docker compose up -d --build,整体拆除是docker compose down。down命令会清掉网络,但外面挂载的数据卷还在,这一点也算是个小优势,重建后再up数据也不会丢。
depends_on是个容易产生误解的配置。它只控制容器启动顺序,不保证依赖服务“准备好了”。MySQL容器打印“ready for connections”之前,backend容器可能已经在连数据库了,于是连不上。成熟的方案是在应用启动脚本里加等待逻辑,或者用类似wait-for-it.sh的工具,先把依赖服务的TCP端口探测通再往后走。
6.2 微服务项目部署的完整流程
微服务用Docker部署,本质上是把每个服务打成一个镜像,再用Compose把所有服务编排起来。我以Spring Boot为例讲一下常见套路,语言不是关键,思路通用。
流程一般是:每个服务模块下写好Dockerfile,然后通过CI或本地构建生成镜像,推到私有仓库或者直接传tar包到服务器,最后在服务器上docker compose up -d。
我见过很多刚接触微服务部署的同学卡在“同一个项目拆成多个服务”这个环节上,总想把所有服务塞进一个容器里。这种操作完全违背了容器的设计理念。一个容器一个主进程,这才是Docker的哲学。如果后端、数据库、Redis全塞一起,你根本没法独立升级、独立扩容,还谈什么微服务。
正确的做法是每个服务独立镜像、独立容器,通过Compose或Kubernetes编排。服务的配置信息通过环境变量注入,比如数据库连接串、Redis地址,全部写进Compose文件的environment里,这样容器在任何环境都跑得起来,只要你换环境变量就行。配置抽离这一步做好了,微服务部署就顺了。
6.3 Java项目从IDEA打包镜像
如果你主力是IntelliJ IDEA,可以下载Docker插件,直接在IDE里右键Dockerfile点击构建,完事能一键push到仓库。命令行的方法更基本,适合任何编辑器:
mvn clean package -DskipTests docker build -t my-service:1.0.0 . docker save my-service:1.0.0 | gzip > my-service.tar.gz最后得到的tar包拷贝到服务器上:
docker load < my-service.tar.gz docker compose up -d这里要提醒一点:Dockerfile里不要把target目录整个COPY进去,那里面有很多上一次构建的旧class和临时文件,既拖慢构建又容易把脏东西带进镜像。正确姿势是用.dockerignore排除掉target目录、.git目录这类无关文件:
target/ .git/ .idea/ *.log Dockerfile这样构建时上下文体积能缩小好几个数量级,特别是在网络上下文中构建时,镜像推送和拉取都能快很多。
6.4 用Docker跑一些开发测试工具的实战
开发测试和教学场景里Docker也特别好使。很多漏洞靶场和安全测试工具都提供了现成镜像,拉起即用,宿主机不会留任何痕迹。比如DVWA靶场,跑起来就是一条命令:
docker run -d --name dvwa \ -p 8085:80 \ vulnerables/web-dvwa等容器启动后浏览器访问http://localhost:8085,它会展示出经典的PHP+SQL环境,用来学习常见的Web漏洞和防御手段非常合适。用完docker stop dvwa && docker rm dvwa,宿主机上干干净净。
同样思路也适合跑机器人平台和边缘设备的开发环境。比如ROS2 Humble的开发镜像,配合micro-ROS agent用作设备间通信调试,这比在宿主机上手工编译ROS2环境省事得多。容器内跑Ubuntu + ROS2,容器外通过共享网络与外设通信,整套开发环境可以在几台设备间快速复制。
这类“用完即走”的容器化方式,是我个人最喜欢的Docker用法之一。你不用为了一个测试需求给宿主机装一堆依赖库,也不用担心测试环境的脏数据污染正式环境。Docker最核心的价值就是这个“环境隔离”能力。
7. 常见问题速查表与整体经验小结
我把高频问题整理成一张速查表,方便收藏使用。遇到问题先查表,表中没有的场景再用docker inspect分析,能省大量时间。
| 现象 | 可能原因 | 解决思路 |
|---|---|---|
| Docker Desktop启动失败,提示virtualization support not detected | BIOS虚拟化未开启或Windows功能未启用 | BIOS打开VT-x/AMD-V,勾选虚拟机平台和WSL2 |
| failed to connect to the docker api at npipe:////./pipe/dockerdesktoplinuxen | Docker Desktop引擎未启动或重启不完整 | 等待鲸鱼图标变稳定后重试,或右键重启 |
| Got permission denied connecting to docker.sock | 当前用户不在docker组 | usermod -aG docker $USER,重新登录 |
| Nginx/其他容器能启动但访问不了 | 端口映射错、容器内监听地址为127.0.0.1、宿主机防火墙拦截 | 依次检查docker ps端口、容器内ss -tlnp、systemctl status firewalld |
| MySQL容器初始化失败 | 数据目录权限不对或已有数据版本冲突 | chown -R 999:999 数据目录,避免新旧版本混跑 |
| Redis主从不同步 | 主库没绑0.0.0.0,或从库无法解析主库主机名 | 主库加--bind 0.0.0.0,主从放同一网络用容器名互访 |
| 镜像拉取超时/极慢 | 网络到Docker Hub不稳定 | 配置registry-mirrors,或者预下载镜像tar包离线导入 |
| 容器重启后数据丢失 | 没有挂载数据卷 | 把持久化目录通过-v映射到宿主机 |
| Docker启动时iptables报错 | 宿主机的iptables规则被外部工具清掉,或与SELinux冲突 | iptables -t nat -F后重启docker;调整SELinux策略 |
最后再分享一个小技巧,是我最近特别喜欢用的:排查任何容器问题前,先查三个地方——状态、日志、环境变量。docker ps -a看状态,docker logs --tail 100 容器名看最近日志,docker inspect 容器名 | grep -A 5 Environment看配置。90%的问题在这三步之内就能定位。不要一上来就想着重装Docker或重建容器,先看日志,多花一分钟,能省半小时。
还有一点尤其想对新手说:Docker的命令背得再熟,也不如理解它背后的逻辑来得重要。镜像和容器的关系、数据卷的持久化、网络模式的差异,这三件事想明白,哪怕命令忘了,查一下帮助就能用起来。反过来只记命令不理解原理,换个场景就抓瞎。我自己带过不少新人,凡是在这三件事上肯花时间的,后面上手都特别快。