1. 为什么我最终选择了Docker来搞定所有部署
先说说我的真实经历。上个月帮朋友把一套已经跑了三年的Python项目部署到新服务器,第一件事就是装MySQL 8.0,然后发现系统自带的MySQL是5.7,数据迁移、配置文件、字符集……每一项都在制造麻烦。我当场决定,所有组件全部用Docker容器化部署重新来一遍。这个决定让原本要折腾两天的环境问题,压缩到一下午就全部解决。
如果你正在被版本冲突、环境依赖、跨机器部署这些事反复折磨,这篇手册就是写给你的。我会从Windows上最容易卡住的Docker Desktop安装开始,讲到MySQL 8.0、Redis主从这些真实业务里高频使用的容器部署方式,再到Docker Compose把整套环境固化成一份YAML文件,最后把容器网络不通这一类玄学问题拆开揉碎。内容尽量按我实际操作时踩过坑的顺序来写,不是官方文档的翻译。
1.1 从一次MySQL环境升级引发的连锁反应
那次升级的痛点很典型:服务器上已经装了MySQL 5.7,但项目代码里用到了MySQL 8.0才支持的窗口函数。直接卸载重装风险太大,因为系统里还有其他老应用依赖5.7的客户端lib。用Docker之后,我拉一个mysql:8.0镜像,映射3307端口启动新实例,老实例继续跑在3306,两边完全隔离。这样的场景在业务部署中太常见了,Docker最直接的价值就是让"不同的服务依赖不同的运行环境"变成一件不需要纠结的事。
这也是我写"完全手册"的出发点:不是教你把Docker当成一个普通的软件装上,而是让你理解它如何改变部署的思维方式。拿MySQL这件事来说,如果没有Docker,你需要考虑操作系统的包管理器、安装源、存储路径、权限、配置文件位置、开机自启脚本……每一样都可能因为系统版本不同而千差万别。而Docker镜像把MySQL、Redis、Nginx这些软件连同它们需要的运行时环境一起打包,你只需要关心暴露哪个端口、挂载哪个目录、启动参数怎么传。
1.2 Docker容器化部署到底改变了什么
我习惯用一个比喻来理解Docker:它就像集装箱运输出现之前的码头。以前的码头工人需要逐个处理形状各异的货物,而现在无论里面装的是衣服还是机器零件,外面都是统一尺寸的集装箱,吊车、卡车、轮船都能无缝对接。Docker镜像就是这个集装箱,容器就是正在运输途中的货物状态。你不用担心这箱货里面装的是Ubuntu还是CentOS、是MySQL还是Redis,因为装箱的那一刻,里面的一切已经被验证过是能正常工作的。
具体到日常部署场景,容器化带来的改变有三个层面。第一,可移植性:我在Mac上构建好的镜像,到Linux服务器、Windows服务器上都能跑,只要对方有Docker引擎。第二,隔离性:多个应用共享一台服务器时,它们各自有独立的文件系统、网络栈和进程空间,一个应用崩溃不会拖垮另一个。第三,可重复性:以前部署靠"运维手记",某一步忘了就被坑;现在部署靠镜像和Compose文件,只要文件在,随时能复现一套一模一样的环境。
1.3 读这篇手册你需要提前准备什么
我的建议是手里有一台Windows 10/11专业版或家庭版电脑,内存至少8GB,并且能访问Docker Hub拉取镜像。不需要任何Docker基础,但你需要会打开命令行,无论是CMD、PowerShell还是Windows Terminal。本手册涉及的命令都以bash和Windows命令行兼容为主,如果提示命令找不到,先检查Docker Desktop是否已经启动。
另外特别提醒一点:不要在生产环境里用我们演示用的"快速启动"命令,那些命令为了方便会省略很多安全配置。读完全文后,你应该能够自己写出带数据卷、带网络隔离、带资源限制的完整部署方案。
2. Windows上安装Docker Desktop:最容易卡住的那一步
如果你在Windows上安装过Docker Desktop,大概率见过这样一个报错:virtualization support not detected docker desktop failed to start because v(后面通常还有半句话,比如"virtualization is disabled in BIOS")。我第一次装的时候也卡在这里,一度以为是Docker Desktop版本问题,折腾了三个小时后才发现,罪魁祸首是BIOS里的虚拟化开关没打开。这一节我就把整个过程拆开讲,照着做,十分钟内能启动。
2.1 先确认虚拟化支持是否打开:Virtualization support not detected
Docker Desktop在Windows上运行容器,本质上依赖底层的虚拟化能力。无论你选择WSL2模式还是Hyper-V模式,前提都是CPU的虚拟化功能已开启。Intel的VT-x,AMD的SVM,名字不同,作用相同。Windows 10以上一般情况下会在任务管理器的"性能"选项卡底部显示"虚拟化:已启用/已禁用"。如果显示禁用,那就需要进入BIOS开启。
不同主板的BIOS入口不一样,开机时按Del、F2、F10都有可能。安全推荐的做法是:重启系统,在开机画面出现时根据屏幕提示进入UEFI/BIOS设置,然后在"Advanced"或"CPU Configuration"里找到Intel Virtualization Technology或SVM Mode,把它设为Enabled。保存退出后重新进入Windows,再打开任务管理器确认虚拟化已经变成"已启用"。
这里有一个很多人不知道的点:即使Windows系统里看不到虚拟化状态,也不代表一定没开启。某些电脑厂商默认关闭虚拟化,却在系统里装了很多虚拟机软件,导致Docker Desktop启动时报"virtualization support not detected"但其实Hyper-V又在运行。所以最稳妥的办法是先把WSL2的状态理顺,再决定Docker用哪种后端。
2.2 推荐安装路径:WSL2模式与Hyper-V模式如何选
现在的Docker Desktop默认推荐WSL2后端,因为它比Hyper-V模式启动更快、内存占用更可控、文件共享也更顺畅。WSL2是"适用于Linux的Windows子系统"的第二代,它是一个轻量级虚拟机,但和普通的Hyper-V虚拟机不一样,它能在几秒内启动,和Windows文件系统可以双向访问。我的建议是:只要能安装WSL2的Windows 10/11版本,一律用WSL2模式。
安装过程其实非常简单,我简化成三步。第一步,以管理员身份打开PowerShell,执行:
wsl --install这条命令会启用所需的Windows功能,并安装默认的Ubuntu发行版。如果该命令报错,说明你的系统版本太旧,需要手动开启"虚拟机平台"和"适用于Linux的Windows子系统"两个Windows功能,然后重启。
第二步,重新启动后,在PowerShell里运行wsl --set-default-version 2,确保WSL默认使用第二代架构。
第三步,下载Docker Desktop安装包,直接双击安装,安装过程中它会自动检测WSL2并询问是否启用。如果一切正常,安装完成后桌面会出现Docker Desktop图标,第一次启动会要求你接受服务协议,然后它会在几秒钟内启动引擎。
2.3 启动反复失败的排查顺序与修复命令
有时候你发现虚拟化也开了、WSL2也装了,Docker Desktop图标还是转圈后弹出错误。这时别急着重装,按这个顺序排查。
先用wsl --status查看WSL的版本状态,确认默认版本是2。如果输出显示的是WSL 1,执行wsl --set-default-version 2,再把已安装的发行版迁移过去。
然后查看Windows功能状态,用PowerShell执行:
Get-WindowsOptionalFeature -Online -FeatureName Microsoft-Hyper-V-All Get-WindowsOptionalFeature -Online -FeatureName VirtualMachinePlatform如果VirtualMachinePlatform显示State : Disabled,需要启用:
Enable-WindowsOptionalFeature -Online -FeatureName VirtualMachinePlatform -NoRestart之后重启电脑,再启动Docker Desktop。
还有一类启动失败是因为Windows的"容器"功能没装好。管理员PowerShell执行:
Enable-WindowsOptionalFeature -Online -FeatureName Containers -All如果以上都检查正常,Docker Desktop仍然失败,你可以打开日志:Docker Desktop界面点右上角问题反馈,查看C:\Users\你的用户名\AppData\Local\Docker\log里的host和engine日志。最常出现的WSL相关错误,通常是WSL2内核太旧,执行一次wsl --update就能解决。
3. Docker日常使用的"第一性原理":镜像、容器、数据卷
装好Docker后,真正开始用之前,必须搞懂三个核心概念。我在教学时发现,很多人记不住命令不是记性问题,而是没有理解这三个东西的关系。搞懂了它们,Docker的命令就不再是一堆需要死背的参数表。
3.1 镜像与容器:用一个跑步比喻讲清楚
把镜像理解成"模板"或"类",把容器理解成"模板运行出来的实例"。我写Java的时候觉得这比喻最贴切:镜像就像Java里的Class定义,容器就像new出来的Object对象。同一个Class可以new出无数个对象,同一个镜像可以启动无数个容器。对象和对象之间互不影响,容器和容器之间同样互相隔离。
实际操作中,镜像可以被共享、被推送、被版本管理。docker pull mysql:8.0拉下来的是一个静态的只读模板;docker run mysql:8.0就是基于这个模板启动一个容器进程。你可以在这个容器里面写文件、改配置、安装临时工具,但这些改动不会影响原始镜像。如果这个容器被删除,改动也就没了。想要保留改动,就要用到数据卷,或者通过Dockerfile构建新的镜像。
3.2 最常用的容器操作命令手册
我认为新手最需要记的容器操作命令就十几条,按场景分类,你不需要会写复杂的Shell脚本,只要会用下面这些就够了。
拉取镜像:
docker pull nginx:latest查看本地已有的镜像:
docker images启动一个交互式容器(常用于调试):
docker run -it --name debug-box ubuntu:22.04 bash后台守护容器(最常用):
docker run -d --name web-app -p 8080:80 nginx:latest查看正在运行的容器:
docker ps查看所有容器(包括已停止的):
docker ps -a查看容器日志:
docker logs -f web-app进入正在运行的容器内部:
docker exec -it web-app bash停止/启动/重启容器:
docker stop web-app docker start web-app docker restart web-app删除容器(必须先停止):
docker rm web-app如果你频繁创建容器,记住加上--rm参数在退出时自动清理:
docker run --rm -it ubuntu:22.04 bash这些命令里,我认为docker exec和docker logs是最值钱的调试工具。遇到容器起不来,先看日志;日志看不出问题,再exec进容器内部看状态,比在宿主机上瞎猜效率高得多。
3.3 数据卷:不配置就等着数据丢失
这是新手最容易忽略的一个点。容器删除后,容器内的所有非镜像文件都会消失。如果你的MySQL数据写在容器内部目录/var/lib/mysql,容器一删,数据库就没了。解决办法是用数据卷。
数据卷有两种常用方式:命名卷和绑定挂载。命名卷由Docker管理,绑定挂载则把宿主机的某个目录直接映射到容器目录。我用一个例子说明:
docker run -d \ --name mysql8 \ -e MYSQL_ROOT_PASSWORD=123456 \ -v mysql-data:/var/lib/mysql \ mysql:8.0这里的mysql-data:/var/lib/mysql就是命名卷。卷名是mysql-data,挂载到容器的/var/lib/mysql目录。将来删除容器再重建,只要指定同样的卷名,数据还在。
绑定挂载更直观,适合开发和配置文件变更:
docker run -d \ --name nginx \ -v /home/user/nginx/html:/usr/share/nginx/html \ -p 80:80 \ nginx:latest这样你直接修改宿主机的/home/user/nginx/html里的文件,nginx容器立刻就能读到。我强烈建议:任何有状态的服务,比如MySQL、Redis、PostgreSQL,启动时必须配置数据卷,否则你就是在赌容器永远不会被误删。我见过不止一次,有人把生产数据库跑在容器里三个月,然后一次docker rm -f毁了全部数据。
4. 用Docker部署MySQL 8.0并完成生产级初始化
MySQL是容器化部署中最典型的例子,因为它有数据持久化、时区、字符集、远程权限、配置文件挂载这些必须考虑的细节。如果你能完整跑通一个生产级MySQL容器,Docker的大部分概念也就掌握了。
4.1 一条命令启动MySQL,以及必须追加的容器参数
最简单的启动命令长这样:
docker run -d \ --name mysql8 \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORD=YourStrongPassword \ mysql:8.0这样确实能跑起来,但你很快就会遇到几个问题:时区不对、中文乱码、容器一删数据全丢。所以我建议在任何真实项目里,启动命令都要加上数据卷挂载和时区配置:
docker run -d \ --name mysql8 \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORD=YourStrongPassword \ -e TZ=Asia/Shanghai \ -v mysql-data:/var/lib/mysql \ -v /path/to/my.cnf:/etc/mysql/conf.d/my.cnf \ mysql:8.0 \ --character-set-server=utf8mb4 \ --collation-server=utf8mb4_unicode_ci这里解释几个参数的重要性。-e TZ=Asia/Shanghai是设置容器时区,如果不设置,MySQL的SYSDATE()、NOW()会返回UTC时间,和你的业务日志对不上。-v mysql-data:/var/lib/mysql是数据持久化,前面已经强调过。-v /path/to/my.cnf:/etc/mysql/conf.d/my.cnf是挂载自定义配置文件,MySQL默认会读取这个目录下的配置文件。最后的--character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci是直接向mysqld进程传入启动参数,把默认字符集改成utf8mb4,这是避免中文乱码的关键。
4.2 时区、字符集、密码策略与远程连接的坑
MySQL 8.0和5.7有很多不一样的地方。首先是默认密码策略升级了,如果你指定一个类似123456的弱密码,即使MYSQL_ROOT_PASSWORD=123456也能启动,但客户端连接时可能报错提示密码太弱。不过这个问题通常在容器内部不生效,因为Docker初始化脚本在创建root用户时就会执行。为了安全,我还是建议用强密码。
其次是远程连接问题。MySQL容器默认只让root从localhost登录。你想从宿主机或者另一台服务器连进这个容器,必须进入容器修改root用户的host:
docker exec -it mysql8 mysql -uroot -p输入密码后执行:
ALTER USER 'root'@'%' IDENTIFIED WITH mysql_native_password BY 'YourStrongPassword'; FLUSH PRIVILEGES;注意MySQL 8.0默认的认证插件是caching_sha2_password,有些老客户端和它不兼容,连接时直接报Authentication plugin 'caching_sha2_password' cannot be loaded。上面这条SQL把认证方式改成mysql_native_password,可以解决绝大多数兼容性问题。
另一个坑是端口占用。如果宿主机已经装了MySQL占用3306端口,你需要把容器端口映射改掉,比如-p 3307:3306。这里的含义是把容器的3306端口映射到宿主机的3307端口,容器内部应用访问还是3306,但从外面连接要用3307。
4.3 实际部署中遇到的三个典型问题
我实际操作MySQL容器时遇到过三个高频问题,都是网上搜不到现成答案的。第一个是docker logs mysql8里不断提示mbind: Operation not permitted。这个警告其实是WSL2环境下内存分配机制的问题,只要容器能正常初始化,不影响使用,可以忽略。如果你实在想消除,可以把Docker Desktop的设置里"Resources"的内存调大,或者改用Linux宿主机部署。
第二个是重启容器后MySQL服务无法启动。常见原因是数据卷目录的权限被改掉了,比如你手滑在宿主机执行了chown -R root:root,导致容器内的mysql用户无法写入。解决办法是进入宿主机数据卷目录,把属主改回MySQL容器内的用户ID(通常UID是999),或者干脆删除本地数据卷重建,当然这只有开发环境才建议这么做。
第三个是我自己踩过的:忘记挂载时区配置,结果日志和数据库时间全都差了8个小时。当时排查了很久,发现容器里的/etc/localtime不是上海时区,后来用-e TZ=Asia/Shanghai重新创建容器解决。这里建议你把"时区"纳入部署检查清单,不要等出了问题再处理。
5. 在Docker里搭建Redis主从:一步步从单机到复制集群
Redis主从是缓存层高可用的基础。我在Docker里搭主从时踩的坑比MySQL还多,主要是容器间通信问题。这一节我会用最简单的方式,在一个自定义网络里启动一个Redis主节点和一个从节点,并验证数据复制。
5.1 先准备Redis配置文件:几个必须改的项
Redis镜像默认不带外部配置文件,你需要在宿主机准备好。我通常在项目目录下建一个redis/conf文件夹,里面放两份配置:redis-master.conf和redis-slave.conf。
主节点配置最少需要改这些:
bind 0.0.0.0 appendonly yes protected-mode no dir /databind 0.0.0.0是让Redis监听所有网络接口,否则容器外无法访问。appendonly yes开启AOF持久化,避免重启丢数据。protected-mode no是在容器环境下让从节点能正常连接主节点,生产环境下建议用防火墙来限制访问范围。
从节点配置比主节点多一行:
bind 0.0.0.0 appendonly yes protected-mode no dir /data replicaof redis-master 6379注意replicaof后面跟的是主节点在Docker网络里的主机名,而不是IP地址。这样即使容器重新创建导致IP变了,主从关系也不会断。这是Docker自定义网络比直接用宿主机IP更稳定的原因。
5.2 容器间相互通信的主从启动命令
先创建一个自定义网络,让容器之间用容器名互相访问:
docker network create redis-net启动主节点:
docker run -d \ --name redis-master \ --network redis-net \ -p 6379:6379 \ -v /path/to/redis/conf/redis-master.conf:/etc/redis/redis.conf \ -v /path/to/redis/data/redis-master:/data \ redis:7.0 \ redis-server /etc/redis/redis.conf启动从节点:
docker run -d \ --name redis-slave \ --network redis-net \ -p 6380:6379 \ -v /path/to/redis/conf/redis-slave.conf:/etc/redis/redis.conf \ -v /path/to/redis/data/redis-slave:/data \ redis:7.0 \ redis-server /etc/redis/redis.conf这里关键点是端口映射:宿主机的6380映射到从节点的6379,这样外部可以同时访问主从两个端口,而容器之间的复制流量走内部网络的6379端口,不受映射影响。
如果你不想提前准备配置文件,也可以用命令行参数直接启动。主节点:
docker run -d --name redis-master --network redis-net -p 6379:6379 redis:7.0 redis-server --appendonly yes从节点:
docker run -d --name redis-slave --network redis-net -p 6380:6379 redis:7.0 redis-server --replicaof redis-master 6379 --appendonly yes这种方式适合快速验证,生产环境还是用配置文件更清晰。
5.3 验证复制状态:INFO replication等操作
启动完成后,进入从节点容器检查复制状态:
docker exec -it redis-slave redis-cli在Redis命令行中执行:
INFO replication重点关注几个字段:
role:slave表示当前是从节点master_host:redis-master表示主节点地址master_link_status:up表示主从连接正常slave_repl_offset会随同步数据增加
然后回到主节点写入数据:
docker exec -it redis-master redis-cli SET foo bar再到从节点读取:
docker exec -it redis-slave redis-cli GET foo如果能读到bar,说明主从同步已经通了。我还遇到过一种情况:master_link_status:down,日志里报Unable to connect。这种时候先检查两个容器是否在同一个网络里,用docker network inspect redis-net看容器列表;再检查主节点的protected-mode和bind配置。绝大多数主从失败都是这两点引起的。
6. Docker Compose:把一套多容器部署固化成YAML代码
当你需要同时部署MySQL、Redis、后端应用、前端Nginx时,挨个docker run会让命令变得又臭又长。Docker Compose的价值就是让你把整套部署拓扑写进一个YAML文件,用docker compose up一键起来。这一节会用一个MySQL+Redis主从的例子,说明Compose文件的核心写法。
6.1 为什么放弃一长串docker run改用Compose
我最早管理多容器时,把所有docker run命令复制到文本文件里,每次部署都手动执行一遍。问题很明显:只要版本一更新,文件就会乱;只要有一台新服务器,就要复制一堆命令;只要涉及容器间依赖,手工启动顺序很容易出错。
Docker Compose把这些问题集中解决掉。它用声明式配置告诉你"这个项目有哪些服务、每个服务用什么镜像、对外暴露哪些端口、挂载哪些目录、依赖哪些其他服务"。别人拿到你的docker-compose.yml,不需要额外说明,就能在你机器上复现一套相同的环境。我认为这已经不只是工具使用,而是一种"基础设施即代码"的雏形。
6.2 一个MySQL+Redis主从的docker-compose.yml实操
下面这个文件是我常用模板的简化版,可以直接保存为docker-compose.yml使用:
version: '3.8' services: mysql8: image: mysql:8.0 container_name: mysql8 environment: MYSQL_ROOT_PASSWORD: YourStrongPassword TZ: Asia/Shanghai ports: - "3306:3306" volumes: - mysql-data:/var/lib/mysql - ./conf/mysql/my.cnf:/etc/mysql/conf.d/my.cnf command: - --character-set-server=utf8mb4 - --collation-server=utf8mb4_unicode_ci restart: unless-stopped redis-master: image: redis:7.0 container_name: redis-master command: redis-server --appendonly yes --protected-mode no ports: - "6379:6379" volumes: - redis-data:/data restart: unless-stopped redis-slave: image: redis:7.0 container_name: redis-slave depends_on: - redis-master command: redis-server --replicaof redis-master 6379 --appendonly yes --protected-mode no ports: - "6380:6379" volumes: - redis-slave-data:/data restart: unless-stopped volumes: mysql-data: redis-data: redis-slave-data:这里有几个细节需要解释。container_name是固定容器名称,方便你写脚本时引用;不写的话Compose会自动生成项目名_服务名_序号这样冗长的名字。depends_on控制启动顺序,比如从节点会在主节点启动后再启动,但这并不保证主节点已经初始化完成——对于Redis复制来说影响不大,但如果你的应用启动后立刻要连接数据库,还需要在应用侧做好重试。
volumes下面的三段声明创建了命名卷。在Compose里,你不再需要手动docker volume create,只要声明了卷名,docker compose up时会自动创建。
6.3 Compose常用生命周期命令与优雅关闭
启动整套环境:
docker compose up -d-d表示后台运行。第一次执行会先拉镜像,之后每次启动都很快。查看当前状态:
docker compose ps查看所有服务日志,这个命令在排错时非常有用:
docker compose logs -f如果只想看某一个服务的日志:
docker compose logs -f mysql8停止并删除所有由Compose管理的容器、网络,但不会删除命名卷里的数据:
docker compose down如果想要连数据卷一起清掉,加-v参数,操作前需要非常谨慎:
docker compose down -v我自己写Compose文件时,还习惯加一个.env文件来管理环境变量,比如MYSQL_ROOT_PASSWORD这种敏感信息就不直接写在YAML里。Compose会自动读取同目录下的.env文件,然后在YAML中用${MYSQL_ROOT_PASSWORD}引用。这在团队协作时能避免把密码提交到代码库,但到底怎么做,取决于你自己对安全和便利的权衡。
7. 容器网络不通?从现象到根因的排查链路
"docker网络不通"应该是所有Docker使用者绕不过去的痛点。我在技术群里看到过上百次提问:"容器已经启动了,端口也映射了,为什么就是连不上?"这一节我把排查思路整理成一条清晰的链路,你照着一步步走,大多数网络问题十分钟内能定位。
7.1 "端口通了应用连不上"的常见误会
有一个非常经典的误区:在宿主机上curl http://localhost:3306通了,就以为MySQL容器没问题,但你的后端应用在另一个容器里却连不上。为什么?因为后端容器访问localhost指向的是自己,不是宿主机。它应该用MySQL容器的IP或者容器名来访问,而不是localhost。
所以我的第一个建议是:不要用localhost作为容器间通信的地址。如果两个容器在同一个Docker网络中,直接用对方容器名作为主机名;如果在宿主机上访问,用127.0.0.1映射端口;如果要在同一台机器跨容器、跨网络访问,优先把两个容器放进同一个自定义网络。
另外,容器内应用监听地址也会导致端口通了但连接失败。例如你的Java应用启动时只监听了127.0.0.1:8080,那么即使容器端口映射到宿主机8080:8080,外部访问依然会被拒绝。排查手段是进入容器:
docker exec -it app-container netstat -an | grep 8080在容器里看到:::8080或0.0.0.0:8080才是正确监听方式。如果只有127.0.0.1:8080,说明应用配置需要改成监听所有接口,通常是把server.address设为0.0.0.0。
7.2 bridge、host、自定义网络的选型与限制
Docker默认的网络配置很容易让新手混淆。默认的bridge网络是每个容器启动时自动加入的,容器之间可以通过IP互通,但容器名不能作为主机名使用,除非你使用docker run --link(现在已不推荐)。所以单独用docker run启动多个容器时,它们都在默认网络里,虽然能互相访问IP,但IP会变动,不适合用来做集群。
更好的选择是创建一个自定义bridge网络,就像第5章里的redis-net。在自定义网络里,容器名自动做DNS解析,而且网络隔离更灵活。创建命令:
docker network create mynet启动容器时指定--network mynet,同一个网络里的容器就能用容器名互访。
host网络模式是让容器直接共享宿主机的网络栈,不再有独立IP,也就不用做端口映射。它性能最好,但Docker Desktop在Windows/Mac上对host网络模式的支持有差异,很多时候无法直接使用。我的建议是:在Windows本机做开发调试时,优先使用端口映射;在Linux服务器上且对网络性能有极高要求时,再考虑host模式。
7.3 一套可复用的网络排查命令清单
最后分享一套我每次排查网络问题时都会执行的清单。
首先看端口监听是否正常:
docker port container-name这个命令列出容器端口与宿主机端口的映射关系。如果结果为空,说明你启动时没有加-p参数,外部自然无法访问。
再看容器实际IP:
docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' container-name拿到IP后,从宿主测试连通性:
ping 172.17.0.2如果ping不通,大概率是网络驱动或防火墙问题。然后进入另一个容器内测试:
docker exec -it app-container bash curl http://db-container:3306如果curl报could not resolve host,说明当前网络里没有db-container这个主机名,确认两个容器是否在同一个自定义网络。如果curl卡住不动,说明端口被防火墙或者应用本身拦截。
从宿主机检查端口是否被监听:
netstat -an | findstr 3306LISTENING状态且地址是0.0.0.0:3306说明正常。如果没有监听,很可能是Docker Desktop没有真正启动,或者端口映射没有生效。
Windows环境下还要关注防火墙。Docker Desktop通常会自动配置Windows防火墙规则,但如果你的网络环境策略比较严格,还是要手动放行Docker相关的端口和进程,尤其是com.docker.backend.exe。排查时可以先临时关闭防火墙测一下,确定是它的问题再放行。
8. 一些只有亲自动手才会知道的容器部署小经验
到这一节,你已经把安装、基础命令、MySQL部署、Redis主从、Compose编排和网络排查都过了一遍。作为收尾,我想分享几条长期实践后沉淀下来的经验,它们不是某个具体命令的参数,而是策略层面的东西。
第一,永远锁定镜像版本。别用latest标签。你今天docker pull mysql:latest拉到的版本和三个月后拉到的版本可能完全不同,一次docker compose pull就能让线上环境悄悄升级一个大版本。应该固定为mysql:8.0.36这样具体的版本号。同样,镜像仓库里记住digest是最保险的,但普通项目至少要做到指定主版本。
第二,容器里不要存任何需要长期保存的数据。数据卷是唯一出路。如果你发现自己正在往容器内部写配置文件或者日志文件,停下来想想能不能挂载到宿主机。容器是"一次性用品",随时可以被替换,但数据不行。
第三,给所有容器设置restart: unless-stopped。无论是docker run还是Compose,都要加上这个重启策略。因为它能让你在服务器意外重启后不用手动一个个重新启动容器。对于没有该参数的容器,Docker引擎启动时不会自动拉起它们。
第四,用docker compose config检查你的YAML文件。这个命令会解析docker-compose.yml并输出完整的配置,能帮你发现格式错误、缩进问题、环境变量未定义等情况。我见过不少同事因为YAML缩进不对而反复up/down,其实执行一次config就能看到问题。
第五,不要怕删除容器重建。只要数据卷还在,删除容器只是删掉一个运行实例,数据完全不受影响。很多人第一次用Docker时对docker rm -f非常恐惧,生怕把服务器弄坏。实际上只要数据卷配置正确,容器随便删,重建才是Docker部署的日常。
我在实际项目中,已经养成了一套固定习惯:新项目一律用Compose定义整套开发环境,镜像版本写死,数据卷显式声明,端口映射做到一台服务器绝不冲突。这套流程从单机到小集群都很稳定,希望这篇手册也能帮你形成自己的部署流程。下次再遇到部署难题,不妨先问一句——这个组件能不能直接容器化跑起来?