pyLoad Docker Compose 部署实战
pyLoad(pyload-ng)Docker Compose 部署实测:官方镜像事实核查(pyload/pyload 无预构建产物)、linuxserver/pyload-ng 编排、WebUI 初始化、下载-解包闭环验证与五类真实踩坑排障实录(SSRF 防护、解包插件默认关闭、UnZip 句柄 bug 等)。
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 的编排文件。本文实测时对这一「常识」做了逐层核查,结论与直觉相反,强烈建议读者了解后再动手:
- 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 命名空间下不存在任何已发布镜像。 - 官方源码里确实有 Dockerfile(
github.com/pyload/pyloadmain 分支),基于lsiobase/alpine(s6 overlay 多阶段构建),EXPOSE 8000(WebUI)与9666(Click'N'Load),声明/config、/downloads两个挂载点,并以PUID/PGID/TZ环境变量控制运行身份。 - 官方 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 配置参数详解》。
三、前置准备
- 确认 Docker 与 Compose 可用:
docker --version && docker compose version
# Docker version 27.x, build xxx
# Docker Compose version v2.x
- 规划目录(本文实测路径,读者可换成自己的数据盘路径):
mkdir -p /opt/data/pyload/config /opt/data/pyload/downloads
- 选定宿主端口: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 初始化与账号安全
- 浏览器访问
http://<服务器IP>:8133/,进入登录页; - 首次启动的默认账号与密码均为
pyload(官方默认凭据),登录后 WebUI 右上角 Settings → General(或登录页右侧栏)修改用户名与密码;官方页明确强调"强烈建议首次启动即修改默认凭据",公网可达的服务器务必改掉并收敛访问面(只在内网/VPN 暴露); - 本文实测演示环境沿用默认凭据完成全部 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条件挡住);② 队列内容持久化在 SQLitestorage表(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三段日志 + 下载目录出现解包产物。
十、参考链接
- 官方仓库 Dockerfile:
https://github.com/pyload/pyload/blob/main/Dockerfile - PyPI 官方页(Docker Images 一节):
https://pypi.org/project/pyload-ng/ - LinuxServer.io pyload-ng 镜像页:
https://hub.docker.com/r/linuxserver/pyload-ng - 本专栏配置详解:《pyLoad 配置参数详解》
- 本专栏命令篇:《pyLoad 常用命令与运维实战(核心篇)》
- 本专栏选型篇:《pyLoad 介绍与选型指南》