Decentralization? We're still early!

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

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

    發布人 Brave 2026-04-24 04:35

    作为一名喜欢折腾的技术爱好者,我一直想在家里搭一套自己的私有云,既能替代各种商业网盘做文件同步,又能顺便练练手。这次我选择了 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 ☁️

    Brave 回复 5 days, 22 hours ago 1 成員 · 0 回复
  • 0 回复

歡迎留言回复交流。

Log in to reply.

讨论開始
00 回复 2018 年 6 月
現在