如何用 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_data 和 nextcloud_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.* *.localNextcloud 的 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-daemonavahi-daemon:mDNS 守护进程avahi-utils:提供avahi-resolve、avahi-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_USER、NEXTCLOUD_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:latest,docker 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六、几点实用经验总结
- 尽量用宿主机已有的基础设施。mDNS、反向代理这些系统级服务,没必要每个项目都在容器里再跑一份,否则就是在和宿主机抢端口。
latest是工具,不是信仰。一旦要长期运行,把镜像 tag 锁到大版本号,免得某天凌晨自动更新后服务起不来。- "首次初始化"环境变量要当心。Nextcloud、Postgres、MariaDB 这类官方镜像里的
*_PASSWORD、*_DATABASE等环境变量,几乎都是"仅在空卷时生效",之后改都得进容器手动操作。 - 部署前先通读日志。很多问题(mDNS 冲突、trusted_domains 报错、升级提示)其实日志里写得明明白白。
docker compose logs -f应该是部署时另开一个窗口常驻的命令。 trusted_domains支持通配符,但每个*只能匹配一段。想覆盖两个八位组,要写两条:192.168.0.*和192.168.1.*,而不是192.168.*.*。
结语
一套可用的私有云就这么搭起来了。下一步打算给它加上自动备份脚本(occ maintenance:mode --on → 打包数据卷 → 关掉维护模式),以及定期做大版本升级演练。如果你也在用 Ubuntu + Docker 折腾家庭服务器,希望这篇记录能帮你少走几步弯路。
Have fun self-hosting ☁️