pyLoad Docker Compose 部署实战

pyLoad(pyload-ng)Docker Compose 部署实测:官方镜像事实核查(pyload/pyload 无预构建产物)、linuxserver/pyload-ng 编排、WebUI 初始化、下载-解包闭环验证与五类真实踩坑排障实录(SSRF 防护、解包插件默认关闭、UnZip 句柄 bug 等)。

pyLoad下载管理器NAS 自托管电子表格2026/10/28

pyLoad Docker Compose 部署实战

目标:在一台真实的 Linux 服务器上,用 Docker Compose 把 pyLoad(pyload-ng)部署起来:完成官方镜像事实核查、编排文件编写、容器启动、WebUI 初始化、真实下载任务的投递与自动解包验证,并沉淀权限、插件、下载源三类典型踩坑的排障实录。本文所有命令与回显均来自 2026-10-28 的真实实测,环境为一台 Ubuntu 22.04 服务器(Docker 27.x),WebUI 端口与下载目录均为本机实测值。

一、环境信息

项目实测值
操作系统Ubuntu 22.04(内核 5.15,x86_64)
服务运行形态Docker 27.x + Docker Compose v2(单机自建网络)
pyLoad 版本pyload-ng 0.5.0b3.dev101(pyload --version 显示 pyLoad 0.5.0,源码 VERSION 文件为 0.5.0b3)
容器镜像linuxserver/pyload-ng:latest(社区维护的 pyload-ng 官方封装);官方源码 pyload/pyload main 分支 Dockerfile 自构建(对照验证)
实验端口WebUI 宿主端口 8133 → 容器 8000(8133 为实测时本机空闲端口,读者可自行替换)
实验目录/opt/data/pyload/config(配置)、/opt/data/pyload/downloads(下载,均为 bind mount)
环境变量PUID=1000、PGID=1000、TZ=Asia/Shanghai
查证日期2026-10-28

二、部署前必须搞清的一件事:pyLoad 官方镜像的真实形态

多数教程会直接给出 image: pyload/pyload:latest 的编排文件。本文实测时对这一「常识」做了逐层核查,结论与直觉相反,强烈建议读者了解后再动手:

  1. Docker Hub 上 pyload/pyload 仓库没有可拉取的标签。实测用 registry API 直查:https://registry-1.docker.io/v2/pyload/pyload/tags/list 返回 {"errors":[{"code":"NAME_UNKNOWN","message":"repository name not known to registry"}]}——该仓库在官方 Docker Hub 命名空间下不存在任何已发布镜像。
  2. 官方源码里确实有 Dockerfile(github.com/pyload/pyload main 分支),基于 lsiobase/alpine(s6 overlay 多阶段构建),EXPOSE 8000(WebUI)与 9666(Click'N'Load),声明 /config、/downloads 两个挂载点,并以 PUID/PGID/TZ 环境变量控制运行身份。
  3. 官方 CI(.github/workflows/build-and-deploy.yml)只发布 PyPI 包,不构建任何镜像。也就是说,PyPI 项目主页 pypi.org/project/pyload-ng/ 上"Docker Images"一节给的 docker build + compose 片段是从源码构建的用法,而不是引用某个官方预构建镜像。

据此,2026-10-28 时点的实际可行路线有两条:

路线内容适用
官方源码构建git clone 官方仓库 → docker build(官方 Dockerfile)→ 本地镜像起栈想严格跟官方源码,能接受构建时间
社区现成镜像linuxserver/pyload-ng:latest(LinuxServer.io 维护,Unraid/主流 NAS 应用模板收录)大多数 NAS/服务器场景,开箱即用

两条路线本文都实测通过。下文以社区镜像为主线编排(更快、可复现),官方源码构建作为对照在同一 compose 里给出注释写法。

⚠️ 注意:pip install --pre pyload-ng[all] 是官方 PyPI 主页的裸机安装路径;容器路线的本质是把这一过程封装进镜像。无论哪条路线,配置体系(pyload.cfg)与端口、目录约定完全一致,可参考本专栏《pyLoad 配置参数详解》。

三、前置准备

  1. 确认 Docker 与 Compose 可用:
docker --version && docker compose version
# Docker version 27.x, build xxx
# Docker Compose version v2.x
  1. 规划目录(本文实测路径,读者可换成自己的数据盘路径):
mkdir -p /opt/data/pyload/config /opt/data/pyload/downloads
  1. 选定宿主端口:WebUI 用容器内 8000,宿主侧任选空闲端口(本文 8133);如需浏览器一键转发(Click'N'Load)再加 9666。可用 ss -ltn | grep <端口> 预检。

四、编写 Compose 编排文件

在实验目录写入 docker-compose.yml(与实测文件一致):

name: pyload-050b3-stack

services:
  pyload:
    image: linuxserver/pyload-ng:latest
    # 官方源码构建对照写法(先 docker build -t pyload:050b3-local 官方仓库根目录):
    # image: pyload:050b3-local
    container_name: pyload-050b3-webui
    environment:
      - PUID=1000          # 宿主运行账号 uid,决定 config/downloads 的读写身份
      - PGID=1000          # 宿主运行账号 gid
      - TZ=Asia/Shanghai
    volumes:
      - /opt/data/pyload/config:/config       # 配置与数据库(pyload.cfg、pyload.db 等)
      - /opt/data/pyload/downloads:/downloads # 下载产物目录
    ports:
      - "8133:8000"    # WebUI
      - "8143:9666"    # Click'N'Load(浏览器一键转发,不需要可删)
    restart: unless-stopped

说明两点(均为官方 Dockerfile 事实):

  • 官方镜像的容器内入口由 s6 托管,进程以 PUID/PGID 指定用户运行,/config 内生成 settings/(配置)、data/(SQLite 数据库)等子目录;
  • 镜像内置 pyLoad 启动参数为 --userdir /config --storagedir /downloads(对应 pyload CLI 的目录重定向参数,见本专栏《pyLoad 常用命令与运维实战(核心篇)》的 CLI 参数表)。

启动:

cd /opt/data/pyload && docker compose up -d
# 实测回显:
# [+] Running 2/2
#  ✔ Network pyload-050b3-stack_default  Created
#  ✔ Container pyload-050b3-webui        Started

首次拉起后观察启动日志(截取关键行):

docker logs --tail 12 pyload-050b3-webui
# [s6-init] making user provided files available at /var/run/s6/etc...
# [ls.io-init] done.
# [2026-10-28 12:12:xx]  INFO  pyload  Connecting to Database: /config/data/pyload.db
# [2026-10-28 12:12:xx]  INFO  pyload  Activated addons: ExternalScripts, TORRENT, UserAgentSwitcher, ...

五、WebUI 初始化与账号安全

  1. 浏览器访问 http://<服务器IP>:8133/,进入登录页;
  2. 首次启动的默认账号与密码均为 pyload(官方默认凭据),登录后 WebUI 右上角 Settings → General(或登录页右侧栏)修改用户名与密码;官方页明确强调"强烈建议首次启动即修改默认凭据",公网可达的服务器务必改掉并收敛访问面(只在内网/VPN 暴露);
  3. 本文实测演示环境沿用默认凭据完成全部 API 验证,生产环境请以自己的强口令替换。

登录后 WebUI 可见的三个常用页签:Queue(队列)、Collector(链接收集器)、Logs(日志),对应 REST API 的 get_queue、get_collector 与日志端点。

六、部署验证:三层确认

第一层:服务状态 API

curl -s -u pyload:pyload http://127.0.0.1:8133/api/status_server
# 实测回显:
# {"pause": false, "active": 0, "queue": 1, "total": 4, "speed": 0, "download": true, "reconnect": false, "captcha": false, "proxy": false}

第二层:OpenAPI 规范可读(REST API 自动暴露):

curl -s -u pyload:pyload http://127.0.0.1:8133/api/openapi.json | head -c 200
# 实测回显(截取):
# {"openapi": "3.0.1", "info": {"title": "pyLoad", "description": "The free and open-source Download Manager written in pure Python", ...

第三层:真实下载闭环(含解包,实测 2026-10-28)。通过 REST API 投递一个公网 zip(pyLoad 官方仓库 stable 分支源码包),确认「下载 → 自动解包 → 压缩包转储」全链路:

curl -s -u pyload:pyload -X POST http://127.0.0.1:8133/api/add_package \
  -H 'Content-Type: application/json' \
  -d '{"name":"extract-final","links":["https://codeload.github.com/pyload/pyload/zip/refs/heads/stable"],"dest":1}'
# 实测回显:8   (返回新建包的 pid)

数秒后检查下载目录(/downloads/extract-final/pyload-stable/ 出现解包后的完整源码树,压缩包被移入回收站目录):

ls /opt/data/pyload/downloads/extract-final/pyload-stable/ | head
# 实测回显:
# docs
# icons
# LICENSE.MD
# locale
# module
# pavement.py
# pyLoadCli.py
# pyLoadCore.py
# pyLoadGui.py
# README.MD

日志侧同步可见三段关键记录(实测逐行):

[2026-10-28 12:45:xx]  INFO  pyload  Package finished: extract-final
[2026-10-28 12:45:xx]  INFO  pyload  ADDON ExtractArchive: pyload-stable.zip | Extract to: /downloads/extract-final/
[2026-10-28 12:45:xx]  INFO  pyload  ADDON ExtractArchive: Moved pyload-stable.zip to trash
[2026-10-28 12:45:xx]  INFO  pyload  ADDON ExtractArchive: pyload-stable.zip | Extracting finished

这一闭环同时也验证了:队列调度(DefaultPlugin 下载)、解包插件(ExtractArchive + UnZip 后端)、回收站策略(delete + 移入 trash)默认配置下均按预期工作——前提是本文第七节的排障点已处理。

七、排障实录(实测踩坑逐条沉淀)

以下四个坑全部在本次部署实测中真实发生,按"现象 → 根因 → 解法"沉淀,读者大概率会在类似路径上再次遇到。

坑 1:私网下载源一律被拒(防 SSRF 机制)

  • 现象:把局域网 HTTP 服务器上的文件(如 http://10.x.x.x:8138/test.zip)作为链接投递后,任务直接进入 failed,日志出现 WARNING ... Server-Side host protection, refusing to download;
  • 根因:pyload-ng 内建防 SSRF 保护,源码 src/pyload/core/network/http/http_request.py 中 allow_private_ip = False 硬编码,对私网/环回地址一律拒绝下载,且无配置开关;
  • 解法:下载源必须是公网可达地址;本地文件投递可改用 upload_container 端点或 WebUI 拖拽上传(链接容器),或让 pyLoad 走公网源。家用 NAS 场景想"局域网互传"时不要试图关掉它——改用 WebUI 上传即可。

坑 2:下载完成后压缩包不解包

  • 现象:zip/tar.gz 下载完成后只落盘不解压,日志无任何 Extract 字样;
  • 根因:pyLoad 的解包是三层结构——ExtractArchive 插件(总开关,addon)+ UnZip/UnRar/SevenZip/UnTar 适配器(各自 enabled)。默认 ExtractArchive 与各适配器全部未激活(plugins.cfg 中 enabled = False);
  • 解法(任选其一):WebUI → Settings → Plugins 勾选 ExtractArchive 与 UnZip 后重启服务;或用 REST API 逐项置位(实测回显):
curl -s -H "X-API-Key: $KEY" -X POST http://127.0.0.1:8133/api/set_config_value \
  -H 'Content-Type: application/json' \
  -d '{"category":"ExtractArchive","option":"enabled","value":true,"section":"plugin"}'
# 实测回显:null   (成功,且配置持久化进 /config/settings/plugins.cfg)

坑 3:UnZip 解包报 "Attempt to use ZIP archive that was already closed"(0.5.0b3.dev101 真实 bug)

  • 现象:激活解包后 zip 下载完成,日志报 Extract failed,其上一级完整回显为 ValueError: Attempt to use ZIP archive that was already closed,解压出半个目录树;
  • 根因:UnZip 插件的 _validate_zip_symlinks() 用 with z: 语法校验 symlink,退出时把传入的 ZipFile 句柄关闭,随后的 z.extractall() 撞上已关闭句柄。0.5.0b3.dev101 实测复现,属上游 bug;
  • 解法(实测有效):容器内对该行打补丁后重启(zip 不含 symlink 时跳过校验是安全的;升级到修复版本后应撤销此补丁):
docker exec pyload-050b3-webui sh -c \
  "sed -i 's/^                self\._validate_zip_symlinks(z)$/                pass  # patched/' /lsiopy/lib/python3.12/site-packages/pyload/plugins/extractors/UnZip.py"
docker restart pyload-050b3-webui

坑 4:一次失败的解包调用把解包线程"锁死"

  • 现象:手动通过 service_call 触发 ExtractArchive.extract_package 传参错误后,无论重启服务还是重投任务,解包再也不触发,且每次启动都刷同一异常;
  • 根因:两个因素叠加——① ExtractArchive 的 extract_queued() 里 self.extracting 标志在异常路径上不会被复位(异常穿透线程后标志永久为 True,后续触发被 not self.extracting 条件挡住);② 队列内容持久化在 SQLite storage 表(identifier='ExtractArchive', key='Queue'),错误参数作为"待解包条目"被存住,每次启动 resume 都会再次炸掉解包线程;
  • 解法(实测有效):清掉坏队列 + 重启复位标志——
docker exec pyload-050b3-webui python3 -c "
import sqlite3
con = sqlite3.connect('/config/data/pyload.db')
con.execute(\"DELETE FROM storage WHERE identifier='ExtractArchive' AND key='Queue'\")
con.commit()
"
docker restart pyload-050b3-webui

之后重新投递下载任务,package_finished 事件即可正常驱动解包(即第六节实测闭环)。

坑 5(附):/api/restart 携带请求体报 500

  • 现象:curl -X POST .../api/restart -d '{}' 返回 {"error": "Internal server error"};
  • 根因:REST 层把 body 字段当关键字参数透传给 Api.restart(),而该方法不接受任何参数(TypeError: Api.restart() got an unexpected keyword argument '{}');
  • 解法:/api/restart 调用不要带请求体(裸 POST 即可,实测回显 null)。

八、常见运维操作速查

操作命令(实测通过)
启动/停止/重启栈docker compose up -d / docker compose down / docker restart pyload-050b3-webui
看实时日志docker logs -f --tail 50 pyload-050b3-webui
修改 WebUI 端口改 compose ports 左侧宿主端口后 docker compose up -d 重建
彻底重置配置docker compose down 后清空 /opt/data/pyload/config/ 再 up -d(回到默认 pyload/pyload 凭据)
配置备份详见本专栏《pyLoad 常用命令与运维实战(核心篇)》备份一节(配置目录 + 下载目录两个 tar)

九、小结

  • 2026-10-28 时点,pyLoad 官方渠道没有可直拉的 pyload/pyload 镜像:官方形态是「源码 Dockerfile + PyPI 发布」,实际部署用官方源码构建或社区 linuxserver/pyload-ng 镜像,两者目录/端口/环境变量约定一致;
  • 编排三要素:/config、/downloads 两个 bind mount + PUID/PGID/TZ + 8000/9666 端口映射;
  • 部署后必做三件事:改默认凭据、激活 ExtractArchive/UnZip 解包插件、(0.5.0b3.dev101 版本)处理 UnZip 句柄关闭 bug;
  • 下载闭环验证标准:Package finished → Extract to → Extracting finished 三段日志 + 下载目录出现解包产物。

十、参考链接