docker run 转 docker-compose 完全指南:为什么转、怎么转、参数对照表
为什么值得把 docker run 迁到 compose
一条 docker run -d --name web -p 8080:80 -v ... 跑起来的服务,本质是配置只存在于你敲过的命令和记忆里。换台机器、半年后重建、同事接手,都得靠翻 shell history 还原。compose 把这一切变成一个可版本管理的 YAML 文件:
| 裸 docker run | docker compose |
|---|---|
| 配置在 shell history 里 | 配置在 docker-compose.yml 里,进 git |
| 多容器要手写依赖顺序 | depends_on 声明依赖 |
| 重建 = 删旧容器再拼长命令 | docker compose up -d 幂等 |
| 停一半的服务靠记忆清理 | docker compose down 一次清干净 |
| 端口/卷散落各处 | 一个文件看全貌 |
三十秒完成转换
直接把 docker run 命令粘进 docker run 转 compose 在线转换器,得到 compose 文件后保存运行:
docker compose up -d
转换全程在你的浏览器本地完成——包含密码、内网地址的命令不会发到任何服务器。
粘贴时几种形态都被容忍:sudo docker container run 前缀会被剥掉;多行 \ 续行直接粘;--name=web 这种 --flag=value 内联写法也认;连 flag 写在镜像名后面(docker run nginx -p 80:80,docker 确实允许)也能正确归位。
参数逐项对照表
| docker run | docker-compose.yml | 说明 |
|---|---|---|
nginx:1.25 | image: nginx:1.25 | 镜像 |
--name web | container_name: web(或省略用 service 名) | 建议省略,让 compose 用 service 名管理 |
-p 8080:80 | ports: ["8080:80"] | 端口映射,注意 YAML 里要加引号 |
-v ./site:/usr/share/nginx/html | volumes: ["./site:/usr/share/nginx/html"] | 绑定挂载 |
-v cache:/var/cache | volumes: ["cache:/var/cache"] | 具名卷(还要在顶层 volumes 声明) |
-e TZ=Asia/Shanghai | environment: ["TZ=Asia/Shanghai"] | 环境变量 |
--env-file .env | env_file: [.env] | 环境变量文件 |
--restart always | restart: always | 重启策略 |
--network mynet | networks: [mynet] + 顶层声明 | 网络 |
--network host | network_mode: host | 特殊网络用 network_mode |
-u 1000:1000 | user: "1000:1000" | 运行用户 |
-w /app | working_dir: /app | 工作目录 |
--entrypoint "sh -c ..." | entrypoint: ... | 入口点 |
-m 512m | mem_limit: 512m | 内存限制 |
--cpus 1.5 | cpus: "1.5" | CPU 限制 |
| 镜像后的命令 | command: ... | 容器命令 |
-d / -it / -i / -t | 无 | compose 默认分离式运行,直接省略 |
--label a=b | labels: ["a=b"] | 标签 |
--hostname db | hostname: db | 主机名 |
--platform linux/arm64 | platform: linux/arm64 | Apple Silicon 上跑 x86 镜像常见 |
--privileged | privileged: true | 先停下来想想:真的需要特权吗 |
--read-only | read_only: true | 只读根文件系统 |
--mount type=bind,source=/a,target=/b | volumes: ["/a:/b"] | 自动折叠为短语法;tmpfs 类型进警告区 |
未识别的 flag(如 --device) | 进警告区 | 转换永远有产出,未映射项逐条列出 |
一个值得知道的实现取向:遇到不认识的参数不报错、不中断,而是收集进输出下方的警告区。因为一条有 15 个 flag 的命令,14 个转好、1 个漏掉,远好于整个拒绝转换——漏掉的那个肉眼补就行。
生成的 YAML 有哪些讲究
转换器的输出不是 flag 的逐字搬运,有几处刻意的工程决策,看懂它们也就看懂了 compose 的坑:
端口一律加引号。 -p 8080:80 写成 ports: ["8080:80"] 而不是 - 8080:80——旧版 YAML 解析器会把 25:25 这类值当成六十进制数解析(一个真实存在过的 YAML 1.1 陷阱),引号是最稳的防御。写 YAML 时的其他引号陷阱见 YAML 转 JSON 指南。
environment 用列表形。 生成的是 environment: ["TZ=Asia/Shanghai"] 而不是 map 形 TZ: Asia/Shanghai。原因:环境值里带 #、引号、空格时,map 形要在 YAML 层再转义一次,两层转义叠起来极易出错;列表形把 KEY=VALUE 当一个字符串整体引用,只有一层转义。
-e 与 env_file 分家。 -e KEY=VALUE 进 environment;--env-file .env 进 env_file,两者可以在同一个 service 里共存。注意 env_file 的路径相对 compose 文件解析,.env 记得进 .gitignore——可用 gitignore 生成器 补全条目。
自定义网络自动补顶层声明。 --network mynet 不仅生成 service 下的 networks: [mynet],还会在文件尾部补:
networks:
mynet:
external: true
external: true 表示这个网络在 compose 之外已存在(用 docker network create mynet 建的)。如果删掉它,compose 会试图自己创建一个 项目名_mynet 网络——容器之间就互相找不到了。
service 名推导规则。 优先用 --name(清洗成合法字符);没给则从镜像名取最后一段并去 tag:registry.example.com/team/app:v2 → app。所以同一份 compose 里转多条命令时,留意 service 名撞车。
转换时的三个经典坑
1. 裸 -e 透传没法转
docker run -e DEBUG nginx # 引用宿主机的 DEBUG 变量
compose 文件是静态的,表达不了「引用宿主机变量」。改写成完整形式 -e DEBUG=value,或统一走 env_file。
2. 匿名卷没有等价物
docker run -v /var/log nginx(只有容器路径)创建的是匿名卷,compose 需要具名卷或绑定路径。转换器会把它列进警告区——给它起个名字(logdata:/var/log)并在顶层声明 volumes。
3. --rm 的语义变了
裸 run 的 --rm 是「退出即删容器」;compose 的生命周期由 down 管理,不需要也不支持 --rm。这不是丢失功能,是模型不同。
4. --link 直接删,别犹豫
老教程里的 --link db:db 在 compose 里完全多余——同一个 compose 项目下的 service 按名字互相可达。转换时看到 --link,删掉即可;如果它来自外部容器的连接需求,正确的做法是把那个容器也拉进同一个 compose,或共用 external 网络。
5. --restart 四个取值要分清
no / always / on-failure / unless-stopped 四者的差异只在「Docker 守护进程重启后」和「手动 stop 后」的行为。生产服务常用 unless-stopped:宿主机重启自动拉起,但你手动 docker stop 它不会诈尸——always 会。顺带一提,依赖容器定期重启来做清理任务的场景,更常见的做法是宿主机的 cron 跑 docker compose restart,cron 语法见 Cron 表达式详解。
反向操作:compose 导出回 docker run
偶尔有反向需求:把一个 service 拆回一条 docker run 去别的机器上跑。思路就是把上表倒过来走一遍:
image:→ 命令末尾的镜像名;ports/volumes/environment逐条变回-p/-v/-e;network_mode: host→--network host;service 下的networks:成员 →--network <名>;restart:/user:/working_dir:/entrypoint:/mem_limit:/cpus:各回各家;command:列表形按顺序拼在镜像后面(列表的每一项是 exec 形的一个 argv,空格参数记得加引号);depends_on/healthcheck/build:没有 run 等价物——前两者靠启动顺序和人工确认,build:要先docker compose build出镜像再 run。
迁移验收清单
转换完别急着 up,过一遍这五步:
- 旧容器退场:
docker stop <旧名> && docker rm <旧名>——同名冲突报错见 容器名冲突排查;端口还没释放报错见 端口占用排查。 - 警告区清零:转换器列出的每条警告要么手动补映射,要么确认可丢弃(如
--rm)。 - diff 运行配置:
docker inspect <旧容器>对比生成 YAML 的 ports/volumes/env,重点看漏没漏。 - 数据卷接管:旧匿名卷里的数据先
docker run --rm --volumes-from <旧容器> -v $(pwd):/backup alpine tar czf /backup/data.tar.gz -C <容器路径> .导出来,再灌进新具名卷。 - 幂等验证:
docker compose up -d之后docker compose down && docker compose up -d再跑一轮,确认可重复。
多容器场景:转换后顺手做依赖编排
把每条 docker run 各转成一个 service 后,加依赖关系:
services:
app:
image: myapp:2.0
depends_on:
db:
condition: service_healthy
db:
image: postgres:16
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
retries: 10
service_healthy 条件(compose v2.20+ 支持)解决「数据库没就绪应用先启动」的经典时序问题——这是裸 run 时代只能靠 sleep 脚本解决的痛点。
翻译表:run 旗标到 compose 键
覆盖九成转换的机械映射:-p 8080:80 → ports: ["8080:80"](宿主:容器);-v /data:/var/lib/app → volumes(bind 语法原样);--name 与 --network → 顶层服务名与 networks 成员;-e KEY=val → environment(列表或 map——map 形态对漏值 typo 更稳);--restart unless-stopped → restart 直接映射。需要判断的旗标:--link(已弃——compose 网络按名可达;删)、--entrypoint(compose 支持,但需要覆盖入口点时先问是镜像错了还是用法错了)、--rm(compose 无意义——生命周期被管理)。模式:机械转换后把 compose 文件当新文档重读一遍——忘了质疑的旗标在第二遍里浮出来。
常见问题
Q:转出来的文件需要 version 字段吗? 不需要。现代 docker compose(v2 CLI)中 version 已废弃,写了反而有 warning。转换器输出的就是无 version 的现代格式。
Q:compose 文件名必须是 docker-compose.yml 吗?
默认识别 compose.yaml / docker-compose.yml;用 -f 可以指定任意文件:docker compose -f prod.yml up -d。
Q:已有容器在跑,直接 up 会冲突吗?
会——同名容器冲突(container name already in use,排查见这篇)。先 docker stop/rm 旧容器,或给 service 换名过渡。端口占用冲突的处理见端口占用排查。
Q:service 名和 container_name 有什么区别?
service 名是 compose 内部的管理单位(网络里按它互相访问);container_name 是容器在 Docker 层面的固定名字。固定 container_name 会让 docker compose up --scale web=3 无法工作(名字唯一),所以转换器只在显式给了 --name 时才写入,一般建议删掉交给 compose 管理。
Q:build: 和 image: 能同时写吗?
能。build 描述怎么构建,image 给构建产物命名打 tag。docker run 没有 build 的等价物——docker build 是独立命令。如果镜像需要本地构建而非拉取,参考 Dockerfile 生成器指南 补齐构建侧。
Q:转换后 docker compose up 报网络不存在?
生成的 networks: external: true 假定网络已存在。先 docker network create mynet,或者删掉 external: true 让 compose 自建(但名字会变成 项目名_mynet)。
排查注记
对照表之外专门写「生成的 YAML 有哪些讲究」,是因为输出刻意不做 flag 的逐字搬运:端口一律加引号防的是旧版 YAML 解析器把 25:25 当六十进制数的真实陷阱,environment 用列表形是为了把转义压到一层。「遇到不认识的参数不报错、收进警告区」也是明说的取舍——15 个 flag 转好 14 个,远好于整个拒绝转换。三个经典坑里 --rm 那条特意注明「不是丢失功能,是模型不同」:compose 的生命周期由 down 管理,不需要退出即删。
ToolVault 工具匣 出品的开发者指南。相关工具:docker run 转 compose 转换器、Dockerfile 生成器、Linux 命令速查。相关阅读:容器名冲突排查、端口占用排查。访问 首页 查看更多开发者工具。
相关工具
相关文章
Linux crontab 定时任务实战:命令、日志与排查
crontab 不只是写表达式:三个必会命令、/etc/cron.d 与用户 crontab 的区别、任务没跑的五步排查法、PATH/时区/% 转义、MAILTO,以及秒级任务的正确解法。
Permission denied (publickey) 怎么解决?Git 推送失败的 6 种原因与修复
git clone/push 报 Permission denied (publickey) fatal: Could not read from remote repository?覆盖公钥没生成、没加载进 agent、没添加到 GitHub/Gitee、多账号配错密钥、deploy key 权限、remote URL 写错六种根因,附 ssh -v 诊断方法。
npm ERR! ERESOLVE:peer dependency 冲突的四种解决策略
npm install 报 ERESOLVE unable to resolve dependency tree 怎么办?理解 peer dependency 的设计意图,掌握四种解决策略(版本修复 / legacy-peer-deps / overrides / dedupe)及其适用场景与风险。