2G 小机器上的 Docker Compose:我踩过的几个坑
2026-10-05
我在一台 2C2G 的国内 ECS(Ubuntu 24.04)上跑自己写的几个小服务,前面挂一层 nginx,同机还有别的自用服务;小机器上编排全用 Docker Compose。 这篇把实测踩过、并且写进配置注释里的东西整理一下。
一、compose 命令本身要先探测
if docker compose version >/dev/null 2>&1; then DC="docker compose"
elif command -v docker-compose >/dev/null 2>&1; then DC="docker-compose"
fi
插件版和旧版 v1 写法不通用,脚本里别硬写;装 docker 我用官方那行 curl -fsSL https://get.docker.com | sh,装完 systemctl enable --now docker。
二、内存上限:不能没有,也不能太小气
mem_limit: 1024m
memswap_limit: 1536m
- 有个吃内存的容器实测常驻 400~800MB,只给它
512m就陷入"OOM 被杀 → 自动重启 → 再被杀"的死循环; memswap_limit要高于mem_limit:内存紧时先溢出到宿主机 swap(我开了 2G),宁可慢一点也别被杀;- 上限是"允许用到"不是预占,空转时实测只用 80MB 左右;
- 小机器最现实的单点故障就是内存,每个服务都设限:万一谁泄漏,被 OOM 杀掉的是它自己,不是同机的邻居。
三、cpus 在这台机器上根本写不了
Error response from daemon: NanoCPUs can not be set, as your kernel
does not support CPU CFS scheduler or the cgroup is not mounted
原因是云盾(aegis)把 cgroup v1 的 cpu 控制器独占挂走了,cgroup v2 的控制器列表里就没有 cpu(一查便知:cat /sys/fs/cgroup/cgroup.controllers);内存控制器还在,所以 mem_limit 仍然有效。
四、restart: unless-stopped 的语义正好是我要的
开机自启、崩了自动拉起,但被我手动 stop 过的服务保持停止,重启服务器后"只有该回来的回来"。重启单个服务一定带 --no-deps:
docker compose up -d --no-deps --force-recreate <服务名>
不带的话 compose 会顺手把 depends_on 的依赖服务一起处理,那个服务被重建之后要重新初始化,我的服务就中断几分钟。这条是真踩过才记住的。
五、卷:具名卷和绑定挂载的属主不一样
- 容器要写数据的目录我用具名卷,它继承镜像里
/app/data的属主;绑定挂载不会继承,宿主机上变 root,容器里的用户就写不进去; - 两个容器之间要递文件,就把同一个宿主机目录挂进两边 —— 容器互相看不见对方的文件系统;
- 配置和状态类目录必须持久化,否则每次重建都要从头初始化一遍。
六、镜像拉不下来就走本地自建 + 国内源
bash <构建目录>/fetch-artifacts.sh
docker build -t <镜像名>:latest <构建目录>/
国内服务器上 registry-1.docker.io 会被 DNS 污染,公共镜像站只能取 manifest、拉不到实际的层。所以我把上游 Dockerfile
等价复刻一份放本地,素材源全挑国内可达的,镜像里装系统包走 mirrors.aliyun.com;代码打进镜像又没有绑定挂载时,改完必须 build 再 up -d。
七、另外三处小配置
- 配置分层:同一个服务既用
env_file又靠代码里的缺省值兜底时,env_file会把基础配置全量灌进环境变量, 兜底永远轮不到;改成只把文件挂进容器、用一个变量指出先读哪个差异文件,顺序变成"先差异项、再拿基础配置补漏"; - 日志:
logging: json-file配max-size: "10m"、max-file: "3",不然小盘迟早被写满; - 安全项:
security_opt: no-new-privileges:true,再加tmpfs: /tmp。
八、配套脚本要幂等,还有换行符那个坑
常用动作我封成几个脚本,反复跑结果一样:体检模式只查不动东西(bash deploy.sh --check 会看内存、swap、配置、容器状态);
同步类脚本发现"已经是目标状态"就直接退出,改配置前先备份、nginx -t 不过就自动回滚。
长驻的小助手做成 systemd 服务(Restart=always + RestartSec=5,依赖 docker 先起来),日志进 journal,出问题用 journalctl -u <单元名> -n 20 看。
换行符单独记一笔:从 Windows 提交脚本时 git 的 core.autocrlf 会把文本存成 CRLF,scp 到 Linux 上就报
bash: ./xxx.sh: /bin/bash^M: bad interpreter。治法是在 .gitattributes 里强制:
*.sh text eol=lf
*.conf text eol=lf