ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

LibrePhotos 环境变量部署指南:特性开关、转码缓存、日志与后台资源调优

LibrePhotos 环境变量部署指南:特性开关、转码缓存、日志与后台资源调优 LibrePhotos 环境变量部署指南特性开关、转码缓存、日志与后台资源调优【免费下载链接】librephotosA self-hosted open source photo management service.项目地址: https://gitcode.com/GitHub_Trending/li/librephotosLibrePhotos 是一款自托管、开源的私人照片管理服务。本文基于官方文档 environment-variables.md 整理聚焦 Docker Compose 部署场景下的进阶环境变量配置包括 Postgres 卷挂载变更、GPU 加速、后台 worker 与资源限制、7 个FEATURE_*特性开关及其对机器学习 sidecar 服务的影响、转码缓存与实时转码上限、日志配置、公网地址与子路径托管、容器命名以及管理员账户变量。读完本文你将掌握如何在不动代码的前提下用.env与环境变量把 LibrePhotos 调教得贴合自己的机器配置。本文所有环境变量均与 docker-compose.yml 中 backend 服务的environment段一一对应实际部署时以.env中的键值为准。PostgreSQL v18 卷挂载变更:::warning Breaking Change Postgres v18 起在 Docker/Kubernetes 中向/var/lib/postgresql/data挂载卷可能失败上游将数据目录强制改为/var/lib/postgresql/MAJOR/docker并从/var/lib/postgresql/data创建了一个指向/var/lib/postgresql的符号链接。 :::必须采取的行动把卷挂载到/var/lib/postgresql而不是/var/lib/postgresql/data。pgautoupgrade会检测到已有数据并将其迁移到新结构。仓库自带的 docker-compose.yml 已经正确采用该做法db: image: pgautoupgrade/pgautoupgrade:latest container_name: db restart: unless-stopped environment: - POSTGRES_USER${dbUser} - POSTGRES_PASSWORD${dbPass} - POSTGRES_DB${dbName} volumes: - ${data}/db:/var/lib/postgresql数据库、缩略图等数据统一放在.env中data指向的主机目录下便于备份与迁移。利用 GPU 加速LibrePhotos 的神经网络与人脸检测face detection依赖 CPU 时性能有限官方提供 GPU 镜像用于加速。步骤如下更新 NVIDIA GPU 驱动确保系统已安装最新版 NVIDIA 驱动。安装 NVIDIA Container Toolkit为 Docker 容器启用 GPU 支持sudo apt install nvidia-container-toolkit更换 backend 镜像为 GPU 版本services: backend: image: reallibrephotos/librephotos-gpu:${tag} # ... 其他配置保持不变 ...在 Compose 中声明 GPU 资源deploy段services: backend: image: reallibrephotos/librephotos-gpu:${tag} # ... 其他配置保持不变 ... deploy: resources: reservations: devices: - driver: nvidia count: 1 capabilities: [gpu]:::note GPU 镜像仅支持 x86 架构ARM 不支持 GPU 镜像。 :::该镜像在仓库中有完整构建定义deploy/docker/backend-gpu/Dockerfile 及其 entrypoint.sh。需要留意的是GPU 镜像基于 Ubuntu 22.04ffmpeg 4.4会影响到下文 实时转码上限 中部分参数的实际效果。限制 CPU 与内存占用backend 容器内运行着两类进程gunicorn响应 API 请求和一组后台 worker扫描图库——缩略图、人脸检测、字幕生成。LibrePhotos 几乎全部的 CPU/内存开销都来自后台 worker且默认每个 CPU 核心启动一个 worker。先设 worker 数量而不是 CPU 上限直觉做法往往是在docker-compose.yml里给容器加 CPU 上限services: backend: cpus: 0.8这通常适得其反。cpus:只是限流容器不会改变 worker 数量——worker 池的规模取决于宿主机上报的核心数cpus:限制改变不了它。结果是你依然拥有同样多的 worker 去争抢一小部分 CPU最先崩的往往是 API请求超过 gunicorn 超时后 worker 被杀后端日志刷出[ERROR] Worker (pid:113) was sent SIGKILL! Perhaps out of memory?这条消息是 gunicorn 对被杀 worker 的通用文案。在 CPU 受限的宿主机上它几乎总是意味着请求太慢而不是机器内存不足。正确做法是直接设 worker 数。在.env中# 只保留一个后台 worker而不是每核一个 workerConcurrency1这才是真正把资源让回给宿主机其他程序的设置。扫描会变慢但 API 保持响应、不会被杀。每个 worker 常驻内存超过约 300 MB 后会被回收见 production.py 中Q_CLUSTER的max_rss: 300000因此 worker 数量也是控制后端内存占用规模的主要杠杆。如果你还想要一个硬上限当 worker 数量合理之后容器级限制作为兜底是可行的。同时要调大 API 超时以免被限流的请求在半途被杀workerConcurrency1 # gunicorn 杀死请求前的等待秒数默认 30 gunicornTimeout120services: backend: cpus: 2.0 mem_limit: 4g如果比起给所有核心分配一部分你更想把 LibrePhotos 钉在特定核心上用cpuset也可以services: backend: cpuset: 0-2Docker Swarm 下对应的是deploy.resources块services: backend: deploy: resources: limits: cpus: 2.0 memory: 4G:::notecpu_shares只在容器之间设置相对优先级当没有其他容器争抢 CPU 时它看起来毫无作用这就是它常常像没设一样的原因。 ::::::warning 不要把容器限制设得太狠以至于首次扫描都无法完成。人脸检测和字幕生成要加载相当大的模型内存低于约 2 GB 时后端会被内核杀死——而那才是真正的 OOM。 :::代码层面production.py 对 worker 数的解释值得展开WORKER_CONCURRENCY未设置时django-q 回退到cpu_count()——即使在容器被cpus:限流的情况下它报告的也是宿主机的核心数所以池子宽度不变限制只会让它挨饿。这与文档结论互为印证。用特性开关关掉重活扫描图库中每一项真正消耗 CPU、内存或网络的环节都可以在部署时用一个FEATURE_*环境变量关闭。它们默认全部为 on所以不设置任何开关的升级不会改变现有行为。可接受的 on 值为true、1、yes、on任意大小写除此之外一律视为 off。该判定逻辑位于 production.py 的_env_flag()。变量.env键关闭后停止的工作FEATURE_VIDEOfeatureVideo视频文件不再导入。扫描像跳过无法读取的文件一样跳过它们不创建Photo不生成视频缩略图。live photo 内的运动视频也不再提取保持为普通静图。FEATURE_FACE_DETECTIONfeatureFaceDetection无论扫描还是上传都不再从照片中提取人脸。人脸扫描被移出扫描流水线UI 中的Scan faces会报错而不是启动作业。人脸识别服务不启动模型永不加载。FEATURE_FACE_CLUSTERfeatureFaceCluster人脸仍会检测但不再聚合成可命名的人物。聚类在每次人脸扫描末尾被跳过Train faces报错。FEATURE_IMAGE_CAPTIONINGfeatureImageCaptioning扫描时与照片上的 Generate caption 按钮都不再生成自动字幕。你手动输入的字幕不受影响。字幕服务不启动。FEATURE_REVERSE_GEOCODINGfeatureReverseGeocodingGPS 坐标不再反查地名因此不会向你的地图服务商发请求。照片保留坐标仍会出现在相册地图和单张照片地图上但因为没有地名不会出现在 Places 页面、没有 Places 相册、也无法按地点搜索。搜索栏搜地点仍然有效。FEATURE_SCENE_CLASSIFICATIONfeatureSceneClassification不再按照片内容打标签海滩、厨房、日落……新照片的 Things 相册保持为空。打标签服务不启动标签模型永不加载。FEATURE_PROCESS_EMBEDDED_MEDIAfeatureProcessEmbeddedMedialive photo 或 motion photo 内嵌的短视频不再提取保持为普通静图。注意提取生效的前提是FEATURE_VIDEO也处于开启状态。该开关与其它开关唯一的差异见 Feature Toggles。关闭某个特性永远不会删除已经生成的内容——已有的字幕、人脸和地名会保留在数据库并继续可见。重新打开后扫描会从断点继续。用官方 Compose 部署时在.env中设置小驼峰键# 一台 CPU 不富裕的机器保留照片跳过机器学习 featureFaceDetectionfalse featureFaceClusterfalse featureImageCaptioningfalse featureSceneClassificationfalse如果使用自建的 Compose 文件或 Kubernetes manifests则直接把FEATURE_*传给 backend 容器services: backend: environment: - FEATURE_VIDEOfalse:::noteFEATURE_PROCESS_EMBEDDED_MEDIA只在文件首次导入的瞬间被检查。对已经扫描过的图库打开它不会为已有照片提取任何内容——即使Rescan All Photos也不行——只有之后新增的文件会受影响。 :::机器学习服务跟随开关:::note 以下行为尚未进入已发布镜像。它在dev分支可用将随下一版本发布在 1.1.0 上开关会停止处理但服务仍然会启动。 :::后端在独立的 sidecar 进程中运行重型模型一个 watchdog 会重启其中任何挂掉的进程。关闭的开关会阻止对应服务启动watchdog 也对其置之不理而不是一分钟后把它拉起来——内存节省正来自这里因为加载后的模型无论是否被调用都占着内存。开关停止启动的服务FEATURE_FACE_DETECTIONface_recognitionFEATURE_IMAGE_CAPTIONINGimage_captioningFEATURE_SCENE_CLASSIFICATIONtags其余服务——exif、thumbnail、clip_embeddings、image_similarity——承载着其余 LibrePhotos 赖以运行的扫描与搜索因此没有开关、始终运行。其余特性开关FEATURE_VIDEO、FEATURE_FACE_CLUSTER、FEATURE_REVERSE_GEOCODING、FEATURE_PROCESS_EMBEDDED_MEDIA在 backend 进程内部生效没有可停止的独立服务。源码证实了这张表services.py 定义了SERVICES含各自端口与SERVICE_FEATURE_FLAGS的映射start_service.py的handle()在all模式下会跳过is_service_enabled()判定为关闭的服务并打印原因start_service.py。check_services()services.py就是那个每分钟巡检一次的 watchdog对已关闭的服务保持沉默不重启。OCR也没有环境变量但它并非始终开启它跟随 Site Settings 中的OCR model选择而该设置默认未选中任何模型。一旦选了模型服务即启动改回 none 则停止启动——watchdog 每分钟重读一次该设置两个方向都无需重启。backend 无法读取的配置比如数据库还在启动中按已选择模型处理因此数据库未就绪时永远不会把服务停掉。这与SERVICE_SITE_GATES {ocr: _ocr_model_selected}services.py的实现一致。被跳过的服务会在 backend 启动日志中只出现一次所以docker logs backend能告诉你某个东西为什么没在运行。Admin Area 的Services列表会把这类服务显示为Disabled而非 unhealthy且不提供 Start 按钮。缓存的视频转码用户开启Always transcode videosSettings → Experimental后浏览器无法解码的容器或编码格式的视频会在播放时被实时转码。实时转换没有已知长度因此响应不带Content-Length和Accept-Ranges完全无法拖动进度——没有时长、没有进度条、不能跳过。因此同一次转码会被写盘一次之后每次播放同一视频都改由该文件提供作为可拖动的普通 mp4。首次播放仍实时流式进行启动速度与现在完全一样副本是在流结束之后才写入的而不是边播边写因为实时转码必须跑在播放之前分给第二个 ffmpeg 会拖慢它。副本进程还被 niced并限制只用一半核心因此播放、缩略图和正在进行的扫描都优先于它。缓存的开销约为每分钟视频 1020 MB——画面运动量决定落在区间何处——并且只针对真正以该设置打开过的视频。没人开启它就什么都不写。变量.env键默认作用TRANSCODE_CACHE_MAX_GBtranscodeCacheMaxGb10缓存可增长的最大体积单位 GB。约每 GB 对应一小时视频。设为0完全关闭缓存只保留实时流式转码。TRANSCODE_CACHE_MIN_FREE_GBtranscodeCacheMinFreeGb2卷上保留不动的最小空闲空间单位 GB。缓存绝不写进这块空间若空闲空间掉入该区间正在进行的转换会被放弃。TRANSCODE_CACHE_MAX_CONCURRENTtranscodeCacheMaxConcurrent1可同时写入的转换数量。每个都是一条 ffmpeg 进程调大它是在用 CPU 换更多视频更快可拖动。TRANSCODE_CACHE_NICEtranscodeCacheNice10后台转换对其它一切退让的程度作为nice值。0关闭这份客气。两个上限任一触达时删除最久未播放的条目直到能放下新文件即使这样还不够该视频就干脆不缓存按原来的方式实时播放。任何文件都不会在其转换完成前被提供因此中断的转换不会留下半可播文件。文件存放在protected_media/transcoded/下以 image hash 命名随照片删除而删除。随时手动删除该目录都是安全的——代价只是重新转换这些视频所需的 CPU。实现细节可查 transcode_cache.pyis_enabled()在无写入位置或TRANSCODE_CACHE_MAX_GB为 0 时关闭缓存L75-L77cached_path()在每次读取时用os.utime刷新 mtime 作为最近提供依据供淘汰排序使用L92-L108。TRANSCODE_CACHE_MAX_CONCURRENT在 production.py 中最小取 1。相关行为还有测试覆盖test_transcode_cache.py 与 test_live_transcode_limits.py。实时转码可能占用多少资源观众等待的那次转码同样有上限而且理由与缓存副本不同。放任自流时 ffmpeg 会用满每个核心、以硬件允许的最快速度转换所以一个人打开一个视频就能饿死 Web UI、正在进行的扫描以及所有其他用户——而播放只需要比实时稍快的输出。多出来的吞吐量毫无意义在快机器上它飞速掠过观众可能根本不会看到的画面。变量.env键默认作用TRANSCODE_LIVE_CPU_FRACTIONtranscodeLiveCpuFraction2将转换限制在机器 1/N 的核心上。2是半数1允许用满全部。无论数值如何至少保留一个核心。该上限是近似而非精确——解码、滤波、编码各自受其约束但读取与 muxing 不受。TRANSCODE_LIVE_READRATEtranscodeLiveReadrate2将转换限制在实时的该倍数即一分钟视频约需半分钟转换。设为0表示以宿主机最快速度转换。TRANSCODE_LIVE_BURST_SECONDStranscodeLiveBurstSeconds30限速生效前以全速转换的视频量让播放仍然瞬间开始、浏览器仍有提前缓冲。与缓存副本不同实时转换不会被 nice有人在看它所以它应优先于后台工作而非向其退让。TRANSCODE_LIVE_READRATE与TRANSCODE_LIVE_BURST_SECONDS需要较新的 ffmpeg——-readrate于 ffmpeg 5.0 引入-readrate_initial_burst于 6.1 引入。CPU 镜像两者都有。GPU 镜像没有它基于 Ubuntu 22.04ffmpeg 为 4.4因此在该镜像上这两个设置无效只有TRANSCODE_LIVE_CPU_FRACTION生效。宿主机自带较旧 ffmpeg 时同理。这无需任何配置——选项只是不被传入核心上限仍然有效。TRANSCODE_LIVE_CPU_FRACTION是除数所以数字越大意味着核心越少4比2更严格。调大它、或调低 readrate会让繁忙的服务器在视频播放期间更响应反向调整则偏向观看者。如果视频在慢机器上卡顿先设TRANSCODE_LIVE_CPU_FRACTION1卡顿意味着转换追不上播放而核心上限才决定它多快——readrate 是它从未触及的天花板。参照尺度一个核心以约 1.5x 实时速度把 1080p 转成 720p两个核心约 2x所以核心少的机器在 1080p 上几乎没有余量更没有留给第二个观看者的余地。对应设置同样定义于 production.py其中TRANSCODE_LIVE_CPU_FRACTION被max(1, ...)保护确保永不小于 1。日志配置backend 把日志文件写入BASE_LOGS指定的目录。ownphotos.log是最先要看的文件也可以从 Admin Area 下载参见 Internal files 与 Library。变量默认作用BASE_LOGS/logs/日志文件写入的目录。启动时若缺失会被创建若创建失败backend 会报错并指明它尝试的路径而不是在无日志的情况下启动。用官方 Compose 时无需设置——容器内路径固定为/logs其后的主机目录跟随.env的data${data}/logs:/logs。只有在官方 Compose 之外运行 backend例如直接在宿主机上时才需要设置。LOG_LEVELINFO写入的最低级别。取值CRITICAL、ERROR、WARNING、INFO、DEBUG之一。无法识别的值回退到INFO并在日志中说明。LOG_TO_CONSOLE1同时把日志发送到容器标准输出docker logs backend或kubectl logs可读取。设为0则只写文件。可接受的 on 值与特性开关一致。LOG_LEVELDEBUG会加入每条照片与每个请求的细节这正是复现 bug 时需要的但大型图库下文件增长很快——事后请改回去。如果日志目录在重启后无法存活请保持LOG_TO_CONSOLE开启。Kubernetes 上/logs通常是emptyDir此时标准输出是唯一能活得比 pod 更久的日志副本。三者都不在官方.env文件中直接传给 backend 容器services: backend: environment: - LOG_LEVELDEBUG:::warningsecret.key存放在BASE_LOGS里、与日志文件为邻。把整个文件夹打包用于 bug 报告等于交出 Django secret key——你实例上的每个 session 与 token 都由它签名。请只单独附加ownphotos.log。删除secret.key也不是解决办法——它会让所有用户登出密码必须全部重置。 :::日志系统的实现集中在 logging_bootstrap.pyresolve_level()对无法识别的级别回退 INFO 并记录延迟警告L71-L90ensure_logs_root()在目录创建失败或不可写时直接抛错并给出修复提示L115-L138文件 handler 使用ConcurrentRotatingFileHandler以应对 gunicorn 与 django-q 多进程并发写日志的轮转问题。日志轮转大小默认 200 MB与保留份数默认 10在 Site Settings 中以 constance 设置管理数据库可用后通过api.util.reconfigure_logging覆盖启动默认值。日志文件名ownphotos.log也在此定义L18。告诉 LibrePhotos 它自己的公网地址FRONTEND_BASE_URL是你的用户实际访问的 URL例如https://photos.example.com不要带结尾斜杠。不设置时LibrePhotos 回退到每个请求看起来来自的 origin。在官方 proxy 后面这个回退是错的proxy 把/api/转发给 backend 时把Host头改写成了backend——这个名字只在 Docker 网络内可解析。这个改写是故意的让 Django 的主机校验在你用任何域名时都能通过但它也意味着 backend 无法自己推断出公网地址。因此在以下场景务必设置它密码重置邮件——邮件里的链接。不设置时回退可能生成指向http://backend/...的链接任何收件人都打不开。单点登录——发给身份提供方的 OAuthredirect_uri浏览器要跟随它、提供方要识别它。SSO 拒绝在无法生成有效地址时启动因此该变量对 OIDC 实际上是必需的。托管在子路径子目录下默认情况下 LibrePhotos 从域名根路径提供如https://photos.example.com/。若需要托管在子路径下如https://example.com/photos/前端必须知道这个 base path。前端从构建期变量VITE_PUBLIC_URL读取它。它设置应用静态资源加载的 base path以及 API 调用所带的前缀默认/。PUBLIC_URL也会被 vite.config.ts 读取但只用于资源路径——单独设置它会导致应用的 API 调用仍指向域名根而失败所以请用VITE_PUBLIC_URL。因为它在前端构建时生效预构建镜像从/提供服务。要使用子路径必须自己用该变量构建前端例如# 直接构建前端 VITE_PUBLIC_URL/photos yarn build:::note 不要加结尾斜杠用/photos而不是/photos/。应用会直接把这个值与/api、/login拼接结尾斜杠会产生/photos//api这样的双斜杠路径。Vite 会自行补上资源 URL 需要的斜杠。 :::发布到宿主机的不是前端容器而是官方 proxy 容器端口httpPort默认3000其 nginx 配置把/api、/media等后端路径锚定在根路径。所以外层反向代理必须在转发前剥掉子路径例如location /photos/ { # proxy_pass 末尾的斜杠会剥掉 /photos/ 前缀 proxy_pass http://127.0.0.1:3000/; }或者自行把 deploy/docker/proxy/nginx.conf 中的 location 块重新锚定到子路径下。:::warning 子路径托管目前并未完全可用。VITE_PUBLIC_URL会重新定位资源与 API 调用但客户端路由apps/frontend/src/App.tsx创建时没有配套的 base path因此应用加载后浏览器内路由仍按根相对路径匹配导航会失效。修复需要改动前端而不只是构建变量。 :::修改容器名称容器名通过docker-compose.yml中的container_name键直接设置proxy、db、frontend、backend。想在同一个宿主机上跑多套 LibrePhotos 栈时改这些值即可services: backend: container_name: myphotos-backend这些名称就是你传给docker exec、docker logs等命令的名字。它们与服务名是两回事——proxy:、db:、frontend:、backend:这些键本身容器之间通过它们在 Compose 网络上互相访问。官方 proxy 按服务名解析frontend和backend所以请保持这些键不变。:::note 老教程提到的make rename辅助命令已随deploy/Makefile一起移除请直接编辑docker-compose.yml。 :::旧环境变量以下变量现在已是站点设置在此设置它们只提供首次初始化时的默认值之后值存于站点设置中你在那里的任何修改优先。# 要忽略的逗号分隔模式列表例如 Synology 设备的 eaDir,#recycle skipPatterns # 允许上传文件 allowUploadtrue # 地理编码地图服务商的 API key。只有切换到 Mapbox、MapTiler、TomTom # 或 OpenCage 时才需要默认的 NominatimOpenStreetMap无需 key。 mapApiKey这三点在 production.py 的CONSTANCE_CONFIG中均有对应SKIP_PATTERNS、ALLOW_UPLOAD、MAP_API_KEY读取环境变量MAPBOX_API_KEY。MAP_API_PROVIDER的合法选择mapbox/maptiler/nominatim/opencage/tomtom也在 production.py 中定义。管理员账户变量userName、userPass和adminEmail不是站点设置。它们仍然是活跃的环境变量在 docker-compose.yml 中映射到ADMIN_USERNAME、ADMIN_PASSWORD和ADMIN_EMAIL# 管理员登录的用户名。 userNameadmin # 上面设置的账户的密码。 userPassadmin # 管理员的邮箱。 adminEmailadminexample.com只要设置了userNamebackend entrypoint 就会在每次容器启动时运行createadmin见 entrypoint.sh首次启动时创建超级用户。用户名转小写adminEmail用于该账户。之后每次启动现有用户的密码都会被userPass覆盖。因此保持userPass设置会在每次重启时重置管理员密码——你在 UI 中改过的密码不会挺过docker compose restart。这与 How to change the admin password, when you cant log in 描述的行为相同。adminEmail只在账户首次创建时生效后续启动createadmin会忽略它并记录一条警告。密码只从ADMIN_PASSWORD环境变量读取createadmin.py不经过命令行参数——因为 Linux 下命令行对全系统可见密码出现在参数里会泄露。未设置ADMIN_PASSWORD时自动生成 32 位随机密码设置为空则报错 Admin password cannot be empty。要阻止密码被重置清空userName——这会完全跳过createadmin。只清空userPass而保留userName则每次启动命令都会以 Admin password cannot be empty 中止。小结一份可落地的调优清单结合以上全部变量在一台资源有限的机器上一份典型的瘦身.env追加配置如下# 后台 worker1 个而不是每核 1 个 workerConcurrency1 # 若同时加 CPU 上限提高 gunicorn 超时默认 30 gunicornTimeout120 # 关闭机器学习特性可随时用 true 重新打开 featureFaceDetectionfalse featureFaceClusterfalse featureImageCaptioningfalse featureSceneClassificationfalse # 转码缓存与实时转码保持默认即可按需调整 transcodeCacheMaxGb10 transcodeCacheMinFreeGb2 transcodeCacheMaxConcurrent1 transcodeCacheNice10 transcodeLiveCpuFraction2 transcodeLiveReadrate2 transcodeLiveBurstSeconds30 # 日志 # logLevelINFO # baseLogs/logs # 公网地址有密码重置邮件或 SSO 时必设 frontendBaseUrlhttps://photos.example.com # 管理员仅在首次启动时需要日常建议清空 userName userNameadmin userPassadmin adminEmailadminexample.com部署后通过docker compose config检查渲染结果用docker logs backend确认服务与日志状态。完整的官方.env模板见 deploy/compose/librephotos.env基础安装流程见 standard-install.md 与 unified-deployment.md。【免费下载链接】librephotosA self-hosted open source photo management service.项目地址: https://gitcode.com/GitHub_Trending/li/librephotos创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表