跳到主要内容
DevOps2026-09-108 分钟阅读

docker run 转 docker-compose 完全指南:为什么转、怎么转、参数对照表

为什么值得把 docker run 迁到 compose

一条 docker run -d --name web -p 8080:80 -v ... 跑起来的服务,本质是配置只存在于你敲过的命令和记忆里。换台机器、半年后重建、同事接手,都得靠翻 shell history 还原。compose 把这一切变成一个可版本管理的 YAML 文件:

裸 docker rundocker 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 rundocker-compose.yml说明
nginx:1.25image: nginx:1.25镜像
--name webcontainer_name: web(或省略用 service 名)建议省略,让 compose 用 service 名管理
-p 8080:80ports: ["8080:80"]端口映射,注意 YAML 里要加引号
-v ./site:/usr/share/nginx/htmlvolumes: ["./site:/usr/share/nginx/html"]绑定挂载
-v cache:/var/cachevolumes: ["cache:/var/cache"]具名卷(还要在顶层 volumes 声明)
-e TZ=Asia/Shanghaienvironment: ["TZ=Asia/Shanghai"]环境变量
--env-file .envenv_file: [.env]环境变量文件
--restart alwaysrestart: always重启策略
--network mynetnetworks: [mynet] + 顶层声明网络
--network hostnetwork_mode: host特殊网络用 network_mode
-u 1000:1000user: "1000:1000"运行用户
-w /appworking_dir: /app工作目录
--entrypoint "sh -c ..."entrypoint: ...入口点
-m 512mmem_limit: 512m内存限制
--cpus 1.5cpus: "1.5"CPU 限制
镜像后的命令command: ...容器命令
-d / -it / -i / -t无compose 默认分离式运行,直接省略
--label a=blabels: ["a=b"]标签
--hostname dbhostname: db主机名
--platform linux/arm64platform: linux/arm64Apple Silicon 上跑 x86 镜像常见
--privilegedprivileged: true先停下来想想:真的需要特权吗
--read-onlyread_only: true只读根文件系统
--mount type=bind,source=/a,target=/bvolumes: ["/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。这不是丢失功能,是模型不同。

老教程里的 --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 去别的机器上跑。思路就是把上表倒过来走一遍:

  1. image: → 命令末尾的镜像名;
  2. ports / volumes / environment 逐条变回 -p / -v / -e;
  3. network_mode: host → --network host;service 下的 networks: 成员 → --network <名>;
  4. restart: / user: / working_dir: / entrypoint: / mem_limit: / cpus: 各回各家;
  5. command: 列表形按顺序拼在镜像后面(列表的每一项是 exec 形的一个 argv,空格参数记得加引号);
  6. depends_on / healthcheck / build: 没有 run 等价物——前两者靠启动顺序和人工确认,build: 要先 docker compose build 出镜像再 run。

迁移验收清单

转换完别急着 up,过一遍这五步:

  1. 旧容器退场:docker stop <旧名> && docker rm <旧名>——同名冲突报错见 容器名冲突排查;端口还没释放报错见 端口占用排查。
  2. 警告区清零:转换器列出的每条警告要么手动补映射,要么确认可丢弃(如 --rm)。
  3. diff 运行配置:docker inspect <旧容器> 对比生成 YAML 的 ports/volumes/env,重点看漏没漏。
  4. 数据卷接管:旧匿名卷里的数据先 docker run --rm --volumes-from <旧容器> -v $(pwd):/backup alpine tar czf /backup/data.tar.gz -C <容器路径> . 导出来,再灌进新具名卷。
  5. 幂等验证: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 命令速查。相关阅读:容器名冲突排查、端口占用排查。访问 首页 查看更多开发者工具。