Docker 日常使用的几个配置与踩坑:时区、capabilities 与 OrbStack 迁移

用 Docker 部署服务的时候,真正卡住人的往往不是「怎么把镜像跑起来」,而是一些环境和权限上的细节。这篇把我先后遇到、记过笔记的三个问题整理到一起:容器时区为什么和宿主机对不上、需要额外权限时应该怎么开、以及在 macOS 上从 Docker Desktop 换到 OrbStack 之后那个烦人的恶意软件警告。它们彼此独立,可以按需跳读。

一、容器时区和宿主机不一致

在跑 MinIO 的时候我发现一个奇怪的现象:date 命令显示的是 CST 时区,但 /etc/timezone/etc/localtime 里写的却是 UTC。

$ date
Fri Jan 24 11:50:19 AM CST 2025

$ cat /etc/timezone
Etc/UTC

两边对不上,说明时区配置本身就是乱的——date 读的是内核当前的时区,而表示时区的那两个文件还停留在旧值,没有同步过来。这种情况下,无论怎么改容器,宿主机这一层先就不对。

先修好宿主机时区

timedatectl 统一设置,它会同时处理内核时区和相关文件:

sudo timedatectl set-timezone Asia/Shanghai

验证:

date
cat /etc/timezone

如果 /etc/localtime/etc/timezone 还是不一致(在一些精简系统或容器基础镜像里 timedatectl 不可用),就手动重建软链接:

sudo ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
echo "Asia/Shanghai" | sudo tee /etc/timezone

这里的关键是理解 /etc/localtime 是一个指向 /usr/share/zoneinfo/ 下具体时区文件的软链接,glibc 读它来决定本地时间;/etc/timezone 只是一个给人和某些工具看的纯文本记录。两者要保持一致。

让容器使用宿主机时区

宿主机修好之后,容器里大概率还是 UTC——因为容器有自己独立的文件系统和时区配置,基础镜像(尤其是 Alpine、Debian slim)默认就是 UTC。

最省事、也最不依赖镜像内部配置的做法,是启动容器时直接把宿主机的时区文件以只读方式挂载进去:

docker run -dt \
  -p 9000:9000 -p 9001:9001 \
  -v /etc/localtime:/etc/localtime:ro \
  -v /etc/timezone:/etc/timezone:ro \
  --name minio_local \
  minio/minio server --console-address ":9001"

ro 表示只读挂载,容器里不能改这两个文件。这样容器时区就始终跟着宿主机走,宿主机改了容器也跟着变,不用在每个镜像里单独设 TZ 环境变量或 apt install tzdata

docker-compose.yml 里写法一样:

services:
  app:
    image: your-image
    volumes:
      - /etc/localtime:/etc/localtime:ro
      - /etc/timezone:/etc/timezone:ro

提示:少数完全静态编译、不走 glibc 的程序(比如某些 Go 程序)只认 TZ 环境变量,这种情况再补一个 -e TZ=Asia/Shanghai 即可。

二、用 —cap-add 精细控制容器权限,别无脑 —privileged

在容器里跑某些服务时,会遇到「权限不够」的报错。第一反应往往是加 --privileged 了事——但这等于把宿主机几乎所有权限都开放给容器,风险很大。更好的做法是用 --cap-add,只把缺的那一个能力加上。

Linux capabilities 是什么

传统 Unix 权限模型很粗糙:要么是普通用户,要么是 root,而 root 能干所有事。Linux capabilities 把原本一整块的 root 权限拆成了几十个独立的功能单元,可以单独授予或撤销。

比如一个进程只是需要修改网络配置,并不需要完整的 root 权限,那么给它 NET_ADMIN 这一个能力就够了,其他权限一概没有。Docker 容器默认只保留了一部分 capabilities,足够大多数应用运行,但不是全部——所以才会出现「明明是 root 却报权限错」的情况。

用法

docker run --cap-add=NET_ADMIN my_image

可以加多个:

docker run --cap-add=NET_ADMIN --cap-add=SYS_TIME my_image

docker-compose.yml 里:

services:
  app:
    image: my_image
    cap_add:
      - NET_ADMIN
      - SYS_TIME

常用的几个

  • NET_ADMIN —— 网络配置,改路由、设置网络接口、配防火墙规则(跑 VPN、软路由类容器常用)
  • SYS_ADMIN —— 范围很广,挂载文件系统、修改部分内核参数等,几乎是「半个 root」,能不用就不用
  • SYS_TIME —— 修改系统时间,在容器里跑 NTP 服务之类的场景会用到
  • SYS_PTRACE —— 允许 ptrace,容器内调试、attach 进程时需要

配合 —cap-drop 实现最小权限

更严格的做法是先撤销全部默认能力,再只把需要的加回来:

docker run --cap-drop=ALL --cap-add=NET_ADMIN my_image

这才是真正的最小权限原则——容器只拥有它干活所必需的那一个能力,其余一律没有。

和 —privileged 的区别

--privileged 会把宿主机几乎所有 capabilities 都给容器,还会解除设备访问限制,基本等于让容器内的 root 拿到了宿主机 root。除非是在做容器嵌套(比如在容器里再跑 Docker / DinD)这类特殊场景,否则不应该用它。原则就一句话:需要什么权限就加什么,这样即使容器被攻破,攻击者能造成的破坏也被限制在很小的范围内。

三、换 OrbStack 后开机报 com.docker.vmnetd malware 警告

在 macOS 上从 Docker Desktop 换到 OrbStack 之后,每次开机都会弹一个系统警告:

"com.docker.vmnetd" was not opened because it contains malware.
This action did not harm your Mac.

这个弹窗不影响 OrbStack 正常使用,但每次开机都跳出来确实很烦。

原因

卸载 Docker Desktop 的时候,/Library/PrivilegedHelperTools/ 目录下的两个文件没有被一并清理掉:

  • com.docker.vmnetd
  • com.docker.socket

这两个是 Docker Desktop 安装的特权辅助进程(privileged helper),卸载主程序后它们还留在系统里,并且仍然注册在 launchd 中。macOS 在开机时尝试加载它们,发现签名已经失效(或被系统判定为不可信),就弹出 malware 警告。OrbStack 有自己独立的虚拟化方案,完全不依赖这两个文件,删掉不影响任何功能。

清理方法

sudo rm -f /Library/PrivilegedHelperTools/com.docker.vmnetd
sudo rm -f /Library/PrivilegedHelperTools/com.docker.socket
sudo launchctl remove com.docker.vmnetd
sudo launchctl remove com.docker.socket

后两行 launchctl remove 是把 launchd 里的注册项也清掉,否则下次启动还可能被重新触发。

验证清理干净:

ls /Library/PrivilegedHelperTools/ | grep docker

没有任何输出就说明删干净了。然后确认 OrbStack 仍然正常:

docker ps

能正常返回容器列表,就说明这两个文件的删除没有影响到 OrbStack。重启之后,那个 malware 警告也不会再出现了。


这三件事单独看都是小问题,但都属于「不知道的时候会卡很久、知道了五分钟搞定」的类型。时区记得从宿主机修起、权限坚持最小化、迁移工具后清理残留——把这几个习惯固定下来,用 Docker 会顺手很多。