包含 docker-compose.yml(实例 2 用 profile 门控)、.env.example 配置模板、 以及基于实测数据的部署说明。 实测确认的关键点: - 镜像仅 134MB 不含游戏文件,约 80-100GB 服务端由 SteamCMD 运行时拉取 - 镜像声明 4 个端口,BEACONPORT=15000 官方 wiki 未提及,host 网络下多实例会冲突 - entry.sh 会自动重写 Rcon.cfg 端口,但 sed 只匹配字面量 21114, 首启后再改 RCONPORT 会静默失败 - entry.sh 每次启动无条件跑 app_update,故共享游戏文件方案有并发写入风险(未验证) - 发现两个未见于文档的环境变量:SERVER_NAME、MULTIHOME - RANDOM 环境变量在本镜像中无效(bash 内置特殊变量覆盖不了),已记录为已知问题 凭据一律使用占位符,.gitignore 挡住 .env / Rcon.cfg / License.cfg 等含密文件。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Squad(战术小队)专用服务器 — Docker 部署
基于 cm2network/squad 镜像的 Squad 专用服务器部署方案,支持单实例与多实例。
标注 [实测] 的内容来自 2026-07-27 对目标环境和镜像的实际验证,非估算或推测。 标注 [未验证] 的内容尚未在本环境证实,请勿直接用于生产。
1. 这套方案解决什么
Squad 服务端官方只提供 SteamCMD 裸装方式(配 screen 托管)。本方案用 Docker 替代:
- 进程守护与自动重启交给 Docker,不依赖
screen会话 - 游戏文件、配置、日志都在挂载卷里,容器可随意重建
- 多实例通过环境变量声明端口,不用改启动脚本
不解决的问题:游戏本体下载量和硬件需求 —— 见下节。
2. 硬件要求 — 先读这一节
2.1 镜像不含游戏文件
[实测] cm2network/squad 镜像只有 134.3 MB(压缩后,6 层),内容是 Debian 基础层 + SteamCMD + entry.sh。
游戏服务端本体(约 80–100 GB)是容器每次启动时由 SteamCMD 拉取的,落在挂载卷里。
两个直接后果:
- 把镜像搬到私有注册表不能省掉这次下载
- 每个独立安装的实例都要下一份,所以多实例基本上必须共享游戏文件(§7.2)
2.2 最低配置
| 资源 | 单实例 | 双实例 | 说明 |
|---|---|---|---|
| 物理核 | 1 | 2 | 看物理核,不是 vCPU。Squad 主 tick 重度单线程,一核跑两实例会双双掉 tick |
| 内存 | 8 GB | 16 GB | 无 swap 时更吃紧,务必设 mem_limit |
| 磁盘(持久) | 120 GB | 160 GB(共享游戏文件) 200+ GB(独立安装) |
另需留出 SteamCMD 更新的临时空间 |
⚠️ Azure 用户注意:
/mnt挂的资源盘是临时盘,VM deallocate 后清空,不能放游戏文件。df -hT看不出来,要查/etc/fstab是否有azure_resource和comment=cloudconfig字样。
2.3 首次下载耗时会远超预期
[实测] 目标环境到 Steam CDN 的速度为 0.8–1.6 MB/s(约 7–13 Mbit/s)。
按 80 GB 计算,单次完整下载需要 14–28 小时。
部署前确认:
- 下载完成后磁盘仍有余量(建议留 20 GB 以上)
- 下载期间尽量不重启容器 —— SteamCMD 支持续传,但会重新校验,很耗时
3. 前置条件
3.1 目标机器
- Linux x86_64,GLIBC ≥ 2.17
- Docker Engine +
docker compose插件 - 当前用户在
docker组,或有 sudo
Ubuntu 上不要用
apt install docker.io。[实测] 目标环境apt-cache policy docker.io显示Candidate: (none)。请走 Docker 官方源(download.docker.com)。
3.2 Docker Hub 不可达时(中国大陆常见)
[实测] 目标环境 registry-1.docker.io / index.docker.io / auth.docker.io 全部超时
(DNS 可解析,属网络层拦截)。
两种应对:
A. 镜像搬运到私有注册表(本方案采用) — 见 §5
B. 配置 registry mirror — /etc/docker/daemon.json:
{ "registry-mirrors": ["https://docker.m.daocloud.io"] }
[实测] 该 mirror 返回 401 正常鉴权挑战,可用。但依赖第三方站点可用性。
3.3 端口放行
主机防火墙之外,云平台安全组也要放行。Azure NSG、阿里云安全组这类配置在机器外部,
机内 iptables / ufw 查不到,是最容易漏的一环 —— 表现为服务端一切正常但玩家搜不到、连不上。
规则见 §4.2。
4. 端口规划
4.1 一个容易漏的端口
[实测] 镜像声明了 4 个端口:
7787/udp 27165/udp 21114/tcp 15000/udp
最后一个 15000/udp(BEACONPORT)常被漏掉 —— 官方 wiki 服务器安装页完全没有提及。
在 network_mode: host 下两个实例都会去绑 15000,导致第二个实例起不来或行为异常,
而日志里不一定报得清楚。
4.2 多实例端口表
游戏端口与查询端口各占 N 和 N+1 两个,所以实例间至少间隔 10,不能只差 1。
| 用途 | 协议 | 实例 1 | 实例 2 |
|---|---|---|---|
| 游戏 | UDP | 7787, 7788 | 7797, 7798 |
| Steam 查询 | UDP | 27165, 27166 | 27175, 27176 |
| Beacon | UDP | 15000 | 15010 |
| RCON | TCP | 21114 | 21124 |
安全组入站规则(实例 1):
UDP 7787-7788 允许
UDP 27165-27166 允许
UDP 15000 允许
TCP 21114 允许 —— 建议限制来源 IP
RCON 是远程管理通道,不应对全网开放,建议只放行管理者 IP。
4.3 network_mode: host 是必需的
Squad 依赖 Steam 查询协议,用 -p 端口映射经常导致服务器在游戏内浏览器中搜不到。
代价:Docker 不再隔离端口,多实例端口必须手工错开。
若每个实例有独立 IP,也可以用 MULTIHOME 绑定(§8.2),比纯靠端口错开更清晰。
5. 镜像搬运到私有注册表
在能访问 Docker Hub 的机器上执行(通常是本地开发机)。
5.1 拉取、打标、推送
# 该镜像只有 linux/amd64。在 Apple Silicon 上必须显式指定 --platform,
# 否则会报找不到匹配平台。搬运不需要运行,所以架构不匹配无妨。
docker pull --platform linux/amd64 cm2network/squad:latest
docker login <注册表地址> -u <用户名> --password-stdin # 密码走 stdin,不要用 -p
docker tag cm2network/squad:latest <注册表地址>/squad-server:v1
docker push <注册表地址>/squad-server:v1
用
--password-stdin而不是-p <密码>:后者会把密码暴露在进程列表(ps)里。
5.2 核对 digest
推送后记录 digest,目标机拉取后比对,确认跑的是同一个镜像:
docker image inspect --format '{{index .RepoDigests 0}}' <注册表地址>/squad-server:v1
[实测] 本次搬运结果与 Docker Hub 上游 digest 完全一致:
sha256:8cba47f53df586c2767525a9f062c2996b36f9a34a1d771a35f67a1713c46893
若注册表无鉴权(任何人可写),这一步是必要的完整性检查,不是可选项。
5.3 HTTP 明文注册表的额外配置
如果私有注册表是 HTTP(非 TLS),Docker 默认拒绝,报
http: server gave HTTP response to HTTPS client。需要:
Linux — /etc/docker/daemon.json:
{ "insecure-registries": ["<地址:端口>"] }
改完 sudo systemctl restart docker。
macOS(Docker Desktop) — 写 ~/.docker/daemon.json 后必须完整退出并重启 Docker Desktop。
踩坑记录:
docker desktop restart可能异步返回,导致后续命令连到的还是旧守护进程, 配置看似没生效。另外用pkill强杀 Docker Desktop 会打断它的退出流程, 使随后的open -a Docker失效。稳妥做法:osascript -e 'quit app "Docker"'→ 轮询等 socket 消失 →open -a Docker→ 轮询等docker info成功 → 再验证:docker info --format '{{json .RegistryConfig.IndexConfigs}}'确认目标地址出现且
"Secure": false。
使用 ACR / Harbor 等有效 TLS 的注册表时,以上都不需要。
5.4 拉取凭据的权限收敛
目标机 docker login 后凭据会以 base64 存进 ~/.docker/config.json(不是加密)。
如果用的是注册表管理员凭据,那台机器一旦被入侵,攻击者就获得了推送权限, 可以覆盖你的镜像 tag。生产环境建议改用只读(pull-only)令牌:
- Azure ACR:创建 scope map + token,只授
content/read - Harbor:创建 robot account,只勾 pull
6. 部署
6.1 准备数据目录
sudo mkdir -p /srv/squad/inst1
# 镜像内以 uid 1000 (steam) 运行,必须给写权限
sudo chown -R 1000:1000 /srv/squad
6.2 配置
cp .env.example .env
$EDITOR .env
6.3 启动
docker compose up -d squad1
docker compose logs -f squad1
首次启动进入 SteamCMD 下载阶段(§2.3,14–28 小时)。
日志出现 Success! App '403240' fully installed. 表示下载完成。
6.4 编辑服务器配置
游戏文件下载完成后,配置文件出现在 /srv/squad/inst1/SquadGame/ServerConfig/:
| 文件 | 作用 |
|---|---|
Server.cfg |
服务器名称、人数上限等主配置 |
Rcon.cfg |
RCON 端口与密码 |
Admins.cfg |
管理员与权限组 |
MapRotation.cfg |
地图轮换 |
MOTD.cfg |
每日消息 |
ServerMessages.cfg |
循环公告 |
Bans.cfg |
封禁列表 |
License.cfg |
服务器授权 |
RCON 密码必须在这里改。 镜像没有 RCONPASSWORD 环境变量,entry.sh 只重写端口、不管密码。
默认密码不改等于把服务器管理权公开。
改完重启生效:
docker compose restart squad1
7. 多实例
7.1 RCON 端口:自动处理,但有个后续陷阱
[实测] entry.sh 会在启动时自动重写 Rcon.cfg 里的端口:
sed -i -e 's/Port=21114/'"Port=${RCONPORT}"'/g' "${STEAMAPPDIR}/SquadGame/ServerConfig/Rcon.cfg"
所以新建实例时不需要手动改 Rcon.cfg 端口 —— 很多教程(包括官方 wiki)说必须手动改,
对这个镜像而言是多余的。
但这个 sed 只匹配字面量 Port=21114。 因此:
⚠️ 首次启动之后再改
RCONPORT,Rcon.cfg 不会跟着变 —— 此时Rcon.cfg里已经是旧的自定义端口,不再包含21114,sed 匹配不上,静默失败。 结果是启动参数与配置文件不一致,RCON 连不上。改端口时必须同时手工改
Rcon.cfg,或者删掉Rcon.cfg让 SteamCMD 重新生成默认文件。
7.2 共享游戏文件 [未验证]
游戏本体约 80–100 GB,下载一次 14–28 小时,两个实例各下一份通常不现实。
思路是游戏本体只留一份共享,每实例独立持有 ServerConfig 和日志。
⚠️ 本方案尚未实测,存在明确的已知风险。
[实测]
entry.sh在每次容器启动时都会无条件执行steamcmd +app_update(源码第 2–17 行,没有"文件已存在则跳过"的判断)。两个容器共享同一游戏目录时,会并发对同一批文件跑 SteamCMD 更新,有文件损坏风险。
若要采用,稳妥流程是:停掉所有实例 → 单独跑一次更新 → 再一起启动; 并且需要改造
entry.sh或覆盖启动命令来跳过第二个实例的更新步骤。在验证完成前,请勿在生产环境使用。
7.3 启用实例 2
docker compose --profile instance2 up -d
启用前确认硬件满足 §2.2 的双实例要求。
8. 镜像中未见于文档的环境变量
以下变量不在镜像的默认 ENV 列表里,是从 entry.sh 源码中确认的 [实测],
但相当有用:
8.1 SERVER_NAME
设置后,entry.sh 会把它写入 Server.cfg 的 ServerName(源码第 22–25 行):
sed -i -e "s/^ServerName=.*/ServerName=\"${SERVER_NAME}\"/" .../Server.cfg
省去手工改配置文件。注意它每次启动都会覆盖 ServerName,
所以如果你在 Server.cfg 里手改了服务器名,又设了这个变量,手改的会被覆盖。
8.2 MULTIHOME
绑定到指定网卡 IP(源码第 47–51 行)。留空 / 0.0.0.0 / 127.0.0.1 时被忽略。
多实例若各有独立 IP,用它比纯靠端口错开更清晰,也能让每个实例都用标准端口。
9. 日常运维
更新服务端
entry.sh 每次启动都跑 SteamCMD 更新,所以游戏发新版本时:
docker compose restart squad1
更新镜像本身:
docker compose pull && docker compose up -d
游戏文件在卷里,不会丢。
查看日志
docker compose logs -f --tail=200 squad1
服务端自身日志在 /srv/squad/inst1/SquadGame/Saved/Logs/。
容器日志轮转已在 docker-compose.yml 里配置(50MB × 3)。也可以在
/etc/docker/daemon.json 里设为全局默认。
停止
docker compose stop squad1
[实测]
entry.sh最后一行是bash SquadGameServer.sh ...而非exec, PID 1 是 bash 脚本。bash 在同步等待子进程时不转发 SIGTERM, 所以优雅停止未必真的优雅。docker-compose.yml里设了stop_grace_period: 120s尽量留出落盘时间,但不保证。 重要操作前建议先在游戏内用 RCON 确认无人在线。
备份
只需备份配置,不含游戏本体:
tar czf squad-config-$(date +%F).tar.gz -C /srv/squad/inst1/SquadGame ServerConfig
10. 已知问题与限制
诚实声明,避免误判。
10.1 RANDOM 环境变量在此镜像中无效 [实测]
镜像默认设了 RANDOM=NONE,entry.sh 第 61 行把 ${RANDOM} 传给服务端。
但 RANDOM 是 bash 内置特殊变量,环境变量覆盖不了它 —— 每次读取都返回随机整数。
实测:
$ env RANDOM=NONE bash -c 'echo ${RANDOM}'
18575
$ env RANDOM=NONE bash -c 'echo ${RANDOM}'
2645
所以传给 SquadGameServer.sh 的实际上是随机数字,不是 NONE。
设置这个变量没有任何效果,不管设成什么。
地图轮换请改用 MapRotation.cfg 控制。
10.2 其他
- §7.2 共享游戏文件方案未实测,且已知有并发更新风险,不要直接用于生产
- 免费服人数上限未经确认。镜像默认
FIXEDMAXPLAYERS=80; 更高人数是否需要 Offworld 授权(License.cfg),请向官方确认,本文不做断言 - 镜像只有
linux/amd64,无 arm64。Apple Silicon 上只能搬运,无法本地运行验证 MODS变量格式是 bash 数组字面量(如(1959152751 2016356569)), 不是逗号或空格分隔的普通字符串 —— 源码用declare -a MODS="${MODS}"解析- 宿主机
ulimit -n若为默认 1024 偏低。已在 compose 中给容器单独调高,未改动宿主机
11. 排障
| 现象 | 原因 | 处理 |
|---|---|---|
http: server gave HTTP response to HTTPS client |
未配 insecure-registries | §5.3 |
| 服务器在游戏内搜不到 | 安全组未放行 UDP,或用了 -p 而非 host 网络 |
§3.3 / §4.3 |
| 第二实例起不来 | BEACONPORT 或 RCON 端口冲突 | §4.1 |
| 改了 RCONPORT 后 RCON 连不上 | Rcon.cfg 没跟着变(sed 只匹配 21114) |
§7.1 |
| 容器反复重启 | 内存不足被 OOM kill | dmesg | grep -i oom 确认;降人数或调 mem_limit |
| 下载卡住不动 | Steam CDN 限速或中断 | 看日志;SteamCMD 会自动重试 |
| 配置改了不生效 | 未重启容器 | docker compose restart |
| 容器内无权限写 | 数据目录 owner 不是 uid 1000 | sudo chown -R 1000:1000 <数据目录> |
设了 RANDOM 没效果 |
镜像 bug,该变量无效 | §10.1 |
12. 参考
- 官方 wiki(中文):https://squad.fandom.com/wiki/Server_Installation/zh
- 镜像仓库:https://hub.docker.com/r/cm2network/squad/
- Squad AppID:
403240,创意工坊 ID:393380
官方 wiki 中文页用 6301 / 26301 作端口示例,那是部分托管商的习惯值, 不是镜像默认值。本文统一采用镜像默认的 Squad 官方标准端口 7787 / 27165。