写启动参数这件事,看似简单,绝大多数人栽的跟头恰恰就藏在那一长串docker run里。见过太多次同事对着终端敲完命令,回车发现端口没生效、容器秒退、数据没落盘,然后又去 Google 折腾半天。我自己整理过一份 Docker 常用镜像启动参数对照表,从 MySQL 到 Redis、从 Nginx 到 Elasticsearch,大部分生产环境里高频出现的镜像都在里面。今天把这份表和背后的思路一起放出来,希望能让新同学少走弯路,也让老手有个随手能翻的速查。
这张表不追求把每个镜像的所有参数都列全,而是把每个镜像最常见的启动方式、最容易漏掉的参数、以及最典型的坑标出来。适合三种人看:刚开始用 Docker、全靠 copy 命令但不清原理的;被线上容器问题反复折腾过的;以及准备写部署脚本或者 docker-compose 前想找个参考的。
1. 为什么需要一份启动参数对照表
1.1 这表不是给你抄作业的,是让你少踩坑的
一个很残酷的现实是:镜像仓库里的 README 通常只给了最基础的启动命令,镜像的种类一多,很容易把不同镜像的参数搞混。举个例子,MySQL 的MYSQL_ROOT_PASSWORD和 Redis 的REQUIREPASS是两套完全不同的变量体系;MongoDB 挂载目录和 MySQL 挂载目录的名字又不一样;Elasticsearch 启动的时候默认会申请一大块虚拟内存,不调ulimit直接报错。这些细节不在实际踩过一遍的时候,你是记不住的。
我最早整理这张表,就是因为在一次部署中连续起了 MySQL、Redis、Nginx 三个容器,结果三台全部踩了不同的坑:MySQL 忘了加character-set-server=utf8mb4,Redis 没有配持久化策略,Nginx 的配置文件挂载方式搞错了。当时就意识到,与其在终端里翻历史记录一条条找,不如把常用参数按镜像维度彻底过一遍。
1.2 什么人、什么场景最需要这张表
如果你的日常工作主要是在服务器上部署中间件、给项目搭建测试环境、或者带着团队做 CI/CD,那你需要一个相对可靠的参数基线。开发环境的要求是快和能用,容错性高;测试环境要求稳定可复现;生产环境则要考虑数据持久化、重启策略、健康检查、资源限制和日志。这张表默认覆盖的是“单机容器化部署”里最常见的一层,也就是能用一套命令快速把中间件拉起来的场景。
如果你们团队已经开始全面用 docker-compose 或者 Kubernetes,那这些参数依然适用,因为 compose 里的ports、volumes、environment本质上就是docker run参数的另一种写法。对照表的意义不是让你天天手敲,而是让你在写配置文件时知道哪个字段对应什么含义,出了问题也知道去哪里找。
2. 先搞懂这些通用参数,再记任何镜像都不会慌
2.1 生命周期参数:-d、--name、--restart
很多人一上来就是docker run -d -p 3306:3306 mysql,但-d表示的是后台运行,而不是“守护进程”那种自愈能力。真正决定容器挂了之后怎么处理的是--restart参数。我这里简单统计过,--restart=always是使用率最高的,它的意思是无论容器被手动停掉还是因为异常退出,Docker 都会尽量把它拉起来。
但如果你的服务必须在启动顺序上依赖别的组件,盲目用always会带来问题。比如某个应用容器依赖 MySQL 先启动,MySQL 容器因为升级需要重启,应用容器可能频繁重启直到依赖恢复。这时候更适合--restart=unless-stopped,它和always的区别在于:如果你明确执行过docker stop,那么容器在机器重启后不会自动恢复。这个语义在临时调试时更友好。
--name就不用多说了,它是你操作容器的唯一标识。最典型的坑是:你不给容器起名字,Docker 会自动生成一个随机的名字,下次你想docker exec -it进入时还得翻docker ps找 ID。还有一点容易被忽略,同一个主机上容器名不能重复,启动多个同类服务时注意别撞名。
2.2 资源与安全参数:-e、-v、-p、-u、--privileged
环境变量-e是镜像预设给我们的“配置开关”,每个镜像的变量含义只能查镜像文档,没有任何通用的推理办法。但有一类变量非常值得重视,就是密码和连接字符串相关的变量。我见过有人把生产数据库密码直接写在docker run命令的参数里,后面翻 shell 历史记录全都露馅了。建议用--env-file指定环境变量文件,权限设成600,这样至少不会在外面裸奔。
-v是数据卷挂载,宿主机目录和容器目录的映射关系。这里有个经常错的地方:容器内的路径是镜像作者决定的,不是你想挂到哪就挂到哪。你想持久化 MySQL 数据,就要挂/var/lib/mysql,不是把整个/var/lib挂出来。挂载之后目录权限也会经常出问题,比如宿主机目录是root:root,容器内进程可能以普通用户运行,结果写不进去,容器直接报错退出。解决办法是先用-u指定 UID,或者提前把宿主机目录属主改掉。
-p是端口映射,格式是“宿主机端口:容器端口”。这里最隐蔽的问题是:如果你不指定宿主机 IP,默认监听0.0.0.0,也就是说所有网卡都能访问。云端部署时,如果安全组没拦死,等于把服务裸奔到公网了。建议显式写127.0.0.1:3306:3306这种只在本机监听的写法,或者交给反向代理统一对外。
--privileged是给容器的 root 权限,很多第三方镜像文档会提示你加上它,但这是个双刃剑。加了确实是省事,很多权限问题都消失了,但也意味着容器内进程一旦被攻破,宿主机基本等于沦陷。能用--cap-add单独加能力就不要开全量特权。
2.3 网络模式与平台差异
Docker 的网络模式有 bridge、host、none 和 overlay 等。默认的 bridge 模式下容器有自己的 IP,端口要映射出来才能被外部访问。host 模式则直接复用宿主机网络,启动时-p是不生效的,因为端口本来就监听着。如果你在容器里看到明明写了-p 8080:80,访问宿主机 8080 却不通,先看看是不是用了--network=host。
另外就是 Docker Desktop 的场景。Windows 和 macOS 上运行 Docker Desktop 存在一个非常常见的启动失败提示:virtualization support 没有检测到。这一般不是镜像参数问题,而是宿主机没有开启 CPU 虚拟化。用户可以进 BIOS 把 Intel VT-x 或者 AMD-V 打开,或者在 Windows 功能里开启“虚拟机平台”,之后重启 Docker Desktop 多半就能通过“WSL 2”后端顺利启动。对照表里的所有命令在 Docker Desktop 上都能跑,只是文件挂载路径要注意 Windows 盘符的写法,比如用D:/data而不是D:\data。
3. 常用镜像启动参数对照表
这一节是重头戏。每个镜像我都会给一个典型的启动命令,再配上参数说明和容易忽略的点。所有命令都基于 Linux 环境,Windows Docker Desktop 下注意路径格式即可。
3.1 MySQL 8.x:字符集和时区最容易被忽略
MySQL 的镜像启动相对简单,核心参数集中在账号、密码、数据库名和数据目录。国内业务基本都要考虑 utf8mb4,因为utf8在 MySQL 里实际是utf8mb3,存不了 emoji 和部分生僻字。用utf8mb4基本是标准做法。
docker run -d \ --name mysql8 \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORD=Root@123456 \ -e MYSQL_DATABASE=appdb \ -e MYSQL_USER=app \ -e MYSQL_PASSWORD=App@123456 \ -v /data/mysql8:/var/lib/mysql \ -v /data/mysql8/conf:/etc/mysql/conf.d \ --restart=unless-stopped \ mysql:8.0 \ --character-set-server=utf8mb4 \ --collation-server=utf8mb4_general_ci \ --default-time-zone='+8:00'注意,启动命令最后面的参数不是-e,而是直接跟在镜像名后面的 mysqld 参数。很多人第一次看到这种写法会愣住:这不就相当于容器启动时给 MySQL 进程传的命令行参数吗?没错,镜像的 entrypoint 脚本会把镜像名之后的参数透传给 mysqld,因此--character-set-server能生效。
如果想把 MySQL 的配置写成外部配置,建议挂到/etc/mysql/conf.d下,文件后缀必须是.cnf,并且在配置文件里写[mysqld]段落。我踩过一个坑:写文件时忘了加段落标题,结果整个配置被忽略,启动也完全正常,但一切默认参数都没生效。MySQL 不会因为你乱写配置就报错,它只会默默忽略,这种“软失败”特别容易坑人。
3.2 Redis:密码、持久化和保护模式
Redis 的镜像本身很小,但参数选择直接决定你的数据安全和可用性。裸启一个 Redis 其实只要一行docker run -d -p 6379:6379 redis,但这样跑起来之后默认没有密码,还允许远程连接,这在有公网 IP 的云服务器上非常危险。之前看到过不少蜜罐日志,扫描器最热衷于扫 6379 端口,碰到没密码的 Redis 直接做免密登录然后写计划任务,所以密码和绑定地址必须给足。
docker run -d \ --name redis7 \ -p 6379:6379 \ -v /data/redis7:/data \ -e REDIS_PASSWORD=Redis@123456 \ --restart=unless-stopped \ redis:7.0 \ redis-server --appendonly yes --requirepass Redis@123456这里同样要理解“镜像名后面的参数会直接交给 redis-server”。如果你在-e里给了REDIS_PASSWORD,但启动命令时不带--requirepass,实际上容器内不会自动读取这个变量。很多镜像会提供辅助脚本,但 Redis 官方镜像更多是“参数透传”的模式,环境变量更像是一种约定,而不是内置行为。所以我更推荐直接用命令行参数,这样诚实且没有歧义。
要是启用 Redis 主从或者哨兵,命令里的参数还要加上--replicaof和--masterauth。我在部署一主一从时,最容易踩的坑是主库设置了密码但从库没配--masterauth,结果从库日志里反复刷MASTER <-> REPLICA sync started: Non blocking connect,连接一直卡住。这时候先不要怀疑网络,大概率是密码没对老。主从同步失败时,Redis 的INFO replication会显示master_link_status:down,这个指标一定要纳入监控。
3.3 Nginx:静态资源与反向代理的挂载差异
Nginx 镜像算是参数组合比较灵活的,核心在于挂载配置文件和 HTML 目录。如果你只是临时跑一个静态网站,最简单的命令是:
docker run -d \ --name nginx \ -p 80:80 \ -v /srv/www:/usr/share/nginx/html:ro \ -v /etc/localtime:/etc/localtime:ro \ --restart=unless-stopped \ nginx:stable-alpine这里:ro表示只读,宿主机内容变化会同步反映到容器内,很适合前端改完代码刷新就生效的场景。但如果你需要自定义 Nginx 配置,建议把整个/etc/nginx/conf.d单独挂出来。这里有个坑:如果你只挂载了单个default.conf,而没有挂载/etc/nginx/nginx.conf,镜像内部的主配置文件可能会引用/etc/nginx/conf.d/*.conf,这个没问题;但如果你把整个/etc/nginx挂载成宿主机目录,宿主机目录是空的话,Nginx 会因为找不到 MIME 类型而报错。
反向代理场景下还常需要设置X-Real-IP和X-Forwarded-For,这些一般写在配置里,不属于启动参数,但容器化的 Nginx 和外部负载均衡配合时需要注意,如果 Nginx 前面还有一层 LB,最好在配置里显式信任对应网段的代理,否则拿到的客户端 IP 全是负载均衡的 IP。
3.4 PostgreSQL:挂载目录归属和初始化脚本
PostgreSQL 在生产环境的使用率非常高,镜像启动方式和 MySQL 类似,但有几个细节不一样。首先是端口,默认5432;其次是数据目录,默认是/var/lib/postgresql/data,不是/var/lib/pgsql。如果你照抄 MySQL 的习惯,把宿主机目录挂到/var/lib/postgresql,那数据其实不会持久化,因为 PostgreSQL 的PGDATA指向的是/var/lib/postgresql/data。
典型的启动命令:
docker run -d \ --name pg16 \ -p 5432:5432 \ -e POSTGRES_USER=app \ -e POSTGRES_PASSWORD=App@123456 \ -e POSTGRES_DB=appdb \ -v /data/pg16:/var/lib/postgresql/data \ --restart=unless-stopped \ postgres:16这里我吃过一个大亏:宿主机/data/pg16目录如果不存在或者属主不是 UID 999(PostgreSQL 镜像内部用户),容器会启动失败,日志里出现could not change permissions of directory "/var/lib/postgresql/data": Operation not permitted。解决办法是提前创建目录并chown 999:999 /data/pg16,或者直接借用官方镜像里的docker run -it --rm -v /data/pg16:/var/lib/postgresql/data postgres:16 chown 999:999 /var/lib/postgresql/data来修正。
PostgreSQL 还有一个非常实用的参数:把初始化 SQL 脚本放到/docker-entrypoint-initdb.d/目录,容器首次启动会自动按字母序执行。这个机制适合初始化表结构、扩展插件和默认数据,但注意它只在数据目录为空时执行;一旦数据目录里有数据,脚本不会再次执行。想做后续迁移,还是老老实实用psql或者专门的迁移工具。
3.5 MongoDB:单机和副本集的启动差异
MongoDB 单机版很简单,但如果你要跑副本集,参数就完全不是一回事了。单机版重点是数据目录和认证方式:
docker run -d \ --name mongo7 \ -p 27017:27017 \ -e MONGO_INITDB_ROOT_USERNAME=admin \ -e MONGO_INITDB_ROOT_PASSWORD=Admin@123456 \ -v /data/mongo7:/data/db \ --restart=unless-stopped \ mongo:7注意 MongoDB 出于安全机制,一旦设置了MONGO_INITDB_ROOT_USERNAME,默认就开启了 auth,即使你只创建了 root 用户,连接字符串里也要带用户名密码。我见过有人想临时免密调试,在启动时故意不写这个变量,结果 MongoDB 直接以不带认证的方式启动,这样数据基本等于对局域网透明。
副本集启动时,需要加--replSet参数,并先启动一个节点,再进入容器执行rs.initiate()。如果你的副本集要在 Docker 容器里固定节点名,建议使用--hostname参数而不是随机容器 ID,否则副本集配置里的 host 会变得非常混乱,客户端连接时会拿到一个无法解析的容器 ID。
3.6 RabbitMQ:管理插件和端口组合
RabbitMQ 需要同时关注服务端口5672和管理界面端口15672。如果只映射了5672,你会发现客户端连得上,但浏览器里打开管理界面 15672 是拒绝连接,因为那是个独立的 web port,不是从 5672 转来的。启动命令:
docker run -d \ --name rabbitmq \ -p 5672:5672 \ -p 15672:15672 \ -e RABBITMQ_DEFAULT_USER=admin \ -e RABBITMQ_DEFAULT_PASS=Admin@123456 \ -v /data/rabbitmq:/var/lib/rabbitmq \ --restart=unless-stopped \ rabbitmq:3-management这里必须用带有management标签的镜像,如果你用的是纯rabbitmq:3,即使端口映射正确,管理界面也不存在。另一个问题是:rabbitmq:3-management镜像默认启用了管理插件,但你如果需要额外插件,比如rabbitmq_delayed_message_exchange,就得在容器内执行rabbitmq-plugins enable,但这个操作在容器重建后会丢失,所以更建议基于该镜像自定义 Dockerfile,把插件装进镜像。
3.7 Elasticsearch:锁内存和节点配置缺一不可
Elasticsearch 在容器里跑起来经常在启动阶段就翻车,最经典的错误是max virtual memory areas vm.max_map_count [65530] is too low。这是宿主机内核参数,不是容器参数,你需要先在宿主机上执行sysctl -w vm.max_map_count=262144,否则 ES 直接拒绝启动。
常规启动命令:
docker run -d \ --name es8 \ -p 9200:9200 \ -p 9300:9300 \ -e discovery.type=single-node \ -e ES_JAVA_OPTS="-Xms512m -Xmx512m" \ -e xpack.security.enabled=false \ -v /data/es8:/usr/share/elasticsearch/data \ --ulimit nofile=65535:65535 \ --ulimit memlock=-1:-1 \ --restart=unless-stopped \ docker.elastic.co/elasticsearch/elasticsearch:8.10.0很多人不了解--ulimit的意义。ES 需要文件描述符数量很大,同时内存锁需要不受限制,否则它会一直告警内存 swapping 导致性能下降。memlock=-1:-1的作用是不限制锁内存大小,这在高并发写入时尤其重要。
如果你跑的是 7.x 以上的版本,默认开启安全认证,访问9200会返回 401。我用xpack.security.enabled=false临时禁掉它是为了方便本地开发,但是任何公网环境都不要这样做,否则你将收获一个被加密挖矿程序塞满的服务器。生产环境请配置真实证书和密码,至少也要做好网络隔离。
3.8 MinIO:端口映射与管理凭据
MinIO 是一个非常轻量的对象存储服务,常用来替代开发环境里的 OSS。它的默认端口是9000(API)和9001(控制台)。如果你只映射9000,浏览器里访问控制台会 404。启动命令:
docker run -d \ --name minio \ -p 9000:9000 \ -p 9001:9001 \ -e MINIO_ROOT_USER=minioadmin \ -e MINIO_ROOT_PASSWORD=Minio@123456 \ -v /data/minio:/data \ --restart=unless-stopped \ minio/minio server /data --console-address ":9001"注意MINIO_ROOT_USER和MINIO_ROOT_PASSWORD最少各 3 位和 8 位,过短会启动失败。这个账号体系从 2022 年起就没有默认的minioadmin/minioadmin全新裸跑逻辑了,必须显式设置。
对象存储最怕的就是数据没落盘。如果你不挂载/data,容器删掉后所有桶和对象全部丢失。这里建议在挂载目录外层做好定期快照或者备份策略,因为 MinIO 本身是单机的话并不提供多副本冗余。
3.9 Kafka:靠 Zookeeper 还是 KRaft
Kafka 镜像的启动参数变动比较大,因为从 Kafka 3.x 开始引入了 KRaft 模式不再强依赖 Zookeeper。但实践中很多团队还在用 Kafka + Zookeeper 的组合,这里给一个 KRaft 单机模式的启动命令,更简单也更适合开发环境:
docker run -d \ --name kafka \ -p 9092:9092 \ -e KAFKA_NODE_ID=1 \ -e KAFKA_PROCESS_ROLES=broker,controller \ -e KAFKA_LISTENERS=PLAINTEXT://:9092,CONTROLLER://:9093 \ -e KAFKA_ADVERTISED_LISTENERS=PLAINTEXT://你的服务器IP:9092 \ -e KAFKA_CONTROLLER_LISTENER_NAMES=CONTROLLER \ -e KAFKA_LISTENER_SECURITY_PROTOCOL_MAP=CONTROLLER:PLAINTEXT,PLAINTEXT:PLAINTEXT \ -e KAFKA_CONTROLLER_QUORUM_VOTERS=1@localhost:9093 \ -e CLUSTER_ID=随便填个UUID \ -v /data/kafka:/var/lib/kafka/data \ apache/kafka:3.7.0这里面最坑的参数是KAFKA_ADVERTISED_LISTENERS。如果这个地址填写的是localhost而你的客户端在宿主机外面连接,生产者会成功连接 bootstrap 然后立刻断掉,因为 Kafka 返回给你的 broker 地址是localhost:9092。把这里改成能路由到容器的实际 IP,或者至少改成宿主机 IP,才能真正连通。
3.10 Consul:端口和启动模式
Consul 作为注册中心也很常见,启动参数主要在-server、-bootstrap-expect和 CSI 相关。本地开发跑一个:
docker run -d \ --name consul \ -p 8500:8500 \ -p 8600:8600/udp \ -v /data/consul:/consul/data \ -e CONSUL_LOCAL_CONFIG='{"leave_on_terminate": true, "disable_host_node_id": false}' \ consul:1.16 \ agent -server -bootstrap-expect=1 -ui -client=0.0.0.08600/udp是 DNS 服务的端口,做服务发现时可能用到,但很多人漏掉了udp后缀,导致端口映射无效。Consul 在单节点开发模式中-bootstrap-expect=1可以简化选举流程,生产环境至少三个节点,不要在生产环境用-bootstrap-expect=1。
4. 启动参数的常见坑与排查实录
4.1 端口不生效:先查绑定地址和防火墙
容器启动后,docker ps显示端口映射了,但从外部访问一直超时。这种情况先别急着怀疑 Docker,先看宿主机端口有没有监听,ss -lntp看看,再检查防火墙和云服务商的安全组规则。有时候你明明映射了3306:3306,但安全组没放行 3306,那当然是通不了的。
另一个经常被忽略的是“容器进程绑定在容器内部 127.0.0.1”。有一些镜像默认只监听回环地址,比如 Redis 默认 bind 127.0.0.1,容器外的请求会被拒绝。用docker exec -it redis redis-cli -h 127.0.0.1 ping能通,但宿主机用 redis-cli 连不上。这时候要改配置文件或指定--bind *之类,但前提是网络安全性可控。
4.2 挂载目录没权限:ExitCode 1 与根因
几乎每个镜像的报错日志里都可能出现 Permission denied,这是挂载目录权限不对导致的。容器内的进程有它自己固定的 UID,MySQL 的 mysql 用户 UID 是 999,PostgreSQL 是 999,Redis 是 999,但不同镜像和版本可能并不一样。解决办法很简单:创建目录后直接 chown 给对应 UID,比如chown -R 999:999 /data/mysql8。你也可以用-u 0强制容器以 root 运行,但这会让日志目录、缓存目录全部被 root 创建,后面调整权限又麻烦,不太推荐。
如果你不确定镜像内部进程的用户 UID,可以在启动时用docker run --rm -it 镜像名 id查一下,那是最快的确认方式。
4.3 容器一启动就退出:看日志的正确姿势
容器反复重启或者 Exited,第一反应一定是docker logs。但这里也有技巧,使用docker logs --tail 100 --timestamps 容器名可以带时间戳看最近 100 行,很多报错信息就在最后几行里。我再强调一下:不要只看容器状态,看不到任何输出就到处搜错误码,直接把完整日志翻出来是最高效的。
比如 MySQL 启动失败,日志里往往带有[ERROR] [MY-010262]类似编号;Redis 会打印FATAL CONFIG FILE ERROR并指出行号;PostgreSQL 会输出FATAL: data directory "/var/lib/postgresql/data" has wrong ownership。日志给出的信息量远远超出你的想象。
4.4 时区、字符集、内存配置的国内实战
时区和字符集在国内场景下几乎是必踩项。很多镜像默认 UTC 时区,业务日志时间戳和数据库时间会差 8 个小时。单独一个容器还好说,如果有多个业务模块同时依赖数据库,那查询结果可能就会出现“早上 8 点前数据查不到”的诡异现象。最简单的做法是在启动参数里加上-v /etc/localtime:/etc/localtime:ro,这会把宿主机时区直接映射进容器。有一些镜像还支持环境变量TZ=Asia/Shanghai,但并不是所有镜像都内置了 tzdata,所以文件挂载方式更通用。
Elasticsearch 的 JVM 参数也是一个高频问题。默认的 heap 可能是物理内存的一半,如果服务器内存小,就容易容器被 OOM Killer 干掉。生产环境请把ES_JAVA_OPTS固定在一个合理范围内,同时关注容器docker stats展示的内存使用,避免超卖导致整台机器卡死。
4.5 镜像拉取慢与国内源配置
Docker Hub 的拉取速度直接影响部署效率,国内环境下这个问题尤其明显。推荐的方案是配置镜像加速器,几乎所有主流云平台都提供 registry mirror,你可以把dockerd的/etc/docker/daemon.json里加registry-mirrors配置,然后重启 Docker。这里有几个常见误区:第一,镜像加速器只对 Docker Hub 有效,拉取quay.io或ghcr.io的镜像依然可能很慢;第二,部分加速器因为需求过大不再开放,不一定能长期稳定使用,最好多配几个作为备选。
还有一件事很容易踩坑:很多同学以为加速器配置好之后,镜像名不用改,但实际上部分加速器在拉取第三方仓库时不会自动补全镜像名。如果你遇到docker pull一直卡很久,可以尝试明确指定镜像的完整仓库地址,比如docker pull docker.io/library/nginx:latest,这样 DNS 解析和路由反而更快,经验之谈。
5. 从零散命令到 docker compose:把对照表固化下来
5.1 一个 compose 示例:MySQL + Redis + Nginx
到了多容器协作阶段,手敲一大串docker run就不是不能用了,而是不好维护了。我会建议把常用启动参数转成docker-compose.yml,放进项目仓库里,团队成员拉下来一条命令就能跑出完整环境。下面是一个常见组合:
version: "3.8" services: mysql8: image: mysql:8.0 container_name: mysql8 restart: unless-stopped ports: - "3306:3306" environment: MYSQL_ROOT_PASSWORD: Root@123456 MYSQL_DATABASE: appdb MYSQL_USER: app MYSQL_PASSWORD: App@123456 command: - --character-set-server=utf8mb4 - --collation-server=utf8mb4_general_ci - --default-time-zone=+8:00 volumes: - /data/mysql8:/var/lib/mysql redis7: image: redis:7.0 container_name: redis7 restart: unless-stopped ports: - "6379:6379" command: - redis-server - --appendonly yes - --requirepass Redis@123456 volumes: - /data/redis7:/data nginx: image: nginx:stable-alpine container_name: nginx restart: unless-stopped ports: - "80:80" volumes: - /srv/www:/usr/share/nginx/html:ro - /data/nginx/conf.d:/etc/nginx/conf.d:ro这里用了command列表而不是字符串,好处是避免引号转义混乱。如果你直接写command: redis-server --appendonly yes --requirepass Redis@123456,有时候 YAML 解析会把密码里的特殊符号搞出问题,用列表形式更稳。
5.2 为什么我不建议继续手敲一长串启动参数
原因很简单:手敲命令无法被版本管理,也不可审查。我曾经在同一个服务器上启动过多个相同镜像,但参数却不一样,后来有人问起某个容器的具体配置,只能靠管它历史命令去猜。转为 compose 文件后,环境配置有了单一事实来源,代码 review 也能看到你改了什么参数。
还有一点,docker compose run这种临时执行方式在有volumes和depends_on的场景里会非常方便。比如要给 MySQL 灌数据,可以用docker compose run --rm mysql8 bash -c 'mysql -hmysql8 ...',它会把服务名自动解析成容器 IP,不用手动维护 IP 地址。
5.3 迁移与备份时的注意事项
打包docker run参数容易漏,打包整个 compose 文件也有前提。数据卷目录最好用相对路径,这样在另一台服务器上拉取项目后可以直接构建。但请注意,compose 文件里的image默认是公共仓库的镜像,如果你们内网环境无法直连 Docker Hub,建议在服务器上先把镜像docker pull下来,再docker save打包搬到内网。
实际备份时不要只备份数据卷目录,还要把 compose 文件和.env文件一起备份,因为环境变量里的密码、端口都是恢复服务的关键。我一般会把所有配置放在同一个目录里,整个目录打包,恢复时先恢复目录,再docker compose up -d即可。
最后再分享一个我自己的习惯:每次启动一个新镜像,先在测试环境完整跑一遍参数,然后特意删掉容器重新创建一次,确认数据是否真的持久化。如果没有挂对数据卷,你会发现容器删掉之后一切数据都没了,这个测试流程花不了两分钟,却能避免你在半夜上线时才发现数据库是个一次性容器。容器化部署越来越默认化,但启动参数并没有想象中那么随意。把这张对照表存下来,改到你的项目里,当你能不看文档直接给团队讲清楚每个参数为什么这么写的时候,说明你已经真正理解了 Docker 的运行逻辑。