☰
Docker 参数实战全解析:从 docker run 到 Dockerfile 与 Compose 配置
2026/10/8 9:00:01 网站建设 项目流程

1. 先把 Docker 的参数逻辑理顺:镜像、容器、守护进程三层关系

在真正开始记 docker run 那一大堆--xxx之前,我强烈建议你先明白一件事:Docker 里几乎所有参数都不是在给命令"加选项",而是在给"容器运行环境"和"进程启动方式"写一份完整配置。

docker CLI 只是客户端,你敲的每一条命令都会发给 dockerd 守护进程,由它调用 containerd、runc 等底层组件完成容器创建、资源隔离和文件系统挂载。这意味着命令行里的参数绝大多数会被组装成一份容器配置描述,再交给运行时执行。这个视角一旦建立,看到--cpus会想到 cgroup,看到--network会想到网络命名空间,理解成本会大幅下降,记忆也不再是一堆孤立词条。

1.1 参数最终落在内核的哪些机制上

如果你把一次 docker run 拆开看,所有参数大概落在四个层面:

  • 镜像与构建层:使用哪个镜像、哪个 tag、是否拉取、是否指定平台、是否覆盖入口;
  • 容器运行时层:容器名、主机名、DNS、网络模式、端口映射、数据卷挂载、rootfs 是否只读;
  • 资源与安全层:CPU、内存、PID、文件描述符限制、capability 增删、SELinux/AppArmor 配置、是否特权模式;
  • 进程与环境层:环境变量、工作目录、运行用户、主进程命令、健康检查命令、停止信号。

这四层和 Linux 隔离机制严格对应:网络命名空间对应网络参数,mount 命名空间对应 volume 参数,cgroup 对应资源限制,用户命名空间和 capability 机制对应安全参数。所以你在宿主机上执行docker inspect看到的一堆 JSON,其实就是这四层配置的落地结果。

1.2 为什么同一个功能有多个写法

Docker 的 CLI 演进过好几轮,早期参数比较随意,一个短字符经常承载多个含义;后来为了工程化,推出了更严格、更结构化的长参数格式。最典型的就是挂载卷有-v和--mount两种写法,发布端口有-p和--publish。

我的建议是:查全量参数时优先理解长参数,因为它语义明确,还支持更多扩展子参数;短参数适合命令行快速操作,两者并不矛盾。比如-v适合临时调试,--mount适合写进脚本或 compose 配置,可读性完全不同。

2. docker run 参数全拆解:最常用的启动入口

docker run 是使用频率最高、参数也最多的命令。它本质上是 docker create 加 docker start 的合并操作。它后面的所有参数,几乎可以组合出任何你想要的容器运行方式。

2.1 前台交互与后台运行:-d、-it、--rm 的取舍

先看三组最基础、也最容易被搞混的参数:

  • -d或--detach:容器以后台守护方式运行,启动后立即返回终端,日志不会直接打到当前终端,要用docker logs查看;
  • -i与-t:-i表示保持标准输入打开,-t表示给容器分配伪终端,两者组合成-it,用于需要交互的容器;
  • --rm:容器退出时自动删除容器及其文件系统,适合临时测试,和-d不冲突。

这里有个非常经典的坑:你执行docker run -d centos会发现容器秒退。原因是 CentOS 这类系统镜像默认命令是bash,没有-it分配终端,bash 在非交互模式下读到 EOF 就退出,主进程一结束,容器生命周期就走完了。所以判断容器能否长期运行,关键看主进程是否阻塞式地活着,而不是看加没加-d。

我举个实际例子。某次我在 CI 里写docker run --rm -d --name tempworker redis:7-alpine,然后另一台机器连上去执行 redis-cli 一直失败。查日志发现容器确实在运行,但 redis 默认没配密码,监听地址也没对外开放。这种问题不是参数错误,而是参数组合后的外部表现,但确实是新手最容易在 run 阶段遇到的困惑。

2.2 资源限制参数:CPU、内存、IO 控制在生产环境不能省

生产环境容器不可能裸奔,资源限制必须设置。Docker 的 CPU、内存限制最终都会落到 cgroup 上:

  • --memory或-m:限制容器最大可用内存,例如--memory=1g。注意它会联动限制 swap,具体行为看--memory-swap;
  • --memory-swap:表示 memory+swap 的总量。比如--memory=1g --memory-swap=1g表示不允许使用 swap;--memory=1g --memory-swap=2g则允许额外使用 1g swap;
  • --cpus:限制容器可使用的 CPU 核心数,支持小数,例如--cpus=1.5;
  • --cpu-shares:相对权重值,默认 1024,值越大在 CPU 争抢时获得的时间片比例越高,但它不是绝对数量限制;
  • --pids-limit:限制容器内 PID 数量,防止某个应用 fork 失控耗尽宿主资源;
  • --ulimit:设置容器内进程的资源限制,比如--ulimit nofile=65536:65536修改文件描述符数量。

特别提醒:--cpus不等于 CPU 亲和性,它只限制 CPU 时间配额。--cpus=1.5表示容器最多使用 1.5 个 CPU 核的总计算时间,但具体跑在哪几个核上由内核调度决定。如果服务对延迟敏感,需要绑定特定核心,请用--cpuset-cpus。这两个参数经常被混为一谈。

内存方面还有个容易踩的坑:默认到上限后内核会触发 OOM,要么回收、要么杀进程。常见做法是设置 JVM-Xmx时留出 Native Memory 和 Metaspace 的余量,再用容器限制兜底。我见过太多容器完全没设内存限制,某个线上服务一波动,直接把整台宿主机打挂,教训相当深刻。

2.3 安全与特权参数:--privileged、--cap-add、--security-opt 的边界

容器不是虚拟机,默认情况下容器内用户对设备、系统调用的访问受 Linux capabilities 机制限制,Docker 会默认屏蔽 mount、sys_time、net_admin 等高危权限。常用参数如下:

  • --privileged:相当于赋予容器几乎全部 capabilities,同时放开 device cgroup 限制。能让容器随意挂载设备、操作网络、加载内核模块,但隔离性急剧下降,能不用尽量不用;
  • --cap-add:单独增加某个 capability,例如--cap-add=NET_ADMIN,容器内就可以配置 iptables 规则,但不会获得其他权限;
  • --cap-drop:移除默认赋予的 capability,常用于安全加固,比如--cap-drop=ALL先全部去掉,再按需--cap-add;
  • --security-opt:配置 SELinux 标签、AppArmor profile、no-new-privileges等,后者能防止容器内进程通过 setuid 提权;
  • --device:直接把宿主设备映射进容器;--device-cgroup-rule用来动态放行设备规则,比--privileged精细得多。

我自己的运维习惯是:能精确到 capability 就绝不用--privileged。比如某个日志采集容器需要读取块设备做统计,我会把对应设备文件映射进去,再按需加SYS_RAWIO之类能力。看起来麻烦,但这是线上风险控制的基本功。这里还要纠正一个误解:--privileged不是万能的,有些操作涉及内核模块、系统启动参数等更深层的限制,给了权限也未必成功。

2.4 端口与网络参数:-p、-P、--network、--network-alias、--add-host

端口发布是最常见的需求,几乎每个 Web 服务容器都要用到。常用参数拆解:

  • -p或--publish:格式是[宿主机IP:]宿主机端口:容器端口/协议,例如-p 8080:80、-p 127.0.0.1:8080:80/tcp。不指定协议时默认同时覆盖 tcp 和 udp;多次使用-p可以发布多个端口;
  • -P或--publish-all:把容器内通过 EXPOSE 声明的端口全部随机映射到宿主机高位端口,配合docker port查看;
  • --expose:只声明暴露哪些端口,不实际发布到宿主机,适用于同网络内容器间访问;
  • --network:指定加入哪个网络,常见有 bridge、host、none、overlay、macvlan,以及用户自定义的 bridge 网络;
  • --network-alias:容器在该网络内的额外别名,多容器通过别名解析时很实用;
  • --add-host:往容器/etc/hosts追加记录,形如--add-host=myhost:192.168.1.10。

端口发布最大的坑是:容器内服务只监听127.0.0.1时,即使写了-p也会连接失败。比如很多框架默认 localhost 模式,容器外完全进不去。排查时先进容器确认监听地址是不是0.0.0.0,不要上来就怀疑端口映射写错。另一个容易忽略的点:-p会帮你在宿主机 iptables/nftables 里加端口转发规则,如果规则有冲突或系统ip_forward未开启,映射会失败。我曾在网络隔离很严的主机上部署容器,docker run一直提示端口绑定失败,最后查出来是防火墙策略把转发禁掉了,和 Docker 本身无关。

2.5 环境变量与参数文件:-e 和 --env-file

容器内应用配置大多通过环境变量传入:

  • -e或--env:直接设置,例如-e TZ=Asia/Shanghai、-e MYSQL_ROOT_PASSWORD=xxx;
  • --env-file:从文件读取环境变量,文件每行KEY=VALUE,支持注释;
  • --env的优先级高于--env-file;
  • --env-file不会自动解析引号里的特殊字符,密码里如果包含空格、$、引号,建议用编码或安全方式处理。

环境变量是否生效完全取决于镜像的启动脚本怎么写,不要想当然认为所有镜像都接受同一套变量。比如跑 MySQL,镜像里的 docker-entrypoint.sh 会读取MYSQL_ROOT_PASSWORD、MYSQL_DATABASE这些变量;跑 Redis,则需要用命令行参数或配置文件方式设置密码,环境变量并不能直接改 redis.conf。遇到变量不生效时,第一反应应该是去看镜像的 entrypoint 脚本,而不是反复改 Docker 参数。

3. 镜像构建、拉取与传输参数:从 build 到 save/load

容器运行之外,镜像相关的参数体系同样庞大。这节我们把拉取、构建、标记、传输这些环节的参数拆开讲清楚。

3.1 docker pull 与多架构平台参数

docker pull本身参数不多,但有一个关键参数容易被忽视:

  • --platform:指定拉取远程平台的镜像,例如在 amd64 机器上临时拉取 arm64 镜像做交叉验证:docker pull --platform linux/arm64 nginx:alpine;
  • --all-tags:拉取某个仓库的全部 tag,在镜像数量可控的私有仓库里很有用,但公共镜像慎用,会拉下海量数据;
  • --quiet或-q:不打印进度信息,脚本里比较干净。

多架构镜像是当前镜像仓库的主流形态,registry 会根据请求端的架构自动返回对应 manifest。但如果你需要在构建机上为其他平台准备镜像,--platform就派上用场了。这个参数能帮你省掉大量临时换机器测试的时间。

3.2 docker build 的上下文与缓存参数

docker build的参数里,最核心的有几个:

  • 上下文路径:命令尾部那个路径,例如.,Docker 会把该目录整体打包发给守护进程,所以.dockerignore很重要。如果忘了排除node_modules、.git这类目录,每次构建都会传输海量无效文件,速度慢到怀疑人生;
  • -f或--file:指定 Dockerfile 路径,默认是./Dockerfile;
  • -t或--tag:给镜像打标签,可多次使用,例如-t myapp:v1 -t myapp:latest;
  • --build-arg:传入构建期变量,格式--build-arg VERSION=1.2,对应 Dockerfile 里的ARG;
  • --no-cache:禁用构建缓存,排查缓存导致的旧依赖问题时非常有用;
  • --target:多阶段构建中只构建到某个指定阶段。

构建缓存的优化思路很重要。尽量按"缓存友好顺序"排列 Dockerfile:先 COPY 依赖声明文件,再 RUN 安装依赖,最后 COPY 代码。这样代码变化时依赖层能命中缓存。很多初级开发者把 COPY 整个项目放在最前面,改一行代码就要重新安装所有依赖,这是很典型的构建效率陷阱。

另外要注意:ARG值变化会导致后续层缓存失效,所以频繁变化的构建参数应该尽量往后放。多阶段构建配合--target可以只构建测试阶段或产物阶段,在 CI 里能把构建时间压到很短。

3.3 tag、push、save、load 的参数细节

镜像的标记和传输也有一组固定套路:

  • docker tag:给镜像打多个标签,例如docker tag myapp:1.0 registry.example.com/myapp:1.0;
  • docker push:推送镜像到仓库,--quiet可以让输出干净一些;
  • docker save:把镜像导出为 tar 包,常用参数-o,例如docker save -o nginx.tar nginx:alpine;
  • docker load:从 tar 包导入镜像,常用参数-i,例如docker load -i nginx.tar;
  • docker rmi:删除镜像,-f强制删除,但要注意正在运行容器的镜像不能直接删;
  • docker prune:清理未使用的镜像,例如docker image prune -a删除所有未被容器引用的镜像。

save和load在内网离线部署时几乎是必备技能。我在无外网环境里部署服务,流程通常是这样:先在联网机器上 pull 镜像,再docker save -o导出,scp 到目标机器,然后docker load -i导入,最后 docker run。这里容易犯的错是:docker save -o导出的 tar 包含多个 tag 时,load之后镜像名可能和预期不一致,所以保存前最好先确认 tag 列表。

4. 容器生命周期管理参数:ps、exec、logs、inspect 与清理

容器创建只是第一步,后续的运维操作同样离不开参数。这一节把日常管理命令里值得注意的参数全部过一遍。

4.1 docker ps 的过滤与格式化

docker ps的参数看起来简单,实际信息量很大:

  • -a:查看所有状态的容器,包括 Exited、Created;
  • -q:只输出容器 ID,脚本里批量处理非常常用,比如docker rm -f $(docker ps -aq)清掉所有容器;
  • -s:显示容器占用的磁盘总大小;
  • --filter或-f:过滤容器,常用写法有--filter status=exited、--filter ancestor=nginx、--filter name=web、--filter label=xxx;
  • --format:自定义输出,Go template 语法,例如docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"。

实际运用中我最常用的是docker ps --filter "status=exited"找出已关闭容器再批量清理。但这里必须强调:docker rm -f $(docker ps -aq)非常危险,会删掉所有容器,包括正在提供服务的容器。公司新同学在开发机敲过一次,把自己的数据库容器删了,当场心态崩了。另外 filter 里的 name 是前缀匹配,不是完全匹配,过滤web会把web1、webapp都匹配出来,自动化脚本里要特别注意。

4.2 docker exec 的参数

docker exec是在运行中的容器里执行新进程,语法和docker run后半部分很像:

  • -i:保持标准输入打开;
  • -t:分配伪终端,和-i组成-it进入交互式 Shell;
  • -u:指定容器内运行命令的用户,比如-u root或--user 1000:1000;
  • -w:指定工作目录;
  • -e:给这次执行临时注入环境变量,只在本次进程中生效;
  • --env-file:同理,临时生效。

有个很容易被忽略的坑:如果镜像的 Dockerfile 指定了USER app,那docker exec默认也以 app 用户运行,除非显式指定-u root。排查以非 root 身份启动的服务时,一定要记得这个机制,否则你看到的"权限不足"可能不是文件权限,而是用户身份不对。

4.3 docker logs 和 docker inspect

docker logs查看日志,常用参数:

  • --tail:指定最近行数,例如--tail 100;
  • -f或--follow:跟随输出;
  • --since/--until:按时间过滤,例如--since 5m、--since 2025-01-01T00:00:00;
  • --timestamps:显示时间戳。

docker inspect查看容器或镜像的底层配置,输出是 JSON。配合--format做精简提取:

  • docker inspect --format '{{.State.Pid}}' 容器名:获取主进程 PID;
  • docker inspect --format '{{json .Mounts}}' 容器名:查看挂载详情;
  • 常用字段包括.HostConfig(资源限制、端口绑定)、.NetworkSettings(IP、网关)、.State(运行状态、ExitCode、OOMKilled)。

这里有一个值得培养的习惯:容器异常退出时,不要只盯日志,先跑docker inspect看State.OOMKilled。如果值为true,说明是内存超限被内核杀掉,日志里通常没有直接报错,只会看到进程消失。我排查过某个 Java 服务频繁重启,docker logs里只有 JVM 启动日志,翻到最后一句戛然而止,后来 inspect 一看OOMKilled: true,立刻锁定是内存限制太小,调大后问题消失。不懂 inspect 参数的话,不知道要浪费多少时间在错误方向上。

4.4 停止、删除与资源清理参数

容器停止和删除也有各自的参数细节:

  • docker stop:默认发 SIGTERM 给主进程,等优雅退出,宽限期默认 10 秒;超时可用-t指定,例如docker stop -t 30 容器名;
  • docker kill:直接发 SIGKILL,也可用--signal指定其他信号;
  • docker rm:删除已停止容器,-f强制删除运行中的容器;-v同时删除关联的匿名卷,这对数据清理很关键;
  • docker system prune:清理未使用的镜像、容器、网络和构建缓存,加-a --volumes会连未引用的数据卷一起清除,务必谨慎。

停止容器的信号机制容易被忽视。如果你的容器主进程是 shell 启动的,shell 可能不会把 SIGTERM 转发给子进程,导致优雅退出失败,最后被强杀。这个问题的源头通常不在 docker 参数,而在镜像的 ENTRYPOINT 写法,后面 Dockerfile 部分会详细说。

5. Dockerfile 编译期参数:镜像不是靠命令堆出来的

镜像构建是整个 Docker 体系中最需要理解参数含义的环节。很多人以为 Dockerfile 就是把 Linux 命令罗列一遍,其实指令参数的选择直接决定镜像层数、缓存命中率、安全属性和最终运行时的进程行为。

5.1 FROM、RUN、COPY、ADD 与构建上下文

  • FROM:基础镜像来源,可加--platform指定平台,例如FROM --platform=linux/amd64 alpine:3.19;
  • RUN:执行构建命令,两种写法:Shell 形式RUN apt-get update && apt-get install -y xxx和 Exec 形式RUN ["apt-get","install","-y","xxx"];
  • COPY:从构建上下文复制文件进镜像,支持--from在多阶段构建里复制上一阶段产物;
  • ADD:比 COPY 多了解压 tar 和识别远程 URL 的能力,但行为隐式,官方建议默认用 COPY,只有明确需要解压时才用 ADD;
  • --chown:复制文件时直接指定所有者,例如COPY --chown=app:app app.jar /app/app.jar,避免在镜像里多执行一次 chown 生成多余层。

构建上下文之前提过,再强调一层:你运行docker build .时,后面的路径就是上下文,Docker 会把目录打包发送给守护进程。.dockerignore直接影响构建效率。如果一个项目没写.dockerignore,每次构建传一堆日志、依赖、临时文件,CI 慢到怀疑人生,这不是 Docker 本身慢,而是上下文太大。

优化逻辑上,RUN 命令按"更新频率从小到大"排列,经常变化的 COPY 放在靠后位置,能最大程度利用缓存。比如先COPY package.json,再RUN npm install,最后COPY . .,这样代码改动时依赖层缓存依然命中。

5.2 CMD 与 ENTRYPOINT 的两层设计

容器启动的进程行为由 ENTRYPOINT 和 CMD 共同决定:

  • ENTRYPOINT:定义主进程程序和固定参数,通常不会被 docker run 后面的命令行覆盖,但--entrypoint可以覆盖它;
  • CMD:提供默认参数,可以被 docker run 后面的命令行整体替换;
  • 两者同时存在时,CMD 的内容会追加到 ENTRYPOINT 后面作为默认参数。

举个例子。假设 Dockerfile 写ENTRYPOINT ["nginx"],CMD ["-g","daemon off;"],那docker run mynginx实际执行的是nginx -g daemon off;。如果执行docker run mynginx -t,CMD 被覆盖为["-t"],最终进程是nginx -t,用来测试配置。反过来,docker run busybox echo hi里的echo hi会整体替换 CMD,而不是 ENTRYPOINT。

这里有个非常隐蔽的坑:ENTRYPOINT nginx这种 Shell 写法,会在容器内先启动一个 shell 再启动 nginx,shell 收到 SIGTERM 时不会自动传给子进程,docker stop无法优雅关闭容器,只能等超时后被强杀。生产镜像建议统一使用 Exec 形式,也就是 JSON 数组写法。这个问题我在排查服务"优雅退出失效"时碰到过好几次,根源都在这里。

5.3 ARG 与 ENV 的边界:构建期和运行期

  • ARG:只存在于构建过程,用--build-arg从外部传入,对应docker build --build-arg VERSION=1.2 .;
  • ENV:既影响构建过程,也写入镜像的运行时环境变量;
  • ENV变量不会自动传递到 RUN 的 Shell 子进程,需要在同一 RUN 里显式使用或 export;
  • 可以用 ENV 固化 ARG:ARG APP_VERSION=1.0 ENV APP_VERSION=$APP_VERSION,这样构建期传入的版本号会固化到运行环境。

还有个常见 bug:--build-arg传参时,必须先在 Dockerfile 里用ARG声明对应名字,否则命令行里传了也会被忽略。很多人遇到--build-arg 不生效,第一反应是命令写法问题,其实是 Dockerfile 里根本没有声明。这个参数传递链路一定要记住。

5.4 HEALTHCHECK、EXPOSE、STOPSIGNAL 等进程参数

  • HEALTHCHECK:定义健康检查方式,重要参数有--interval检查间隔、--timeout超时、--start-period启动宽限期、--retries连续失败次数。检查结果可通过docker inspect查看;
  • EXPOSE:只声明端口,不发布端口,如果镜像里没写 EXPOSE,-P不会映射任何端口;
  • STOPSIGNAL:设置容器停止时发送的信号,默认 SIGTERM,某些应用需要改成 SIGQUIT 等信号才能优雅退出;
  • WORKDIR:设置工作目录,影响 RUN、CMD、ENTRYPOINT、COPY、ADD 的默认路径;
  • USER:指定最终进程的运行用户,强烈建议生产镜像避免以 root 运行;
  • VOLUME:声明匿名卷挂载点,启动时即使没挂载,也会创建匿名卷;
  • SHELL:修改 Shell 形式指令的默认 Shell。

特别提一下HEALTHCHECK的 start-period。很多人只设置检查间隔,结果容器还在初始化数据库,健康检查就连续失败,调度系统判定不健康,服务被过早杀掉。正确做法是给足够启动时间,比如 MySQL 初始化可能要几十秒,start-period 40s很常见。这个参数不解决应用启动慢的问题,但能避免误杀。

6. 数据卷与网络专项参数:持久化、连通性与隔离

运行状态之外,容器最容易被忽略的两个领域就是卷和网络,它们的参数直接影响数据安全和集群可访问性。

6.1 -v 与 --mount:两种写法与权限问题

挂载卷有两种语法:老式-v简洁,源路径:目标路径[:模式],比如-v /data:/var/lib/mysql、-v myvol:/data(命名卷)、-v /host/path:/container/path:ro(只读挂载)。

--mount是结构化写法,采用type=xxx,source=xxx,target=xxx,readonly的键值对形式,支持 bind、volume、tmpfs、npipe 类型。还能设置 bind-propagation,例如--mount type=bind,source=/host,target=/container,bind-propagation=rshared。生产环境建议尽量用--mount,语义清晰,更适合写进脚本和维护。

卷与权限是这里最大的坑。很多镜像内进程以非 root 用户运行,比如 uid 1000,但宿主机挂载目录属主是 root,容器内进程无法写入。解决方式有两种:一是创建目录后 chown 成与容器内用户一致的 uid;二是用--user参数让容器进程以宿主机某用户运行。如果不想改宿主目录权限,还可以用 SELinux 的:Z或:z标签。总之,挂载卷的权限问题十有八九是 uid 不匹配,docker exec进去执行id一看便知。

6.2 网络驱动与自定义 subnet 参数

Docker 默认提供 bridge、host、none 三种网络,Swarm 模式还会用到 overlay 和 macvlan:

  • bridge:默认模式,容器通过虚拟网桥互通,外部访问依赖端口映射;
  • host:容器直接使用宿主网络栈,性能和兼容性最好,但没有网络隔离;
  • none:容器没有网络接口,适合纯离线计算任务;
  • macvlan:让容器获得独立 MAC 地址,从网络设备视角看就是一台真实主机;
  • overlay:跨宿主机容器通信时使用,主要在 Swarm 下用。

docker network create常用参数:

  • --driver:指定网络驱动;
  • --subnet:指定 CIDR,例如--subnet 172.20.0.0/16;
  • --gateway:指定网关;
  • --ip-range:指定动态分配范围;
  • --attachable:允许非 swarm 容器连接。

同一自定义 bridge 网络里的容器可以直接通过容器名互相访问,这是 compose 项目里服务互相连通的基础。如果需要容器固定 IP,必须在docker network create时指定 subnet,再在docker run --ip里指定。这个参数组合在搭建跨主机通信的开发环境里非常常用。

6.3 日志驱动与 log-opts

Docker 日志系统通过 json-file、journald、syslog、fluentd、gelf 等驱动工作。docker run --log-driver指定日志驱动,默认 json-file 会写本地文件,长期运行可能占用大量磁盘。所以必须配置 log-opts:

  • --log-opt max-size=10m:单个日志文件大小上限;
  • --log-opt max-file=3:最多保留几个文件;
  • --log-opt mode=non-blocking:写入慢时容器不阻塞,丢弃部分日志避免影响业务;
  • --log-opt tag="custom-{{.Name}}":给日志对象打标识,便于汇聚后过滤。

这里特别提醒:默认 json-file 驱动如果没有设置 max-size 和 max-file,生产环境很容易出现日志堆积把磁盘撑爆。我处理过最典型的情况是接口服务一天输出 GB 级日志,容器本身只有 2G 磁盘空间,最后整台宿主机/var/lib/docker分区被写满,所有容器全部故障。根源就是 run 时没给日志参数设限。所以凡是运维入口,务必主动把日志大小限制在合理范围,这比事后写清理脚本更靠谱。

7. Docker Compose 的字段参数:从单容器到服务编排

单容器用 docker run 足够,但一套完整服务通常包含多个容器:应用、数据库、缓存、消息队列。这时用 Compose 描述服务之间的依赖和网络关系更合适。Compose 文件本质上是把 docker run 参数映射成 YAML 字段,但多了编排层概念。

7.1 services 关键字段与 docker run 参数的对应

services 下常用字段:

  • image:镜像,对应docker pull+ run 的 image 部分;
  • build:构建配置,对应docker build,包含 context、dockerfile、args;
  • container_name:显式指定容器名;
  • ports:发布端口,格式"宿主机端口:容器端口",也可以用 target/published/protocol 长写法;
  • expose:只暴露端口给网络内其他服务;
  • environment:环境变量,类似docker run -e,也支持env_file;
  • volumes:挂载配置,可用短语法或长语法;
  • restart:重启策略,no、always、on-failure、unless-stopped;
  • command:覆盖镜像 CMD;
  • entrypoint:覆盖镜像 ENTRYPOINT;
  • healthcheck:健康检查,字段和 Dockerfile HEALTHCHECK 一致;
  • depends_on:服务启动顺序依赖;
  • deploy:部署资源限制配置。

新版 compose.yaml 已经不再推荐写version字段,早期version: "3"这种写法已经过时,新项目不用再写。这里重点说depends_on:如果只看字面,以为它控制"服务启动完成后再启动下游",但早期版本只控制启动顺序,不保证服务健康。正确的做法是配合 healthcheck 和condition: service_healthy,下游服务才会真正等依赖就绪。

7.2 .env 与变量插值的机制

compose 支持在 .env 文件里定义变量,并在 compose.yaml 中用${VARIABLE}做插值。例如数据库密码可以不再明文写在配置文件里,而是通过 .env 引入,.env 文件可以留在本地不进代码仓库,既保结构又保密级隔离。

这里有个容易混乱的坑:.env文件里的变量和environment字段下的变量是两套机制。environment会直接传给容器,.env只负责 compose.yaml 本身的插值。如果你在 .env 里写了MYSQL_ROOT_PASSWORD,但 compose.yaml 的environment字段没有引用,这个变量不会自动进入容器,很多人在这里兜圈子。

7.3 资源限制与重启策略的高频组合

在 compose 中控制资源限制可以写deploy.resources.limits,但在本地 docker compose 环境下部分字段不一定直接对容器生效。更稳妥的方式是使用 run 时的底层资源限制能力,或者在 compose 里配置明确的配置。实际项目中,我最常用的组合是:

  • restart: unless-stopped,服务器重启后能自动把服务拉起来;
  • healthcheck设置合理 start-period,避免误判;
  • volumes使用命名卷而不是宿主机路径,便于备份和迁移;
  • networks显式指定自定义网络,把不需要对外暴露的端口只绑定到127.0.0.1。

特别想提醒:不要把数据库放在容器层,一旦容器被删除、升级或异常重建,数据很可能跟着丢失。正确方案是命名卷或宿主机目录持久化。这个参数层面看似简单,背后是容器设计里最核心的"无状态与有状态分离"原则。

8. 高频报错与参数相关的排查记录

最后这部分专门记录我在实际运维中遇到的几个典型问题,它们都和参数理解不到位有关,希望帮你节省排查时间。

8.1 端口映射失败:bind address already in use

这个报错出现时,很多人第一反应是端口被占用,但实际上一半以上情况不是真正的进程占用,而是 docker-proxy 仍在运行,或 iptables 规则残留。处理步骤一般是:

  • docker ps看是否已有容器占用了端口;
  • lsof -i:8080或ss -tlnp确认宿主端口占用者;
  • 如果确认规则残留,谨慎重启 docker 服务恢复。

还有一小类场景是容器内服务监听127.0.0.1,即使-p映射关系正确,外部访问仍然失败,因为流量到达容器网卡后,应用没监听容器 IP。排查思路是进容器同时测试curl 127.0.0.1:端口和curl 容器IP:端口,基本一测便知。

8.2 容器启动后退出:Restart (0) 或 Restarting

看到容器一直 Restarting,第一反应用docker logs看日志,方向是对的。但也别忽略docker inspect里State.ExitCode和State.Error:

  • ExitCode=127:通常是命令不存在,比如镜像里没有 bash,但 entrypoint 写的是 bash。Alpine 镜像默认是 ash,临时验证可以改用/bin/sh或安装 bash;
  • ExitCode=137或143:进程被 SIGKILL/SIGTERM 杀死,和资源限制、stop 超时、OOM 有关;
  • ExitCode=139:段错误,常见于架构不匹配,比如 amd64 宿主上跑 arm64 镜像,这时要检查--platform参数和镜像 tag。

8.3 权限不足:operation not permitted 与 cannot mkdir

这类报错多半是 SELinux 或 capabilities 问题。如果宿主机开启 SELinux,挂载卷后容器内进程访问宿主目录被拒绝,报错可能只是operation not permitted。解决方式要么给目录做好 SELinux 标签,要么挂载时加:z或:Z。

另一个常见操作是容器内执行 mount、iptables 时被拒绝,一般是因为容器默认缺少相关 capabilities。解决办法不是直接--privileged,而是按需加--cap-add,比如需要 iptables 就--cap-add=NET_ADMIN。长期维护下来,按需加权限既能保住隔离边界,也方便未来安全审计。

8.4 磁盘被 /var/lib/docker 占满

这个问题在上文日志参数里提过,再补充清理思路:docker system prune可以清理未引用的镜像、容器、网络和构建缓存,但加-a --volumes时一定要谨慎,因为它会连所有未使用容器引用的数据卷一起删。稳妥流程是先用docker ps -a确认没有需要保留的容器,再分开执行docker image prune和docker volume prune。

如果磁盘已经写满,Docker 守护进程可能已经无法正常工作,需要先腾出空间再恢复服务。平时就应通过 log-opts 和定期清理策略把风险窗口控制在最小。

这篇笔记基本覆盖了我日常运维和开发中使用 Docker 的绝大多数参数场景。个人体会是,参数本身只是表象,真正值得琢磨的是参数背后的资源隔离、文件挂载、进程模型这三个核心逻辑,只要把这三块想明白,碰到陌生参数时也能马上猜到它属于哪个层面、会带来什么影响。最后分享一个小习惯:每学一个新参数,我都会用docker inspect去看这个容器创建后的实际配置,等于把参数翻译成可观测的底层状态,这个反查过程比背十遍命令都管用。

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

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

立即咨询