部署配方 · 文件与存储
Pi 4 上的 Nextcloud:真正麻烦的是数据库
Nextcloud 是这台机架上最重的服务,也是失败方式最多、最安静的一个。它依然值得跑,因为属于自己的文件同步,胜过按月租来的文件同步。下面是让它活得下来的拆分方式:数据放 NAS、数据库用 MariaDB 而不是 SQLite、PHP 调到上传真的能完成,以及四个让反向代理不再搞坏所有生成链接的 config.php 值。
| 容器镜像 | nextcloud:30.0.5-apache |
|---|---|
| 宿主端口 | :8082/tcp, :8443/tcp |
| 数据目录 | /srv/homelab/nextcloud/html |
| 内存预留 | 1100 MB |
| CPU 上限 | 1.0 vCPU (cpus: "1.0") |
| 更新节奏 | monthly within a major, and read the upgrade notes before every major — Nextcloud skips no majors, 29 to 31 is not a supported jump |
| ARM 兼容 | arm64 native since 25; the apache tag is the one to use because the fpm tag has no web server inside it |
| 宿主 | 容器 | proto | 暴露 | 用途 |
|---|---|---|---|---|
| :8082 | 80 | tcp | 仅局域网 | web UI, WebDAV and desktop client sync |
| :8443 | 443 | tcp | 仅局域网 | direct TLS for clients that refuse plain http on the LAN |
部署步骤
用正确的属主建两个卷
apache 镜像以 uid 33(www-data)跑 PHP。网站根目录和数据库目录都必须对该 uid 可写,NAS 导出也要用匹配的属主。
run sudo mkdir -p /srv/homelab/nextcloud/{html,db} sudo chown -R 33:33 /srv/homelab/nextcloud/html sudo chown -R 999:999 /srv/homelab/nextcloud/db sudo mkdir -p /mnt/nas/nextcloud-data && sudo chown -R 33:33 /mnt/nas/nextcloud-data在宿主机上用稳定的导出方式挂载 NAS
这里选 NFS 是对的:它保留 uid 33,也没有 CIFS 在 Nextcloud 频繁小 stat 调用上的逐请求开销。
run # /etc/fstab 192.168.10.5:/export/nextcloud /mnt/nas/nextcloud-data nfs4 rw,hard,timeo=100,retrans=3,nofail,x-systemd.automount 0 0 sudo mount -a && findmnt /mnt/nas/nextcloud-data先起数据库,再起应用
应用容器首次启动会跑安装程序,数据库还没接受连接时它会失败。光靠 depends_on 不会等就绪——真正等的是 healthcheck。
run cd /srv/homelab/nextcloud && docker compose up -d nextcloud-db docker exec nextcloud-db mariadb -unextcloud -p"$NC_DB_PASSWORD" -e 'select 1' docker compose up -d nextcloud登录之前先设好可信域名
在一个不在 trusted_domains 里的地址上登录会得到 400,看起来像安装坏了。先把值设好,再打开界面。
run docker exec -u www-data nextcloud php occ config:system:set trusted_domains 0 --value="cloud.lan" docker exec -u www-data nextcloud php occ config:system:set trusted_domains 1 --value="192.168.10.20"修好维护定时任务,跑第一次全量扫描
第一次 files:scan 很慢,应该从命令行跑,而不是靠打开网页触发。NAS 上 200GB 的数据目录,Pi 4 大约要四十分钟。
run docker exec -u www-data nextcloud php occ background:cron docker exec -u www-data nextcloud php occ files:scan --all docker exec -u www-data nextcloud php occ status
为什么用 MariaDB 而不是默认的 SQLite
这个镜像开箱就用 SQLite,单用户、小文件量时它确实够用。但只要桌面客户端开始同步一棵目录树、同时手机在上传照片,它就不够用了:SQLite 的写操作是串行的,文件锁争用表现出来的却是同步报错,而那些报错里一个字都不提数据库。
MariaDB 大约多花 450MB 和一个容器。缓冲池设成 512MB,这是树莓派上性价比最高的一个调优项:默认的 128MB 之下,每一次没命中缓存的元数据查询都要落盘,而落的是单板计算机那点可怜的存储。
command: --transaction-isolation=READ-COMMITTED --innodb-buffer-pool-size=512M
# READ-COMMITTED 是 Nextcloud 文档要求的隔离级别,缓冲池大小
# 决定了文件列表是秒开还是要等五秒数据在 NAS,代码在固态盘
数据目录通过 NFS 而不是 SMB 从 NAS 挂载:文件属主能干净地映射到 uid 33,而且 Nextcloud 大量的小 stat 调用在 NFS 上明显快于 CIFS。
网站根目录留在本地固态盘上,因为 PHP 的 opcode 缓存要的是低延迟,而这份代码本身很小。这个拆分还让备份更省事:NAS 上的数据本来就由 NAS 自己的备份任务覆盖。
| 内容 | 位置 | 理由 |
|---|---|---|
| /var/www/html | 本地固态盘 /srv/homelab/nextcloud/html | PHP 要低延迟,且体积小 |
| /var/www/html/data | NAS,走 NFS | 已由 NAS 备份覆盖 |
| /var/lib/mysql | 本地固态盘 | 数据库 I/O 不该跨网络 |
| 备份 | NAS,再异地一份 | 3-2-1 原则,第二份不在派上 |
代理后面真正要紧的四个 config.php 值
Nextcloud 根据它收到的请求生成绝对 URL。躲在反向代理后面时,它看到的是 http://nextcloud:80,然后忠实地把这个地址交给你浏览器——curl 看着一切正常,页面样式却加载不出来。
四个值能解决全部问题:trusted_domains、overwriteprotocol、overwrite.cli.url 和 overwritehost。最后一个最常被漏掉,而它漏掉之后,手机 App 分享出去的链接不只是不安全,而是根本是错的。
docker exec -u www-data nextcloud php occ config:system:set trusted_domains 0 --value="cloud.lan"
docker exec -u www-data nextcloud php occ config:system:set overwriteprotocol --value="https"
docker exec -u www-data nextcloud php occ config:system:set overwrite.cli.url --value="https://cloud.lan"
docker exec -u www-data nextcloud php occ config:system:set overwritehost --value="cloud.lan"让上传真的能传完
镜像默认的 PHP 限制是 512MB,比手机视频小,更比相机的 raw 小得多。PHP_UPLOAD_LIMIT 和 PHP_MEMORY_LIMIT 这两个环境变量是官方支持的做法;手工改 php.ini 能用,直到下一次拉取镜像把文件换掉。
另一半是分块上传,桌面客户端在超过 10MB 时会自动使用。如果上传总是在某个固定大小失败,那你撞到的是代理的限制,不是 PHP 的。
# 确认 PHP 实际生效的值,而不是你以为设过的值
docker exec nextcloud php -i | grep -E 'upload_max_filesize|post_max_size|memory_limit'没人配置的后台任务
Nextcloud 默认用 AJAX 跑维护任务,也就是说只有有人打开页面时才会触发。管理概览会在一个橙色框里抱怨这件事,而人们学会了无视那个框。
把模式改成 cron,再在宿主机上每五分钟调一次 cron.php。两行配置,换来的是文件扫描跟得上,而不是每次你想看它就触发一次全量重扫。
docker exec -u www-data nextcloud php occ background:cron
# 然后在宿主机上:
# */5 * * * * docker exec -u www-data nextcloud php -f /var/www/html/cron.phpcompose 文件
整份文件放在 /srv/homelab/nextcloud/compose.yaml。标签固定版本号,不用 latest——树莓派上回滚比升级麻烦得多。
services:
nextcloud:
image: nextcloud:30.0.5-apache
container_name: nextcloud
restart: unless-stopped
ports:
- "8082:80/tcp"
- "8443:443/tcp"
environment:
TZ: Asia/Shanghai
MYSQL_HOST: nextcloud-db
MYSQL_DATABASE: nextcloud
MYSQL_USER: nextcloud
MYSQL_PASSWORD: ${NC_DB_PASSWORD}
NEXTCLOUD_ADMIN_USER: ${NC_ADMIN_USER}
NEXTCLOUD_ADMIN_PASSWORD: ${NC_ADMIN_PASSWORD}
NEXTCLOUD_TRUSTED_DOMAINS: "cloud.lan rack.twinkling.top"
OVERWRITEPROTOCOL: https
PHP_MEMORY_LIMIT: 512M
PHP_UPLOAD_LIMIT: 16G
volumes:
- /srv/homelab/nextcloud/html:/var/www/html
- /mnt/nas/nextcloud-data:/var/www/html/data
depends_on: [nextcloud-db]
networks: [rack]
nextcloud-db:
image: mariadb:11.4
container_name: nextcloud-db
restart: unless-stopped
command: --transaction-isolation=READ-COMMITTED --innodb-buffer-pool-size=512M
environment:
MARIADB_ROOT_PASSWORD: ${NC_ROOT_PASSWORD}
MARIADB_DATABASE: nextcloud
MARIADB_USER: nextcloud
MARIADB_PASSWORD: ${NC_DB_PASSWORD}
volumes:
- /srv/homelab/nextcloud/db:/var/lib/mysql
networks: [rack]
networks:
rack:
external: true加固清单
- trusted_domains 只列代理域名和局域网地址,其它一律以 400 拒绝
- 设置了 overwrite.cli.url 与 overwriteprotocol,生成的链接不会泄露容器主机名
- 管理员账号不用于日常同步;每个人有自己的账号,管理员账号里不存数据
- 管理员组内的所有账号强制开启双因素
- 数据目录放在网站根目录之外,Web 服务器配错也不会把原始文件直接吐出来
- 只从内置应用商店装应用;每次升级后检查被因签名问题禁用的应用
备份方案
三部分,缺一不可:每晚用 mysqldump 导出数据库、备份含 config.php 与 apps 的配置目录、以及数据目录。恢复时必须把匹配的数据库和匹配的配置一起放回去,因为实例 id 和密钥都在 config.php 里,配错的一对会让所有客户端掉线并报服务端错误。
上线后验证
- 管理概览里的警告只剩你明确接受的那几条
- 桌面客户端上传一个 4GB 文件能完成,说明 PHP 限制、分块和代理体积上限三者一致
- 分享链接在局域网外用 WireGuard 能在手机上打开,且 URL 里的主机名正确
- 后台任务模式显示为 cron,任务列表里最后一次执行在五分钟以内
踩过的坑
- 把恢复出来的数据库和另一次安装的 config.php 混用,会让所有客户端掉线并报服务端错误,实例 id 也是对不上的。这两者必须一起备份、一起恢复。
- apache 镜像把日志写在容器里。没有 logrotate,也没给 json-file 驱动设 max-size,一个同步死循环就能把容器的可写层写满,站点随即因为磁盘满而挂掉,而那个报错指向的地方毫无线索。
- 在 NAS 支撑的数据目录上、用默认 PHP 内存上限跑全量 files:scan,大约跑到一半就会挂。请在第一次扫描之前就把 PHP_MEMORY_LIMIT 设成 512M,而不是之后。
- Nextcloud 不允许跨大版本升级。29 直接到 31 不是一条升级路径;你得 29、30、31 跑三次升级程序。
硬件与选型问答
- Pi 4 跑 Nextcloud 够用吗,还是应该放到 NAS 上?
- 一到两个用户做文件同步,4GB 的 4B 够用。一旦加上 Talk、Collabora,或者超过四个账号,NAS 是更合适的宿主——Nextcloud 的瓶颈是 PHP 工作进程并发,而树莓派只有四个核。本机架把 Nextcloud 留在派上,是因为 NAS 的角色是备份目标而不是计算节点,把负载挪上去等于让存放备份的机器也需要被备份。
- MariaDB、PostgreSQL 还是 SQLite?
- 用 MariaDB,并且按 Nextcloud 文档要求设成 READ-COMMITTED 隔离级别。PostgreSQL 同样好,某些查询还略快;SQLite 只在两个客户端不同时同步的前提下才够用,而它失败时给出的同步错误里根本不会提到数据库。
- 整套要预留多少内存?
- 两个同步客户端下应用容器 1.1GB,MariaDB 缓冲池 512MB 加上开销,再留 300MB 余量。总共是一块 8GB 板子上的 2GB,还剩得下 Jellyfin 的位置。