Brave
@47c132035ed7a1105a867946b99d67af 网络安全与大数据研究小组 公开
讨论 如何用 Ubuntu 服务器和 Docker Compose 部署家用 Nextcloud

如何用 Ubuntu 服务器和 Docker Compose 部署家用 Nextcloud

作为一名喜欢折腾的技术爱好者,我一直想在家里搭一套自己的私有云,既能替代各种商业网盘做文件同步,又能顺便练练手。这次我选择了 Nextcloud,部署方式是 Docker Compose,运行环境是 Ubuntu + Docker,通过 mDNS 在局域网内用 *.local 域名访问。

本文记录整个部署过程,以及中途遇到的几个典型问题和解决思路,希望能给有类似需求的朋友一些参考。


一、环境与最终配置

运行环境

  • OS:Ubuntu(家庭服务器)
  • 容器运行时:Docker + Docker Compose v2
  • 局域网访问方式:mDNS(Avahi)

最终的 docker-compose.yml

services:
  db:
    image: mariadb:10.11
    container_name: nextcloud-db
    restart: unless-stopped
    command: --transaction-isolation=READ-COMMITTED --binlog-format=ROW
    volumes:
      - db_data:/var/lib/mysql
    environment:
      MYSQL_ROOT_PASSWORD: RootPass_ChangeMe_123
      MYSQL_DATABASE: nextcloud
      MYSQL_USER: nextcloud
      MYSQL_PASSWORD: NcPass_ChangeMe_456
      MARIADB_AUTO_UPGRADE: "1"

  app:
    image: nextcloud:latest
    container_name: nextcloud
    restart: unless-stopped
    ports:
      - "5050:80"
    depends_on:
      - db
    volumes:
      - nextcloud_data:/var/www/html
    environment:
      MYSQL_HOST: db
      MYSQL_DATABASE: nextcloud
      MYSQL_USER: nextcloud
      MYSQL_PASSWORD: NcPass_ChangeMe_456
      NEXTCLOUD_TRUSTED_DOMAINS: myhost.local localhost 192.168.1.* 192.168.0.* 10.0.0.* *.local
      # 首次安装向导走完后,取消下面两行注释再 docker compose up -d
      # OVERWRITEPROTOCOL: http
      # OVERWRITEHOST: "myhost.local:5050"

volumes:
  db_data:
  nextcloud_data:

说明:配置中的密码、主机名均为示例,请按自己实际情况修改。本文后续统一用 myhost.local 代表家庭服务器的主机名。


二、配置设计思路

1. 数据库用 MariaDB 10.11

Nextcloud 官方文档推荐 MariaDB / MySQL。这里选 10.11(LTS)而不是 latest,主要是为了避免未来某天 docker compose pull 把大版本顶上去导致崩溃。

两个关键启动参数:

  • --transaction-isolation=READ-COMMITTED:Nextcloud 官方推荐的隔离级别,能减少死锁
  • --binlog-format=ROW:配合上面的隔离级别,保证事务一致性

MARIADB_AUTO_UPGRADE=1 让容器启动时自动跑 mysql_upgrade,跨版本升级时省心。

2. 应用容器只暴露 5050 端口

ports:
  - "5050:80"

Ubuntu 上 80 端口经常被其他服务占用(例如以后可能会装的 Nginx),这里映射成 5050 干净利落。访问地址就是 http://myhost.local:5050

3. 数据全部走命名卷

db_datanextcloud_data 都用 Docker 命名卷,而不是绑定宿主机目录。好处是不用操心宿主机权限,容器内的 www-data 用户能正常读写。缺点是备份时要用 docker run --rm -v ... tar 的方式导出,绑定目录则可以直接 rsync,各有取舍。

4. NEXTCLOUD_TRUSTED_DOMAINS 用通配符

myhost.local localhost 192.168.1.* 192.168.0.* 10.0.0.* *.local

Nextcloud 的 trusted_domains 支持 * 通配符(一个 * 只能匹配一段),多个值用空格分隔。这样局域网里任何设备用 IP 或 .local 名字访问都能放行。

⚠️ 安全提示:通配符仅适用于纯内网部署。一旦暴露到公网,应当写死具体域名。


三、一个关键决定:局域网里用什么名字访问?

家里的设备(手机、Mac、iPad)都在同一个 Wi-Fi 下。我不想每次都记 IP,于是打算用 mDNS(也叫 Bonjour / Avahi),通过 xxx.local 的方式访问。

最初的想法:让容器自己广播 mDNS

第一版方案里我加了一个 mdns 容器,专门跑 avahi-daemon,用 network_mode: host 去广播一个自定义名字。结果容器日志里全是:

Detected another IPv4 mDNS stack running on this host, effectively disabling Avahi's mDNS stack.
Host name conflict, retrying with next-2.local
Host name conflict, retrying with next-3.local
...

排查端口占用:

sudo ss -ulpn | grep 5353

发现宿主机(Ubuntu)本身就跑着一个 avahi-daemon,正在广播 myhost.local。容器里那个 avahi 被挤得节节败退——两者都在争 UDP 5353。

换思路:直接用宿主机已有的 mDNS 名

既然宿主机已经在广播 myhost.local,为什么还要自己再造一个?直接用现成的就好:

NEXTCLOUD_TRUSTED_DOMAINS: myhost.local ...

浏览器访问 http://myhost.local:5050 完事,compose 里那个多余的 mdns 服务直接删掉。最好的方案往往是不做

如果你的 Ubuntu 还没装 avahi,启用很简单:

sudo apt install -y avahi-daemon avahi-utils libnss-mdns
sudo systemctl enable --now avahi-daemon
  • avahi-daemon:mDNS 守护进程
  • avahi-utils:提供 avahi-resolveavahi-browse 等调试工具
  • libnss-mdns:让 Ubuntu 自己也能解析 *.local(Mac 自带 Bonjour,不需要)

验证:

avahi-resolve -n myhost.local        # 应返回宿主机 IP
avahi-browse -a -t                   # 列出局域网所有 mDNS 服务

四、部署过程中踩到的几个坑

坑 1:NEXTCLOUD_TRUSTED_DOMAINS 改了不生效

现象:改了 compose 里的环境变量,docker compose up -d 重启后,浏览器依然报 Access through untrusted domain

原因NEXTCLOUD_TRUSTED_DOMAINS 这类环境变量只在数据卷为空(首次初始化)时写入 config/config.php。如果之前装过,config 已经存在,后续修改环境变量完全不会被读入。

解决:进容器用 occ 命令改:

docker compose exec --user www-data app \
  php occ config:system:set trusted_domains 0 --value="myhost.local"

docker compose exec --user www-data app \
  php occ config:system:set trusted_domains 1 --value="192.168.1.*"

docker compose exec --user www-data app \
  php occ config:system:set trusted_domains 2 --value="*.local"

查看当前值:

docker compose exec --user www-data app php occ config:system:get trusted_domains

经验:所有 NEXTCLOUD_* 开头的"首次初始化"环境变量都有这个特性,包括 NEXTCLOUD_ADMIN_USERNEXTCLOUD_ADMIN_PASSWORD 等。第一次启动之后,改配置一律走 occ 或者直接编辑 config.php

坑 2:升级提示 "Update needed"

现象

Update needed
Please use the command line updater because updating via browser is disabled in your config.php.

原因:镜像用了 nextcloud:latestdocker compose pull 可能拉到更新的大版本,而数据卷里的 Nextcloud 实例版本比较老。

解决:命令行升级

docker compose exec --user www-data app php occ upgrade
docker compose exec --user www-data app php occ maintenance:mode --off

⚠️ Nextcloud 不允许跨大版本升级(比如 27 → 29 会拒绝)。如果版本差距大,要先把镜像暂时改成中间版本(如 nextcloud:28)过一次 occ upgrade,再切到目标版本。

教训:生产环境应该把镜像版本锁死,而不是用 latest

image: nextcloud:30-apache

这样 pull 只会在 30 的小版本内更新,大版本升级由自己掌控节奏。

坑 3:分享链接生成的 URL 不带端口

Nextcloud 生成的分享链接和客户端回调地址,默认根据请求头推断协议和域名。家里用 http://myhost.local:5050 访问没问题,但分享链接可能生成成 http://myhost.local/s/xxx(丢了 5050),点开打不开。

解决:在 config.php 里固定外部访问的协议和主机:

docker compose exec --user www-data app \
  php occ config:system:set overwriteprotocol --value="http"

docker compose exec --user www-data app \
  php occ config:system:set overwritehost --value="myhost.local:5050"

或者在初次安装完成后,编辑 compose 把注释去掉再 up -d(仅对重新初始化的实例生效):

OVERWRITEPROTOCOL: http
OVERWRITEHOST: "myhost.local:5050"

五、完整部署步骤汇总(Ubuntu)

# 0. 如果还没装 Docker
sudo apt update
sudo apt install -y docker.io docker-compose-v2
sudo usermod -aG docker $USER
# 重新登录一次让 docker 组生效

# 1. 准备目录
mkdir -p ~/nextcloud && cd ~/nextcloud
# 把前面的 compose 文件保存为 docker-compose.yml,记得改密码

# 2. 启动
docker compose up -d

# 3. 查看日志,确认没有错误
docker compose logs -f app

# 4. 浏览器打开 http://myhost.local:5050,走首次安装向导
#    设置管理员账号密码即可

# 5. 安装完成后,固定外部访问 URL
docker compose exec --user www-data app \
  php occ config:system:set overwriteprotocol --value="http"
docker compose exec --user www-data app \
  php occ config:system:set overwritehost --value="myhost.local:5050"

# 6. Mac 端清下 mDNS 缓存(Linux / Windows 通常不需要)
sudo dscacheutil -flushcache
sudo killall -HUP mDNSResponder

六、几点实用经验总结

  1. 尽量用宿主机已有的基础设施。mDNS、反向代理这些系统级服务,没必要每个项目都在容器里再跑一份,否则就是在和宿主机抢端口。
  2. latest 是工具,不是信仰。一旦要长期运行,把镜像 tag 锁到大版本号,免得某天凌晨自动更新后服务起不来。
  3. "首次初始化"环境变量要当心。Nextcloud、Postgres、MariaDB 这类官方镜像里的 *_PASSWORD*_DATABASE 等环境变量,几乎都是"仅在空卷时生效",之后改都得进容器手动操作。
  4. 部署前先通读日志。很多问题(mDNS 冲突、trusted_domains 报错、升级提示)其实日志里写得明明白白。docker compose logs -f 应该是部署时另开一个窗口常驻的命令。
  5. trusted_domains 支持通配符,但每个 * 只能匹配一段。想覆盖两个八位组,要写两条:192.168.0.*192.168.1.*,而不是 192.168.*.*

结语

一套可用的私有云就这么搭起来了。下一步打算给它加上自动备份脚本(occ maintenance:mode --on → 打包数据卷 → 关掉维护模式),以及定期做大版本升级演练。如果你也在用 Ubuntu + Docker 折腾家庭服务器,希望这篇记录能帮你少走几步弯路。

Have fun self-hosting ☁️