Sunshine Docker Compose 部署指南

Sunshine v2026.914.233613 的 Docker Compose 部署全流程实测:镜像 tag 选型(OS 后缀陷阱)、GameStream 端口契约、配置目录属主 uid 1001 陷阱、首启凭据初始化、Moonlight PIN 配对 API 闭环、持久化数据布局、6 条排障实录与反代/升级/卸载(实测环境 2026-11-03)。

SunshineSunshineDocker Compose游戏串流2026/11/03

Sunshine Docker Compose 部署指南

目标:用 Docker Compose 把 Sunshine(v2026.914.233613)部署为游戏串流主机端,覆盖镜像选型、端口契约、首启凭据初始化、Moonlight PIN 配对全流程、持久化与常见故障。本文所有回显均为 Ubuntu 24.04.5 LTS 宿主机上逐条实测照抄;宿主机无 GPU 驱动,串流画面链路不可用,但 Web UI / REST API / 配对 / 配置面完整实测通过(这正是无 GPU 服务器评估 Sunshine 管理面的可行形态)。

1. 环境信息

项目值
宿主机 OSUbuntu 24.04.5 LTS(x86_64,内核 7.0.0-38-generic)
CPU / 内存6 核 / 16 GB
GPU无独显驱动可用(容器未挂 /dev/dri,串流编码链路不可用,仅实测管理面)
Docker29.9.2(linux/amd64)
Docker Composev5.6.1
镜像ghcr.io/lizardbyte/sunshine:v2026.914.233613-ubuntu-24.04
容器基座Ubuntu 24.04.4 LTS
部署栈名sunshine-2026.914.233613-stack
管理端口宿主 8135 → 容器 47990(Web UI / API,HTTPS)
示例账号admin / q1w2e3r4

⚠️ 硬件前置:Sunshine 的串流主体功能要求主机具备 GPU 编码能力(NVENC/AMF/QSV/VAAPI 任一)与可抓取的显示输出。纯无 GPU 服务器按本文部署后只能使用 Web UI/API/配置面,客户端连接会失败——这是产品形态使然,不是部署错误。

2. 版本选型依据

  • 最新稳定版 v2026.914.233613(GitHub Releases,发布于 2026-09-15,查询 2026-11-03)。版本号为日期式 YYYY.MDD.HHMMSS。
  • ghcr.io 镜像 tag 与发布 tag 对应,但必须带 OS 后缀:v2026.914.233613-ubuntu-24.04。实测裸 latest tag 的构建缺运行时依赖,启动即崩:
$ docker run --rm --entrypoint sunshine ghcr.io/lizardbyte/sunshine:latest --version
sunshine: error while loading shared libraries: libva.so.2: cannot open shared object file: No such file or directory
  • 带后缀 tag 启动正常,--version 回显:
$ docker run --rm --entrypoint sunshine ghcr.io/lizardbyte/sunshine:v2026.914.233613-ubuntu-24.04 --version
[2026-11-03 02:12:16.482]: Info: Sunshine version: 2026.914.233613 commit: 63d35f702ee9e362e43263742981836ec0710384
[2026-11-03 02:12:16.482]: Info: Package Publisher: LizardByte
[2026-11-03 02:12:16.482]: Info: Publisher Website: https://app.lizardbyte.dev
[2026-11-03 02:12:16.482]: Info: Get support: https://app.lizardbyte.dev/support

结论:生产固定 v2026.914.233613-ubuntu-24.04;nightly 通道 tag 为 nightly-ubuntu-24.04(ghcr 标签列表,查询 2026-11-03),仅测试环境用。

3. 部署前置

3.1 端口契约

容器端口宿主端口(本例)用途
47990/tcp8135Web UI / REST API(HTTPS)
47984/tcp47984GameStream HTTPS(TLS+客户端证书)
47989/tcp47989GameStream HTTP(serverinfo/pair 握手)
48010/tcp48010RTSP 会话协商
47998/47999/48000/udp同名控制/视频/音频流

Web UI 端口恒为配置键 port(默认 47989)+ 1 = 47990(实测 port=48999 时日志即提示 UI 在 49000,见 C1)。

3.2 配置目录属主(关键前置)

镜像内运行用户为 lizard,uid/gid = 1001(不是常见的 1000):

$ docker run --rm --entrypoint bash ghcr.io/lizardbyte/sunshine:v2026.914.233613-ubuntu-24.04 -c 'id; grep lizard /etc/passwd'
uid=0(root) gid=0(root) groups=0(root)
lizard:x:1001:1001::/home/lizard:/bin/bash

服务进程(PID 1)实际以 lizard 运行——二进制启动时自行降权,且不应用 PUID/PGID 环境变量(实测目标 uid 恒为 1001):

$ docker exec sunshine-2026.914.233613-host bash -lc 'ps -o user,pid,cmd -p 1; id'
USER         PID CMD
lizard         1 /usr/bin/sunshine
uid=1001(lizard) gid=1001(lizard) groups=1001(lizard)

挂载的配置目录若属主不对,首启会因无法创建 credentials/ 而崩溃循环(见 9.1)。部署前先建目录并授权:

$ mkdir -p config && chown 1001:1001 config
$ ls -ld config
drwxr-xr-x 3 1001 1001 4096 Nov  3 10:37 config

4. compose 编排

docker-compose.yml(全文实测运行通过):

name: sunshine-2026.914.233613-stack

services:
  sunshine:
    image: ghcr.io/lizardbyte/sunshine:v2026.914.233613-ubuntu-24.04
    container_name: sunshine-2026.914.233613-host
    hostname: sunshine-host
    environment:
      PUID: "1000"
      PGID: "1000"
      TZ: Asia/Shanghai
    ports:
      - "8135:47990"          # Web UI / HTTP API(HTTPS)
      - "47984:47984"         # GameStream HTTPS(TLS+客户端证书)
      - "47989:47989"         # GameStream HTTP(serverinfo/pair)
      - "48010:48010"         # RTSP 视频流(TCP)
      - "47998:47998/udp"     # 控制通道(UDP)
      - "47999:47999/udp"     # 视频流(UDP)
      - "48000:48000/udp"     # 音频流(UDP)
    volumes:
      - ./config:/home/lizard/.config/sunshine
    networks:
      sunshine-net:
        ipv4_address: 10.89.62.10
    restart: unless-stopped

networks:
  sunshine-net:
    driver: bridge
    ipam:
      config:
        - subnet: 10.89.62.0/24
          gateway: 10.89.62.1

要点说明:

  • name: 显式固定栈名,避免目录名变化导致项目漂移。
  • PUID/PGID 环境变量是镜像默认值残留,entrypoint 不会应用它们(实测 uid 仍为 1001),真正要做的是宿主目录属主 1001:1001。
  • 带 GPU 的游戏主机部署应追加设备映射与显示相关挂载(devices: ["/dev/dri"]、X11/Wayland socket 等),本文无 GPU 形态不演示。
  • 自定义网络固定子网与容器 IP(10.89.62.10),便于防火墙规则与 mDNS 替代发现。

5. 启动与首验

$ docker compose up -d
[+] Running 2/2
 ✔ Network sunshine-2026914233613-stack_sunshine-net  Created
 ✔ Container sunshine-2026.914.233613-host            Started

$ docker compose ps
NAME                            IMAGE                                                       COMMAND               SERVICE    CREATED          STATUS         PORTS
sunshine-2026.914.233613-host   ghcr.io/lizardbyte/sunshine:v2026.914.233613-ubuntu-24.04   "/usr/bin/sunshine"   sunshine   16 minutes ago   Up 5 minutes   0.0.0.0:47984->47984/tcp, ... 0.0.0.0:8135->47990/tcp, ...

首启日志(无 GPU/显示环境下的真实形态——编码链路 fatal,但管理面照常起来):

$ docker logs sunshine-2026.914.233613-host 2>&1 | head -30
[2026-11-03 02:12:44.766]: Warning: Couldn't find /dev/dri, kmsgrab won't be enabled
[2026-11-03 02:12:44.766]: Error: Couldn't find any of the following libraries: [libavahi-common.so.3, libavahi-common.so]
[2026-11-03 02:12:44.766]: Info: Install the Avahi libraries for mDNS support
[2026-11-03 02:12:44.766]: Error: Couldn't find any of the following libraries: [libavahi-client.so.3, libavahi-client.so]
[2026-11-03 02:12:44.766]: Info: Install the Avahi libraries for mDNS support
[2026-11-03 02:12:44.767]: Info: Sunshine version: 2026.914.233613 commit: 63d35f702ee9e362e43263742981836ec0710384
[2026-11-03 02:12:44.767]: Info: Package Publisher: LizardByte
[2026-11-03 02:12:44.767]: Info: Publisher Website: https://app.lizardbyte.dev
[2026-11-03 02:12:44.767]: Info: Get support: https://app.lizardbyte.dev/support
[2026-11-03 02:12:44.767]: Warning: [portalgrab] Failed to connect to dbus. Cannot enumerate displays, returning empty list.
[2026-11-03 02:12:44.767]: Warning: Gamepad auto is disabled due to gamepads.virtualhid-not-available
[2026-11-03 02:12:44.767]: Warning: Gamepad generic is disabled due to gamepads.virtualhid-not-available
[2026-11-03 02:12:44.767]: Warning: Gamepad 360W is disabled due to gamepads.virtualhid-not-available
[2026-11-03 02:12:44.767]: Warning: Gamepad DS5 is disabled due to gamepads.virtualhid-not-available
[2026-11-03 02:12:44.767]: Warning: Gamepad DualSense is disabled due to gamepads.virtualhid-not-available
[2026-11-03 02:12:44.767]: Warning: Gamepad Steam is disabled due to gamepads.virtualhid-not-available
[2026-11-03 02:12:44.767]: Fatal: Unable to find display or connected
[2026-11-03 02:12:44.767]: Fatal: Unable to find display or connected
[2026-11-03 02:12:44.767]: Fatal: Unable to find display or connected
[2026-11-03 02:12:44.767]: Fatal: Unable to find display or connected
[2026-11-03 02:12:44.767]: Fatal: No available encoder backends: [nvenc, vulkan, vaapi, software]
[2026-11-03 02:12:44.767]: Info: No available encoder backends: [nvenc, vulkan, vaapi, software]
[2026-11-03 02:12:44.767]: Info:
[2026-11-03 02:12:44.767]: Info:   Open the Web UI to set your new username and password and getting started
[2026-11-03 02:12:44.767]: Info:
[2026-11-03 02:12:44.767]: Info: Configuration UI available at [https://localhost:47990]
[2026-11-03 02:12:45.030]: Info: Starting main loop
[2026-11-03 02:12:45.030]: Error: System tray is not initialized
[2026-11-03 02:12:45.030]: Info: Main loop has exited

解读:

  • No available encoder backends: [nvenc, vulkan, vaapi, software]——四类后端探测全失败(无 GPU/显示),串流不可用;
  • Open the Web UI to set your new username and password——无凭据时等待首启初始化;
  • Configuration UI available at [https://localhost:47990]——管理面照常监听;
  • System tray is not initialized / Main loop has exited——无头环境托盘不可用,主循环退出但 Web UI 线程存活(服务仍在跑,见 9.5 对 restart 的影响)。

宿主端口监听核验:

$ ss -ltn | grep -E ':(8135|47984|47989|48010)\s'
LISTEN 0 4096 0.0.0.0:48010 0.0.0.0:*
LISTEN 0 4096 0.0.0.0:47989 0.0.0.0:*
LISTEN 0 4096 0.0.0.0:47984 0.0.0.0:*
LISTEN 0 4096 0.0.0.0:8135  0.0.0.0:*

GameStream 握手面(47989)直接可探:

$ curl -s "http://127.0.0.1:47989/serverinfo?uniqueid=00000000-0000-4000-8000-000000000000&hostaddress=10.89.62.1&protocol=128&appversion=7.1.431"
<root appversion="7.1.431.-1" GfeVersion="3.23.0.74" protocolversion="399" serverinfo="7.1.431.-1" ...>
  <hostname>sunshine-host</hostname>
  <localip>10.89.62.10</localip>
  <state>SUNSHINE_SERVER_FREE</state>
  <HttpsPort>47984</HttpsPort>
  <ExternalPort>47989</ExternalPort>
  ...
</root>

6. 首启初始化(Web UI 配对流程实测)

浏览器打开 https://<宿主IP>:8135(自签证书需信任;未设凭据时自动跳转 /welcome 创建账号)。等价 API 流程实测如下:

$ curl -sk -o /dev/null -w "code=%{http_code} redirect=%{redirect_url}\n" https://127.0.0.1:8135/
code=307 redirect=https://127.0.0.1:8135/welcome

$ curl -sk -X POST -H 'Content-Type: application/json' \
    -d '{"newUsername":"admin","newPassword":"q1w2e3r4","confirmNewPassword":"q1w2e3r4"}' \
    https://127.0.0.1:8135/api/password -w " code=%{http_code}\n"
{"status":true} code=200

凭据创建后,Web UI 与 API 统一走 HTTP Basic 认证(realm 即 GameStream 主机名):

$ curl -sk -D - -o /dev/null https://127.0.0.1:8135/ | grep -iE '^HTTP|www-auth'
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Basic realm="Sunshine Gamestream Host", charset="UTF-8"

$ curl -sk -o /dev/null -w "code=%{http_code}\n" -u admin:q1w2e3r4 https://127.0.0.1:8135/
code=200

$ curl -sk -o /dev/null -w "code=%{http_code}\n" -u admin:wrongpass https://127.0.0.1:8135/
code=401

凭据落盘为 config/sunshine_state.json(密码为 salt 派生哈希,非明文):

$ cat config/sunshine_state.json
{
    "username": "admin",
    "salt": "6S&yMBx5gPuzi9an",
    "password": "5D6BA198AD1FD6B5120FD093D7EB101FBAFCD48467181297D8A11D28DE1AFE07"
}

7. Moonlight 客户端配对实测

真实 Moonlight 客户端在 UI 上显示 4 位 PIN 并发起握手;这里用 GameStream 协议请求模拟客户端侧,完整验证主机侧配对闭环。

1)客户端发起配对(uniqueid 必须是 UUID 格式,phrase=getservercert 注册会话):

$ curl -s "http://127.0.0.1:47989/pair?uniqueid=12345678-9abc-def0-1234-56789abcdef0&phrase=getservercert&clientcert=<hex证书>&salt=<32位hex>&devicename=Pixel-8"
(该请求会挂起等待主机侧输入 PIN,最长约 2 分钟超时)

2)主机侧查看待配对请求(Web UI 的 PIN 框背后就是这组 API):

$ curl -sk -u admin:q1w2e3r4 https://127.0.0.1:8135/api/pin
{"pairings":[{"address":"10.89.62.1","id":"2b07da1c95c2735e69613739fac44279","name":"Pixel-8"}],"status":true}

3)提交 PIN 批准(pairing_id 为上一步的 id,PIN 必须恰好 4 位数字):

$ curl -sk -u admin:q1w2e3r4 -X POST -H 'Content-Type: application/json' \
    -d '{"pairing_id":"2b07da1c95c2735e69613739fac44279","pin":"1234","name":"Pixel-8"}' \
    https://127.0.0.1:8135/api/pin

4)等待中的客户端请求立即收到结果(paired=1 + 主机证书 hex):

<root status_code="200"><paired>1</paired><plaincert>308202543082013C...</plaincert></root>

5)待配对列表清空:

$ curl -sk -u admin:q1w2e3r4 https://127.0.0.1:8135/api/pin
{"pairings":[],"status":true}

细节:PIN 由 salt+pin 派生 AES 密钥参与后续握手,错误 PIN 不会在这一步被 API 拒绝,而是导致客户端侧校验失败、配对中断(实测用 9999 批准时 API 同样返回 paired=1,但真实客户端会报 PIN 不匹配)。完整握手完成后客户端证书登记进 sunshine_state.json,GET /api/clients/list 可见。

8. 持久化与数据布局

./config 挂载即全部状态,首启后实测:

$ ls -la config/
total 40
drwxr-xr-x 3 1001 1001 4096 Nov  3 10:37 .
-rw-r--r-- 1 1001 1001  679 Nov  3 10:28 apps.json
drwxr-xr-x 2 1001 1001 4096 Nov  3 10:28 credentials
-rw-r--r-- 1 1001 1001    0 Nov  3 10:28 sunshine.conf
-rw-r--r-- 1 1001 1001 4520 Nov  3 10:37 sunshine.log
-rw-r--r-- 1 1001 1001  116 Nov  3 10:31 sunshine.log.1
-rw-r--r-- 1 1001 1001 5070 Nov  3 10:34 sunshine.log.2
-rw-r--r-- 1 1001 1001  144 Nov  3 10:29 sunshine_state.json

(宿主上 uid 1001 无对应用户名,故显示数字;容器内同一路径显示为 lizard:lizard。)

  • apps.json:默认含 Desktop / Low Res Desktop / Steam Big Picture 三个流应用;
  • credentials/:cacert.pem/cakey.pem,CN=Sunshine Gamestream Host 自签证书(有效期约 40 年,Web UI 与 GameStream TLS 共用);
  • sunshine.conf:初始为空,UI 保存配置后写入键值行(见 C1);
  • sunshine.log(.1~.5):日志轮转保留 5 份。

备份迁移只需打包 config/ 目录(含已配对客户端与凭据)。

9. 常见问题(排障实录)

9.1 首启崩溃循环:Permission denied

现象:容器反复 Restarting (0),日志:

[2026-11-03 02:28:39.520]: Error: Failed to apply config: filesystem error: cannot create directories: Permission denied [/home/lizard/.config/sunshine/credentials/]

原因:宿主 config/ 目录属主为 root(uid 0),而镜像内用户是 uid 1001(lizard),PUID/PGID 环境变量不被 entrypoint 应用。

解决:

$ chown 1001:1001 config
$ docker compose up -d

9.2 裸 latest tag 起不来

libva.so.2: cannot open shared object file——无 OS 后缀的最小构建缺编码依赖,改用 -ubuntu-24.04 后缀 tag(见第 2 节)。

9.3 第二个实例启动报端口占用

同时跑两个 Sunshine 时后启动者 fatal 退出:

[2026-11-03 02:25:52.278]: Fatal: Couldn't bind RTSP server to port [48010], Address already in use

48010 是 RTSP 协商端口(本版本实测确认,并非文档常见的 47990 说法);同机双实例必须错开 port 与 RTSP。

9.4 mDNS 自动发现不可用

日志 Couldn't find any of the following libraries: [libavahi-common.so.3, ...]——镜像未打包 Avahi 库,Moonlight 客户端无法自动发现主机,需手动添加主机 IP(端口 47989)。功能不受影响。

9.5 无头环境 /api/restart 挂死

无 GPU/显示时主循环已退出(Main loop has exited),此时调用 POST /api/restart 会触发中断但进程无法自行收尾:

[2026-11-03 02:17:17.047]: Info: Interrupt handler called
[2026-11-03 02:17:27.048]: Fatal: 10 seconds passed, yet Sunshine's still running: Forcing shutdown

进程卡住不退出,需外部重启(docker compose restart)。带显示的正常主机不受影响。

9.6 浏览器证书告警

自签证书(CN=Sunshine Gamestream Host)触发告警属预期;长期使用建议反代挂受信证书(见第 10 节)或在客户端信任该 CA。

10. 反向代理与 HTTPS

Web UI 是 HTTPS 自签证书 + Basic 认证。如需受信证书与 443 统一入口,反代模板(nginx,配置模板非本文实测项):

server {
    listen 443 ssl;
    server_name sunshine.example.com;
    ssl_certificate     /etc/nginx/certs/sunshine.crt;
    ssl_certificate_key /etc/nginx/certs/sunshine.key;

    location / {
        proxy_pass https://127.0.0.1:8135;
        proxy_ssl_verify off;
    }
}

注意:反代只服务 Web UI/API(47990);GameStream/RTSP/UDP 流媒体端口(47984/47989/48010/47998-48000)不走 HTTP 反代,需直接放通或端口映射,否则 Moonlight 能配对但无法串流。

11. 升级与回滚

升级:改 compose 中 image tag 后重新拉取并滚动更新(状态全在 config/,无损):

$ docker compose pull
 Image ghcr.io/lizardbyte/sunshine:v2026.914.233613-ubuntu-24.04 Pulling
 Image ghcr.io/lizardbyte/sunshine:v2026.914.233613-ubuntu-24.04 Pulled
$ docker compose up -d

回滚:image tag 改回旧版本(如 v2026.617.162634-ubuntu-24.04)再 up -d。sunshine_state.json/apps.json/credentials/ 向后兼容,回滚前备份 config/ 即可。

12. 卸载

$ docker compose down
[+] Running 2/2
 ✔ Container sunshine-2026.914.233613-host Removed
 ✔ Network sunshine-2026914233613-stack_sunshine-net Removed
$ rm -rf config/        # 连同凭据/配对/配置一并清除,先备份再删

13. 参考链接