☰
Docker实战全记录:从安装部署到MySQL/Redis与Compose编排
2026/10/2 9:13:44 网站建设 项目流程

开篇先聊点实在的。我每天跟Docker打交道,从最开始在Windows上装个Desktop都折腾半天,到现在帮团队搭各种中间件环境,中间踩过的坑、啃过的文档、总结下来的参数用法,都在这一篇里了。这篇文章不是什么官方文档的翻译,是我自己从零到一、从能跑到跑好这份实战过程中沉淀下来的全量笔记,尤其适合刚接触容器、或者用Docker部署MySQL、Redis这类基础服务时总遇到问题的同学。

先说清楚这篇笔记覆盖了什么:Docker和Docker Desktop的安装、核心概念原理、镜像与容器的日常操作、每一个高频参数的逐个解释,再到用Docker部署MySQL 8.0、搭建Redis主从、用Compose编排多容器,最后是网络不通、虚拟机检测不到、MySQL装不上这些高频疑难杂症的排查思路。既讲是什么,也讲为什么,更讲出了问题怎么办。软件版本和命令我都以当前主流版为准,并会标注清楚哪些是兼容性层面的坑。让我从最基础但也最容易被忽视的问题讲起。

1. 先搞清楚Docker到底解决什么问题

1.1 镜像与容器:两个核心概念一次弄懂

很多人第一次接触Docker,被“镜像”“容器”“仓库”这几个词搞晕。我用大白话把它讲透。

镜像(Image)可以理解为一个只读的模板、一份完整的“安装包”。里面打包好了操作系统的基础组件、你需要的运行时、应用代码、环境变量和配置。它本身不运行,只是描述“我要一个什么样的环境”,相当于一个类(Class)。

容器(Container)则是基于镜像创建的运行实例,是真正跑起来、真正干活的那个进程。同一个镜像可以启动多个容器,容器之间相互隔离,每个容器有自己的文件系统、网络和进程空间。这就好比同一个类的不同对象,内存地址不同、运行状态也互不影响,你这个窗口里跑的Redis崩了,不影响另一个窗口里Redis继续服务。

还有一个概念是仓库(Registry)。它专门存镜像,可以理解为“镜像的网盘”。Docker Hub就是默认的公共仓库,就好比官方App Store,我们常用的mysql、redis、nginx等镜像都在上面。你可以在本地打标签推送到自己的私有仓库,后面部署时直接从仓库拉取,保证开发、测试、生产三套环境拿到的镜像内容完全一致。

理解了这三者的关系,你的思路就清晰了:从仓库拉取镜像(docker pull)→ 基于镜像启动容器(docker run)→ 容器运行应用 → 不再需要的容器可以停止并删除,镜像还可以留着复用。

1.2 为什么大家都在用Docker(以及什么时候不需要)

咱们做开发的,最痛苦的一件事就是环境不一致。你在自己电脑上跑得好好的,交到同事那儿就报错,更别提上服务器那一关。Docker把应用和它依赖的运行环境整个打包进镜像,无论在哪个机器上,只要装好Docker,一条命令就能复现完全一样的运行环境,这就把“在我机器上能跑”变成了“在任何机器都能跑”。

Docker带给我们几个很实际的价值:

  • 启动快。容器本质上是宿主机里的进程,秒级启动,和虚拟机那种动辄几十秒的引导完全不在一个量级,非常适合做开发调试和弹性扩缩容。
  • 隔离干净。应用之间的依赖不再互相污染。比如一个项目要MySQL 5.7,另一个要MySQL 8.0,以前你在本机装两套数据库很痛苦,卸载不干净、配置文件冲突。用Docker分别起两个容器,端口不冲突,老死不相往来。
  • 环境一致。构建出的镜像,从你的笔记本到测试机到生产服务器都是一模一样的,大大减少“测试通过了,生产炸了”的情况。
  • 资源利用更高。多个容器共享宿主机的操作系统内核,不需要像虚拟机那样为每个实例分配一整套完整的操作系统资源。

但我也得说句公道话:Docker不是万能的。如果你的应用本身就是单机版、没什么外部依赖,或者对操作系统内核有特殊定制要求,那Docker带给你的价值就有限;如果你要跑一些平台级、需要直接管理物理硬件的任务,容器也不适合。另外,Windows下跑Docker本质上依赖WSL2或Hyper-V的Linux虚拟机,性能和裸机运行Linux还是有差距,生产环境优先Linux服务器没错的。

2. Docker安装:Windows、macOS、Linux三个平台一次说清

2.1 Docker Desktop与Docker Engine的选择

Docker分两个层次。一个是纯粹的引擎(Docker Engine),也就是守护进程,负责容器生命周期的管理,最核心,通常跑在Linux服务器上;另一个是带图形界面的桌面软件(Docker Desktop),内置了引擎、命令行工具,额外提供可视化管理器,适合日常开发使用。

Docker Desktop最方便的地方在于自带一套完整的运行环境,Windows用户不需要自己去折腾WSL或Hyper-V,安装包全都帮你配好,几个勾选框点掉就直接能用。但请注意,Docker Desktop对个人和小团队是免费开源的,对大型企业收费,商用前务必去官网核对一下最新授权条款。

如果是生产服务器,我的建议是装纯Docker Engine,轻量、省内存、完全没有图形界面的拖累,通常使用各发行版官方源里的docker-ce包。别在生产环境装Desktop,没有任何必要。

2.2 Windows上安装的完整流程与“虚拟化未检测到”的解法

Windows用户装Docker Desktop,看起来无脑下一步,实际上前置条件没满足的话,装完启动就会报那个经典的错误:Virtualization support not detected,Docker Desktop failed to start because virtualization support is disabled...

这个报错95%以上是两种情况:要么BIOS没开虚拟化(Intel VT-x / AMD-V),要么Windows的虚拟化功能没开。排查按这个顺序来:

  1. 打开任务管理器 → 性能 → CPU,看右下角“虚拟化”一栏,如果是“已启用”就说明BIOS层面已开启,往下走;如果是“已禁用”,重启电脑进BIOS(开机时按F2、Del、或F10,具体看厂商),找到Intel Virtualization Technology或SVM Mode(AMD),设为Enabled,保存重启。
  2. Windows功能里开启相关组件。在开始菜单搜“启用或关闭Windows功能”,勾选“适用于Linux的Windows子系统”和“虚拟机平台”这两项,如果是Docker Desktop老版本也可能依赖Hyper-V,酌情一起勾上。这会要求重启系统,重启完再打开Docker Desktop,一般就能起来了。
  3. Windows 10 2004以上版本,我更推荐直接确认WSL2已启用。以管理员身份打开PowerShell,执行wsl --install或wsl --update,先把WSL2装好,再回头装Docker Desktop。

装完后,在桌面确认小鲸鱼图标正常,打开终端(PowerShell或CMD)输入:

docker version docker info

只要能看到Client和Server两段信息,Server那段的Operating System写着Docker Desktop或某个Linux发行版,就说明通了。如果docker version只显示Client,说明Server没启动,排查上面步骤。

macOS用户相对省事,从官网下载Apple Silicon或Intel对应包安装,首次启动会请求安装辅助组件并开机自启,输入密码授权即可。但要注意,Mac上的Docker Desktop同样禁止在生产环境使用,Windows/Linux同理。

2.3 Linux服务器上安装(以Ubuntu为例,不带图形界面)

如果你面对的是一台云服务器,或者一台没有桌面的Linux主机,用纯Engine就足够了,速度快、占用低。Ubuntu系的安装方式如下:

# 先卸载可能存在的旧版本 sudo apt remove docker docker-engine docker.io containerd runc # 安装必要依赖 sudo apt update sudo apt install ca-certificates curl gnupg # 添加Docker官方GPG密钥 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 sudo chmod a+r /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 # 安装docker引擎及配套工具 sudo apt update sudo apt install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

安装完成后,默认情况下执行docker命令需要sudo,可以把自己加入docker用户组,重登后免sudo:

sudo usermod -aG docker $USER

这个是常规操作了,顺手把服务设置成开机自启:

sudo systemctl enable docker sudo systemctl start docker

CentOS / RHEL系类似,用yum装docker-ce,软件源地址换一下就行,这里不展开。核心是装完能跑docker run hello-world,容器能成功输出信息,你的Docker环境就算立起来了。

3. 每天必用的镜像与容器命令(含参数逐一解释)

3.1 镜像操作:搜索、下载、查看、删除

先说镜像层的操作,这套命令是最高频的。

docker search mysql docker pull mysql:8.0 docker images docker image rm mysql:8.0 docker rmi $(docker images -q)
  • docker search去仓库搜索镜像,能看到镜像名称、描述、Stars、是否官方(OFFICIAL列带[OK])。建议优先用官方镜像,安全性和支持度更有保障。
  • docker pull 镜像名:标签把镜像拉到本地。标签就是版本号,比如mysql:8.0、redis:7.2、nginx:1.27。不写标签默认是latest,但latest随时间会发生变化,哪天重新拉取就会变成新的上游版本,开发和运维上都不好把控,所以我强烈建议任何部署都写明具体版本标签。
  • docker images列出本地所有镜像,会显示仓库名、标签、镜像ID、创建时间、大小。注意:latest不一定是最新,标签是由维护者决定的。
  • docker rmi删除本地镜像。如果有容器还在使用它,会报错,需要先删容器。一条命令清理所有镜像慎用,我一般只在全新的测试机上执行这种操作。

补充一个镜像历史的小工具:

docker history mysql:8.0

可以看到镜像的分层构建过程,每一层是什么命令生成的、大小多少,排查镜像体积时非常有用。层数越多,镜像一般越大,所以平时写Dockerfile要有合层意识,能用一条RUN写完的就别拆成三条。

3.2 容器操作:启动、停止、进入、日志

容器和镜像最大的区别是容器有生命周期、有状态。下面是基本命令:

# 基于镜像创建并启动一个容器 docker run -d --name my-nginx -p 8080:80 nginx # 查看运行中的容器 docker ps # 查看所有容器(包括已停止的) docker ps -a # 停止/启动/重启容器 docker stop my-nginx docker start my-nginx docker restart my-nginx # 进入正在运行的容器(交互式终端) docker exec -it my-nginx bash # 查看容器日志 docker logs -f my-nginx # 删除容器(先停止才能删) docker rm my-nginx # 删除全部停止的容器 docker container prune

逐项解释:

  • docker run是创建容器并启动,如果本地没有镜像会自动去拉。参数-d是后台运行,意思是容器在后台悄悄跑,你马上能拿到终端控制权;-p 8080:80是把宿主机的8080端口映射到容器的80端口,我在下一节详细讲。
  • docker ps是最常用查看类命令,字段有容器ID、镜像、启动命令、创建时间、状态、端口映射和名称。容器ID是64位十六进制字符串,够了用前几位即可,比如a1b2c3。
  • docker exec -it my-nginx bash是进入容器内部调试,-i保持标准输入打开,-t分配一个伪终端,两个一起用才能进入交互式Shell。容器内不一定有bash,有些精简镜像是只有sh,所以用sh更保险。
  • docker logs -f查看容器输出日志,-f表示跟随模式,类似tail -f。平时排查应用启动报错,第一件事就是看日志。

实操心得:docker run和docker start完全是两个概念。run是“创建并启动”,start是“启动已存在的容器”,很多新手把这两个搞混,导致反复创建了一堆容器。如果我需要临时跑一个测试容器,用完即走,我会加--rm参数,容器退出时自动删除,省得手动清理:

docker run -d --rm -p 8080:80 nginx

但注意,加了--rm之后这个容器就不能被stop后再start了,它停止就等于删除。需要长期稳定运行的服务别带这个参数。

3.3 必须搞懂的run参数:-d / -p / -v / -e / --name / --restart

这是全篇最核心的部分。docker run有一堆参数,至少这六个你必须深入理解,否则你写出来的命令要不跑不起来,要不停掉就丢了数据和配置。

-d(后台运行)

-d是detach模式,容器在后台运行,由守护进程管理生命周期。不加这个参数的话,容器会以前台进程的方式运行,你按Ctrl+C,容器就停了,适合看输出调试;加了-d后,运行的实质实际上仍是用户态进程,只不过你在终端上看到的输出不再交互,而是被收集到日志里。

-p(端口映射)

容器内部的应用默认只能被容器自己访问,外人访问不到。我们需要把容器的某个端口映射到宿主机上,这样从外部就能通过宿主机的IP和端口访问容器里的服务。格式是-p 宿主机端口:容器端口,比如:

docker run -d --name mysql8 -p 3306:3306 mysql:8.0

这个例子把容器内的3306(MySQL服务端口)映射到宿主机的3306。如果你想宿主机端口是13306避免冲突,就写-p 13306:3306,外部通过13306访问。注意:只要宿主机端口被占用,启动就失败,这也是很多“启动不了”的原因。

-v(数据卷挂载)

容器创建后,容器内的文件系统是隔离的,容器被删掉,容器内产生的数据也没了。你不想数据库一重启就丢数据吧?所以要把容器里的重要目录“挂载”到宿主机上,用自己的目录替换容器里的目录。格式是-v 宿主机目录:容器目录:

docker run -d --name mysql8 -p 3306:3306 -v /mydata/mysql:/var/lib/mysql mysql:8.0

/var/lib/mysql是MySQL在容器内默认存储数据的目录,/mydata/mysql是宿主机上的目录,这个目录必须存在(不存在的话Docker会自动创建)。我要强调一点:-v做的是目录绑定,其中容器目录如果为空目录,宿主机目录内容也不会被覆盖,但如果容器目录里有初始文件,Docker会先做一个复制初始化,具体行为可能随版本变化。新版更推荐用--mount,但日常开发用-v足够了。持久化逻辑就是:容器可以被随意删掉重建,但数据就放在宿主机这个指定目录里,顶多换个容器再挂载即可。

-e(环境变量)

很多镜像预留下来配置接口,通过环境变量来设置。比如MySQL镜像需要设置root密码:

docker run -d --name mysql8 -p 3306:3306 -e MYSQL_ROOT_PASSWORD=123456 mysql:8.0

镜像脚本在启动时读取这些环境变量,完成初始化。不同镜像的环境变量列表要看它的官方文档。-e 键=值的方式可以写多个,-e之间用空格隔开即可。

--name(容器名称)

给容器一个可读的名称,方便后续操作。不写的话,Docker自动生成一个随机名字,命令操作起来不方便。容器名称在同一台机器上是唯一的,重复会报错。

--restart(重启策略)

容器挂了怎么办?手动重启还是让它自动拉起?这个参数定义重启策略,取值不同含义也不同:

取值行为
no默认值,容器退出后不自动重启
on-failure[:max-retries]容器以非0状态退出(程序报错)时自动重启,可限定最大重试次数
unless-stopped除非管理员手动stop,否则无论容器怎么退出(包括开机时)都自动重启
always只要容器停止就立即重启,包括手动stop后重新启动Docker守护进程时

推荐生产环境服务容器使用--restart unless-stopped或--restart always,区别在于Docker重启时是否自动拉起手动停掉的容器,前者尊重你主动stop的状态,后者总会启动。你想让服务在服务器重启后自动恢复,必须带这个参数:

docker run -d --name mysql8 --restart unless-stopped -p 3306:3306 -e MYSQL_ROOT_PASSWORD=123456 mysql:8.0

这个组合命令完美串起了全部六个参数,也是我日常部署一个基础服务的标准手势。你可以把它当成一条模板命令,照着改镜像、改端口、改目录、改环境变量,就能部署大部分开源服务。

4. 实战:用Docker跑起MySQL 8.0(含参数解析与失败排查)

4.1 一条命令跑起来

MySQL是Docker部署的典型场景,因为它的安装在裸机上是出了名的麻烦,尤其是版本切换和数据目录管理。用Docker就清爽多了。

先把镜像拉下来,指定版本标签:

docker pull mysql:8.0

然后写入我生产环境常用的启动命令,比上面的模板再多几个参数:

docker run -d \ --name mysql8 \ --restart unless-stopped \ -p 3306:3306 \ -v /mydata/mysql/conf:/etc/mysql/conf.d \ -v /mydata/mysql/data:/var/lib/mysql \ -e MYSQL_ROOT_PASSWORD=Root@123456 \ -e TZ=Asia/Shanghai \ mysql:8.0 \ --character-set-server=utf8mb4 \ --collation-server=utf8mb4_unicode_ci

这里我额外加了两步:

第一个-v /mydata/mysql/conf:/etc/mysql/conf.d把宿主机上的配置目录挂载到MySQL镜像是用来加载自定义配置的目录,我习惯把自己的my.cnf片段放到宿主机这个目录下,这样调参数不需要进容器改文件,而且容器重建后配置还在。

第二条-e TZ=Asia/Shanghai设置时区,MySQL默认时区是UTC,如果你不设置,查到的日期会比北京时间早8小时,这是很多人“日期对不上”的坑。

最后两个参数--character-set-server=utf8mb4和--collation-server=utf8mb4_unicode_ci是追加在镜像名后面的启动参数,会直接传给MySQL服务器进程。utf8mb4是完整的Unicode字符集,能存emoji,联调时的排序规则用unicode_ci对中文排序更合理。这应该是中文项目的标准题,没跑这两个参数的MySQL很容易出现“存emoji报错”的情况。

启动后等几秒,用日志确认初始化完成:

docker logs -f mysql8

看到ready for connections这个日志,基本就成功了。然后再验证外部能不能连:

mysql -h127.0.0.1 -P3306 -uroot -pRoot@123456

如果你本机没有mysql客户端,也可以进容器里用容器自带客户端:

docker exec -it mysql8 mysql -uroot -p

到这里,一个持久化、自启动、UTF8MB4、带时区的MySQL 8.0就交付了。

4.2 连接与配置细节

其实很多用户连MySQL时还会遇到一个经典报错:Authentication plugin 'caching_sha2_password' cannot be loaded。

MySQL 8.0默认的认证插件是caching_sha2_password,而一些老旧的客户端或图形工具(比如老版本Navicat、部分Java驱动)不支持这种认证方式。解决方案是创建或修改用户时指定老兼容的认证插件:

ALTER USER 'root'@'%' IDENTIFIED WITH mysql_native_password BY 'Root@123456'; FLUSH PRIVILEGES;

或者干脆在创建用户时:

CREATE USER 'app'@'%' IDENTIFIED WITH mysql_native_password BY 'App@123456'; GRANT ALL PRIVILEGES ON *.* TO 'app'@'%'; FLUSH PRIVILEGES;

我个人建议:如果业务驱动比较新,优先保持默认的caching_sha2_password,安全性更好;如果客户端是老版本,再用上面的方案做兼容,这属于老生常谈但必须提醒的操作。

另外,远程连接时,MySQL默认只监听容器内,我们已经通过-p 3306:3306把端口暴露到宿主机了,所以外部通过宿主机IP就能连接。要开放公网访问的话,记得在安全组或防火墙放行3306端口,我这个测试环境只在内网访问,公网不要轻易暴露数据库端口。

4.3 常见的MySQL安装失败场景

端口冲突启动即退出

现象:docker run执行后容器秒退,docker ps -a显示Exited (0),日志里看到mysqld: Can't read dir of '/etc/mysql/conf.d/'或直接报3306端口被占用。

原因:宿主机3306已经有个MySQL或者别的服务在监听了。排查用netstat -tlnp | grep 3306,有结果说明端口占用。

解决:把宿主机的映射端口改成别的,比如-p 13306:3306,或者停掉宿主机的服务。注意端口是宿主机的那个端口,容器内端口不用改。

数据目录权限不对导致无法初始化

现象:日志里有chown: changing ownership of '/var/lib/mysql/': Operation not permitted,或者进程反复重启。

原因:新版的mysql镜像默认以mysql用户运行,但当挂载的宿主机目录权限不对(比如挂到一个没有写权限的目录,或者SELinux拦截),就会报权限问题。

解决:确认宿主机目录存在且有写权限,最简单是mkdir -p /mydata/mysql/data && chown -R 1000:1000 /mydata/mysql/data,因为mysql镜像里默认uid是1000。注意不要草率地chmod -R 777,不安全。

root密码验证不通过

现象:日志本身正常,但客户端连的时候说Access denied for user 'root'@'localhost'。

原因:环境变量没有正确传给容器,或者老的容器里已经初始化过了,环境变量只在初始化数据目录时生效。

解决:删掉容器和旧的挂载数据目录,重新创建;或者进容器重置密码。排查时可以docker inspect mysql8查看Config.Env,确认环境变量有没有真的传进去。

初始化时内存不足

某些最小规格的云主机(1G内存)启动MySQL 8.0会大概率内存不够,容器反复被OOM杀掉,日志中看到Killed字样。这种情况考虑加swap,或者让容器限制内存、调低MySQL的buffer pool大小;更省资源的替代思路是:如果只是临时开发环境,用MySQL 5.7镜像也能满足大部分需求。

5. 进阶实战:Redis主从一键部署,顺便入门Docker Compose

5.1 手工方式搭建Redis主从

Redis部署比MySQL简单太多,因为它就是一个二进制文件跑起来,连外部依赖都没有。但主从复制如果直接按裸机文档操作,还是需要配置、启动、验证,用Docker更是能压缩到一分钟。

先创建主节点,我使用配置挂载的方式,把主节点的redis.conf放到宿主机上,统一管理:

mkdir -p /mydata/redis/conf /mydata/redis/data # 最简单的redis.conf,强调持久化 cat /mydata/redis/conf/redis.conf <<EOF appendonly yes dir /data EOF docker run -d \ --name redis-master \ --restart unless-stopped \ -p 6379:6379 \ -v /mydata/redis/conf/redis.conf:/etc/redis/redis.conf \ -v /mydata/redis/data:/data \ redis:7.2 \ redis-server /etc/redis/redis.conf

这里解释几个关键点:

  • appendonly yes开启AOF持久化,Redis默认只把数据放内存,进程一重启全部拜拜,开AOF重启后才能从磁盘恢复。
  • 挂载/data是镜像官方声明的数据目录,配合上述配置的dir /data保持一致。
  • 最后一句redis-server /etc/redis/redis.conf指定用我们挂载进去的配置文件启动,默认的官方镜像会用内置默认配置,但为了分工明确,建议像这样显式指定路径。

再建从节点。从节点的作用就是复制主节点的数据,用于读写分离或高可用。端口我用6380避免和主节点一样:

cat /mydata/redis-slave/conf/redis.conf <<EOF appendonly yes dir /data EOF docker run -d \ --name redis-slave \ --restart unless-stopped \ -p 6380:6379 \ -v /mydata/redis-slave/conf/redis.conf:/etc/redis/redis.conf \ -v /mydata/redis-slave/data:/data \ redis:7.2 \ redis-server /etc/redis/redis.conf --replicaof 宿主机IP 6379

重点看最后那部分:--replicaof 宿主机IP 6379,它的作用就是告诉这个从节点去连接谁成为主节点。因为我这个redis-slave容器的端口是映射到宿主机的6380,它要去访问主节点时,不能走localhost,因为localhost是它自己,要走宿主机IP(局域网里的地址或者Docker宿主机的网关地址),才能回到宿主机的6379,再到主节点。

启动后验证一下主从关系:

docker exec -it redis-slave redis-cli -p 6379 info replication

看到role:slave以及master_link_status:up,就说明主从复制已经正常。手动往主节点写一个key,从节点能查到,就非常清楚地验证了复制的实时性。不要把测试key遗留在生产环境,毕竟谁也不想线上放着乱七八糟的key。

5.2 docker-compose.yml怎么写

手工跑两个容器还能接受,但如果涉及一组服务(比如一个web应用、一个MySQL、一个Redis、一个队列),每一行都手敲就不合适了。Docker Compose的出现就是解决“多容器编排”问题:把容器的启动参数写进一个yaml文件,一条docker compose up -d全部搞定,停止、重建、查看日志也都有对应命令。

我以前面这两套服务为例,写一个compose文件,名字就叫docker-compose.yml:

services: mysql8: image: mysql:8.0 container_name: mysql8 restart: unless-stopped ports: - "3306:3306" volumes: - /mydata/mysql/conf:/etc/mysql/conf.d - /mydata/mysql/data:/var/lib/mysql environment: MYSQL_ROOT_PASSWORD: Root@123456 TZ: Asia/Shanghai command: - --character-set-server=utf8mb4 - --collation-server=utf8mb4_unicode_ci redis-master: image: redis:7.2 container_name: redis-master restart: unless-stopped ports: - "6379:6379" volumes: - /mydata/redis/conf/redis.conf:/etc/redis/redis.conf - /mydata/redis/data:/data command: redis-server /etc/redis/redis.conf redis-slave: image: redis:7.2 container_name: redis-slave restart: unless-stopped depends_on: - redis-master ports: - "6380:6379" volumes: - /mydata/redis-slave/conf/redis.conf:/etc/redis/redis.conf - /mydata/redis-slave/data:/data command: redis-server /etc/redis/redis.conf --replicaof 宿主机IP 6379

然后在文件所在目录执行:

docker compose up -d

它会解析这个文件,按依赖关系依次创建容器。depends_on这里的作用是确保redis-master先启动,但请注意它只保证顺序,不保证主服务内部完全就绪(比如MySQL可能需要一分钟才真正准备好),所以还是要通过日志或健康检查来确认。想查看当前这批服务状态用:

docker compose ps docker compose logs -f

车间里如果改了yaml配置,重新拉配置并应用变更用:

docker compose up -d

它会自动对比现有容器与文件定义,重建有改动的服务。真正要彻底删掉这批容器用:

docker compose down

注意,不带-v的情况下,down只删除容器和网络,不会删掉你挂载的数据卷,所以数据还是安全的,这点设计得很稳。

5.3 compose常用字段解释

compose文件有几个高频字段,我把它们的含义和坑一并说完。

字段对应docker run参数说明
image镜像名:标签指定镜像来源,不存在时会自动拉取
container_name--name自定义容器名
restart--restart重启策略,和docker run保持一致
ports-p宿主机端口:容器端口,注意yaml里冒号要加引号写,比如 "8080:80"
volumes-v宿主机目录:容器目录
environment-e键值对形式设置环境变量
command镜像名后的启动参数覆盖Dockerfile里的CMD,可以是字符串或列表
depends_on依赖关系控制服务启动顺序

有几个容易踩的格式坑:

  • ports里"3306:3306"写成数字不加引号,在YAML解析时会被误认为端口字符串没问题,但遇到6379:6379这种纯数字其实也会正常,不过我见过"8080:80"被解析成yaml的sexagesimal(60进制)数字的极端情况,稳妥直接统一加引号。
  • volumes里路径如果包含空格或特殊字符,务必加引号。
  • environment里的值不要写成MYSQL_ROOT_PASSWORD=123456带=号,用键: 值的形式,否则会报解析错误(除非你用旧版list格式)。

Compose文件就是你把docker run的整个人脑配置转写成文件,一旦习惯之后,部署一套服务从半小时缩短到三分钟,这才是Docker真正省时的体现。

6. Docker网络不通?这份排查手册收好

很多人在容器间通信时卡壳,明明两个容器都在跑,却互相ping不通、连接不上。这背后是Docker的网络模式没有搞透。这一节我完整梳理,给你一个速查表。

6.1 三种网络模式

Docker常见的网络模式有桥接(bridge)、主机(host)和none,还有一个容器间共享网络的模式(container)。默认是bridge,它会在宿主机上创建一个虚拟网桥(docker0),每个容器会分配一个独立的内网IP,容器之间通过这个网桥通信。但要注意:容器IP是动态的,每次重建都会变,所以跨容器通信绝对不能写死对方的IP。

host模式就是容器直接复用宿主机的网络栈,容器里的进程直接监听宿主机端口,没有网络隔离。这么做性能最好,但失去了网络隔离的灵活性,并且同一个端口只能跑一个容器,不适合多实例部署。

none模式就是完全不给网络,适合极重视离、不需要连外的场景,平时用得少。

container模式让容器和另一个指定容器共享同一个网络命名空间,比如某些管理容器的场景会用到,日常开发不常碰。

用命令查看当前网络的概况:

docker network ls

输出常见的是bridge、host、none三个,以及你自定义的overlay网络(跨主机场景)。

6.2 容器间通信的坑与解决方案

最典型的场景:MySQL容器在跑,你的后端应用容器想访问它,如果应用里写localhost或127.0.0.1,那指的就是它自己,而不是MySQL容器,这就是“网络不通”的最大原因。

解决方案有两个:

方案一:使用宿主机IP和映射出来的端口。比如应用容器连接宿主机IP:3306就可以访问到MySQL了。宿主机IP这一块在容器里可以用host.docker.internal(Docker Desktop环境专门支持这个名字,Linux纯Engine则需要在docker run时加--add-host=host.docker.internal:host-gateway才能让这个名字生效)来获取。这个方法简单,缺点是多走了一层端口转发。

方案二:使用Docker自定义网络,这是生产环境更推荐的做法。创建一个自定义bridge网络,容器加入同一个网络后,可以直接用容器名当作域名来互相访问,而且DNS自带解析。比如:

docker network create mynet docker run -d --name mysql8 --network mynet -e MYSQL_ROOT_PASSWORD=123456 mysql:8.0 docker run -d --name app --network mynet -p 8080:8080 myapp:1.0

这样app容器里就可以直接用mysql8:3306当连接地址,IP变了也不怕,容器重建再多次,容器名不变,应用配置不用动。这比写IP优雅得多。在Compose里,只要启动的多个服务定义了networks:段指向同一个自定义网络,就能自动通过服务名互访,这也是Compose默认行为。

我还遇到过“容器能通外网但curl不通内网”的情况,多半是代理配置问题,容器里默认没有使用宿主机的代理,如果宿主机通过代理上网,需要在docker build或docker run中设置HTTP_PROXY、HTTPS_PROXY环境变量。

最后一个常见问题:容器内的服务监听了127.0.0.1而不是0.0.0.0。很多应用默认只监听本机回环接口,映射端口后外部依然访问不到。这是个应用层问题,跟Docker无关,但排查时容易把人绕晕。判断方法很简单:进容器执行netstat -tlnp,看看服务是监听0.0.0.0:还是127.0.0.1:,前者才对外可见;如果是后者,去掉配置文件里的bind 127.0.0.1。

网络排查工具不能少,我建议你的工具箱常备这几个命令:

  • docker inspect 容器名:查看容器详情的最终手段,几乎能查所有配置和网络IP。
  • docker exec 容器名 ping 目标:容器内Ping不通目标时,先判断是不是容器没配路由或DNS。
  • docker logs 容器名:服务启动报错时,第一手信息来源,远比看容器状态有用。

从工作实践来说,我踩得最深的坑还是“用IP而不是用名字通信”。记住一条原则:容器之间的通信,首选自定义网络加容器名,不要用IP,不要用localhost。

我个人在实际操作中的体会是,Docker的坑,十有八九不是Docker本身的问题,而是网络模型和文件系统没有吃透。参数背得再多,不如亲手把MySQL、Redis、Compose这三套组合跑一遍,跑得通了,你对容器的理解就完全不一样了。这套笔记里每一条命令、每一个参数,都是我反复在干净环境里验证过的,你可以照着一条条执行,有问题再回来查排查表。后续如果你要尝鲜K8s,Docker这些基本功越扎实,上容器编排平台就越顺畅。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询