Skip to content

Nextcloud + Memories 完整部署(FPM + go-vod)

在 Synology NAS(Intel J4125)上部署 Nextcloud + Memories 相片管理,使用官方 FPM 映像 + 自訂 Dockerfile(加入 ffmpeg)+ 獨立 Nginx + go-vod VA-API 硬體加速影片轉碼 + Recognize AI 標籤。

概述

與 LSIO 版(NAS 自建 Nextcloud (LinuxServer.io))相比,本方案基於 Nextcloud 官方 FPM 映像,架構更複雜但功能更完整:

功能LSIO 版本方案(FPM + Memories)
Nextcloud 基本功能
相片時間軸
影片 HLS 轉碼(go-vod)✅ VA-API 硬體加速
AI 人臉/物件辨識(Recognize)✅ WASM 模式
地圖 + 地點名稱(Geocoding)
架構複雜度低(1 主容器)高(5 容器)

Stack 包含 5 個服務:

  1. app(Nextcloud FPM + ffmpeg + supervisord)— 主應用 + 背景任務 + External Storage 掃描
  2. web(Nginx)— HTTP 前端,靜態資源快取 + FastCGI 轉發
  3. db(MariaDB 11)— 資料庫
  4. redis(Redis 8-alpine)— File locking + APCu 分散式快取
  5. go-vod(radialapps/go-vod)— 影片轉碼(VA-API 硬體加速)

核心內容

自訂 Nextcloud FPM 映像設計

官方 nextcloud:fpm 映像不含 ffmpeg(Memories 縮圖生成需要)和 supervisord(多程序管理)。需要自訂 Dockerfile 補裝。使用 NCHC 台灣鏡像加速 apt 安裝(debian-security repo 保留官方 CDN,僅替換主 repo)。

supervisord 管理三個程序

  • php-fpm:主 PHP 處理程序
  • /cron.sh:官方內建腳本,busybox crond 每 5 分鐘執行 Nextcloud 背景任務(cron.php
  • scan-cron:第二個獨立 busybox crond 實例,使用 /var/spool/cron/scan-crontabs/ 作為目錄,與 /cron.sh 完全隔離

兩個 crond 使用不同 crontab 目錄完全隔離,互不干擾。idle 時整個 supervisord 開銷約 20-30MB RAM,CPU ≈ 0%。

go-vod 外部轉碼器架構

go-vod 是 Memories 的外部影片轉碼服務。Memories 將轉碼請求傳給 go-vod(go-vod:47788),go-vod 用 VA-API 將影片轉碼為 HLS 串流後回傳。

關鍵設定:

  • NEXTCLOUD_HOST=http://web:80:go-vod 需從 Nextcloud 下載 binary 和媒體,用 Docker 內部網路(不繞 Cloudflare),HTTP 不需 TLS
  • trusted_domains 必須包含 web:go-vod 的 HTTP Host header 是 web,Nextcloud 拒絕非信任 host,導致 go-vod 持續報 Failed to fetch ... Retrying(首次部署在 Nextcloud + Memories 安裝完成前,此 log 為正常行為)

J4125 VA-API 硬體加速

Intel Celeron J4125(Gemini Lake Refresh,UHD 600)支援 H.264 和 HEVC 8-bit 的 VA-API 編解碼,啟用後轉碼效能提升 4-5 倍。radialapps/go-vod 基於 jellyfin/jellyfin 映像,已內建 Intel media driver(iHD)。若遇到 bug,設 LIBVA_DRIVER_NAME=i965 切換到舊版 driver。

確認 /dev/dri/renderD128 存在:

bash
ls -la /dev/dri/
# 若不存在,嘗試:sudo modprobe i915

Nginx 配置重點

本方案的 nginx.conf 基於 Nextcloud 官方範例,關鍵特點:

  • fastcgi_request_buffering off:串流轉發,不暫存到 NAS 磁碟,減少 I/O,支援大檔上傳(⚠ DAVx⁵ 等 HTTP Transfer-Encoding: chunked 的客戶端需改為 on,否則產生 0-byte 檔案)
  • PHP location 在靜態資源之前:避免 .php 路徑進入靜態資源的 try_files 造成 rewrite loop
  • immutable 快取:帶 ?v=xxx 的靜態資源標記 immutable,提升重複訪問效能
  • 敏感路徑封鎖/config/lib/data 等路徑回傳 404,防止原始碼外洩

Recognize AI 在 J4125 的特殊考量

J4125 無 AVX 指令集,Recognize 自動切換至 WASM 模式tensorflow.purejs = true)。管理面板的「Tensorflow WASM mode」等項目可能持續顯示 Checking,這是已知 UI bug(#1123),不影響實際功能。

Recognize 背景分類屬於 TIME_INSENSITIVE 任務,只在維護時段(maintenance_window_start = UTC 19)執行,每次 cron 觸發只處理一小批。首次部署建議手動跑完整分類(背景 nohup),後續增量由 cron 自動處理,recognize:classify 支援斷點續跑。

--unscanned vs 全量掃描策略

本方案採混合掃描策略(與 LSIO 版純全量掃描不同):

模式頻率能偵測
--unscanned(增量)每 15 分鐘新上傳檔案(filecache 標記未完成的條目)
不帶 flag(全量)每日 UTC 19:00所有變更,包含外部刪除、修改

增量掃描後自動執行 memories:index,確保 Memories 時間軸即時更新。

關鍵要點

  • trusted_domains 必須包含 web(讓 go-vod 的 HTTP Host header 通過)
  • docker compose exec --user www-data 是所有 occ 指令的正確方式(官方映像不同於 LSIO)
  • Bind mount 目錄(data/config/custom_apps)必須在首次 up 前手動建立並 chown -R 33:33
  • tmpfs /tmp:exec 是必要的(Memories exiftool binary 在 /tmp 執行)
  • Recognize 首次分類需手動完整執行,後續 cron 自動增量
  • go-vod 連 Nextcloud 用 http://web:80(內網),不繞 Cloudflare

實際應用

此部署適用於:

  • 完整 Google Photos 替代方案(時間軸、地圖、AI 標籤、人臉辨識)
  • Intel NAS(J4125 或類似),利用 VA-API 硬體加速影片轉碼
  • 有多個 NAS 共享資料夾(多 volume)需要透過 Nextcloud 管理和 AI 索引

部署設定參考

以下為實際部署時使用的完整設定,供日後查詢與複製使用。

環境參數

項目
NAS 硬體Synology NAS(Intel Celeron J4125,UHD 600)
Nextcloud 版本32(官方 FPM 映像)
外部 port8880:80(Nginx HTTP)
Cloudflare TunnelHTTP,連入宿主機 IP:8880
External Storage 掃描增量每 15 分鐘 + 全量每日 UTC 19:00

docker-compose.yaml(完整)

yaml
services:
  # ============================================================
  # MariaDB
  # ============================================================
  db:
    image: mariadb:11
    restart: unless-stopped
    command: >
      --transaction-isolation=READ-COMMITTED
      --log-bin-trust-function-creators=1
      --innodb-buffer-pool-size=512M
      --character-set-server=utf8mb4
      --collation-server=utf8mb4_unicode_ci
    volumes:
      - /volume1/docker/nextcloud/mariadb/mysql:/var/lib/mysql
    environment:
      - MYSQL_ROOT_PASSWORD=<YOUR_DB_ROOT_PASSWORD>
      - MYSQL_DATABASE=nextcloud
      - MYSQL_USER=nextcloud
      - MYSQL_PASSWORD=<YOUR_DB_PASSWORD>
      - TZ=Asia/Taipei

  # ============================================================
  # Redis(無密碼)
  # ============================================================
  redis:
    image: redis:8-alpine
    restart: unless-stopped
    command: redis-server
    volumes:
      - /volume1/docker/nextcloud/redis/data:/data
    environment:
      - ALLOW_EMPTY_PASSWORD=yes

  # ============================================================
  # Nextcloud (FPM) — 自訂 image 含 ffmpeg + supervisord
  # ============================================================
  app:
    # 預建 image(基於 Dockerfile.nextcloud)。如需本地 build,改用:
    # build:
    #   context: .
    #   dockerfile: Dockerfile.nextcloud
    image: <your-registry>/nextcloud-fpm:<tag>
    restart: unless-stopped
    depends_on:
      - db
      - redis
    command: ["/usr/bin/supervisord", "-c", "/etc/supervisor/supervisord.conf"]
    volumes:
      - nextcloud_html:/var/www/html
      - /volume1/docker/nextcloud/data:/var/www/html/data
      - /volume1/docker/nextcloud/config:/var/www/html/config
      - /volume1/docker/nextcloud/custom_apps:/var/www/html/custom_apps
      # External Storage(NAS 共享資料夾)
      - /volume1/data:/mnt/data
      - /volume2/data2:/mnt/data2
      # 設定檔 mount(修改後重啟容器即生效,不需 rebuild)
      - ./supervisord.conf:/etc/supervisor/supervisord.conf:ro
      - ./scan-external-storages.sh:/opt/scan-external-storages.sh:ro
      - ./scan-crontab:/opt/scan-crontab:ro
    environment:
      - MYSQL_HOST=db
      - MYSQL_DATABASE=nextcloud
      - MYSQL_USER=nextcloud
      - MYSQL_PASSWORD=<YOUR_DB_PASSWORD>
      - REDIS_HOST=redis
      - REDIS_HOST_PORT=6379
      - OVERWRITEPROTOCOL=https
      - OVERWRITECLIURL=https://cloud.yourdomain.com
      # TRUSTED_PROXIES 以空格分隔,僅首次初始化生效。後續用 occ 設定。
      - TRUSTED_PROXIES=172.16.0.0/12 10.0.0.0/8
      - PHP_MEMORY_LIMIT=1G
      - PHP_UPLOAD_LIMIT=16G
    tmpfs:
      - /tmp:exec    # Memories exiftool 需要在 /tmp 執行 binary

  # ============================================================
  # Nginx (Reverse Proxy for FPM)
  # ============================================================
  web:
    image: nginx:alpine
    restart: unless-stopped
    depends_on:
      - app
    volumes:
      - nextcloud_html:/var/www/html:ro
      # custom_apps 是獨立目錄,不在 nextcloud_html 中。
      # Nginx 必須直接存取其靜態資源(JS/CSS),否則 App 前端全部 404。
      - /volume1/docker/nextcloud/custom_apps:/var/www/html/custom_apps:ro
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
    ports:
      - "8880:80"

  # ============================================================
  # go-vod (External Transcoder for Memories)
  # ============================================================
  go-vod:
    image: radialapps/go-vod
    restart: unless-stopped
    init: true
    depends_on:
      - app
    environment:
      # Docker 內部連接 Nginx,不繞 Cloudflare
      - NEXTCLOUD_HOST=http://web:80
      - NEXTCLOUD_ALLOW_INSECURE=1
    devices:
      - /dev/dri:/dev/dri    # VA-API 硬體加速
    volumes:
      - /volume1/docker/nextcloud/data:/var/www/html/data:ro

volumes:
  nextcloud_html:

Dockerfile.nextcloud

dockerfile
FROM nextcloud:32-fpm

# 替換主 repo 為 NCHC 鏡像(加速台灣下載),保留 security repo 官方 CDN
RUN sed -i '/debian-security/!s|deb.debian.org|opensource.nchc.org.tw|g' \
      /etc/apt/sources.list.d/*.sources 2>/dev/null; \
    sed -i '/debian-security/!s|deb.debian.org|opensource.nchc.org.tw|g' \
      /etc/apt/sources.list 2>/dev/null; \
    apt-get update && \
    apt-get install -y --no-install-recommends \
      ffmpeg \
      procps \
      supervisor \
    && rm -rf /var/lib/apt/lists/* \
    && mkdir -p /var/log/supervisord /var/run/supervisord \
    && mkdir -p /var/spool/cron/scan-crontabs

# Imagick 已內建 | Perl 通常不需要(Memories 自帶 exiftool binary)
# VA-API 由 go-vod 容器負責,此容器的 ffmpeg 僅用於縮圖擷取

# 覆蓋 CMD 後仍需此變數觸發 Nextcloud 自動安裝/更新
ENV NEXTCLOUD_UPDATE=1

# CMD 和 supervisord.conf 由 docker-compose.yaml 提供,不寫在 image 內

需要 VA-API 於 app 容器內(internal transcoder 模式)時,額外安裝 libva2intel-media-va-driver-non-freelibva-drm2。若 apt 找不到 non-free driver,先加入來源:

dockerfile
RUN echo "deb http://deb.debian.org/debian bookworm non-free non-free-firmware" >> /etc/apt/sources.list.d/non-free.list

supervisord.conf

ini
[unix_http_server]
file=/var/run/supervisord/supervisor.sock

[supervisorctl]
serverurl=unix:///var/run/supervisord/supervisor.sock

[supervisord]
nodaemon=true
logfile=/var/log/supervisord/supervisord.log
pidfile=/var/run/supervisord/supervisord.pid
childlogdir=/var/log/supervisord/
logfile_maxbytes=50MB
logfile_backups=10
loglevel=error

[rpcinterface:supervisor]
supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface

[program:php-fpm]
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0
command=php-fpm

[program:cron]
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0
command=/cron.sh

[program:scan-cron]
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0
command=/bin/bash -c "cp /opt/scan-crontab /var/spool/cron/scan-crontabs/www-data && chmod 600 /var/spool/cron/scan-crontabs/www-data && exec busybox crond -f -c /var/spool/cron/scan-crontabs -L /dev/stdout"

/cron.sh 是官方 image 內建腳本,用 busybox crond 每 5 分鐘執行 cron.phpscan-cron 是第二個獨立 crond 實例,使用 /var/spool/cron/scan-crontabs/ 作為 crontab 目錄,啟動時將 mount 的 /opt/scan-crontab 複製並設權限 600(busybox crond 要求),以 www-data 身份執行排程。

scan-external-storages.sh

bash
#!/bin/bash
# 參數:--full 全量掃描(不帶則為增量掃描)

# === External Storage 掛載路徑 ===
# 格式:<Nextcloud 使用者>/files/<掛載點名稱>
# ⚠ 誤填檔案系統路徑(如 /mnt/data)會導致 occ 報 "Unknown user" 錯誤。
# 可用 occ files_external:list 確認掛載點名稱。
PATHS=(
  "admin/files/data"
  "admin/files/data2"
)

cd /var/www/html || exit 1

SCAN_FLAGS="--unscanned"
if [[ "$1" == "--full" ]]; then
  SCAN_FLAGS=""
fi

for p in "${PATHS[@]}"; do
  echo "[scan] Scanning: $p (flags: ${SCAN_FLAGS:-full})"
  php occ files:scan $SCAN_FLAGS --path="$p"
done

php occ memories:index

scan-crontab

crontab
# 增量掃描(每 15 分鐘,只處理新檔案)
*/15 * * * * /opt/scan-external-storages.sh

# 全量掃描(每日 UTC 19:00 = 台灣凌晨 3 點,處理刪除/修改的檔案)
0 19 * * * /opt/scan-external-storages.sh --full

容器內 crond 使用 UTC 時區。若容器設了 TZ=Asia/Taipei,則改用台灣時間 0 3

nginx.conf(完整)

nginx
worker_processes auto;

error_log  /var/log/nginx/error.log warn;
pid        /var/run/nginx.pid;

events {
    worker_connections 1024;
}

http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;

    types {
        text/javascript mjs;
        application/wasm wasm;
    }

    sendfile        on;
    keepalive_timeout 65;

    client_max_body_size 16G;
    client_body_timeout 300s;
    client_body_buffer_size 512k;
    fastcgi_buffers 64 4K;

    gzip on;
    gzip_vary on;
    gzip_comp_level 4;
    gzip_min_length 256;
    gzip_proxied expired no-cache no-store private no_last_modified no_etag auth;
    gzip_types application/atom+xml text/javascript application/javascript application/json application/ld+json application/manifest+json application/rss+xml application/vnd.geo+json application/vnd.ms-fontobject application/wasm application/x-font-ttf application/x-web-app-manifest+json application/xhtml+xml application/xml font/opentype image/bmp image/svg+xml image/x-icon text/cache-manifest text/css text/plain text/vcard text/vnd.rim.location.xloc text/vtt text/x-component text/x-cross-domain-policy;

    # 帶 ?v=xxx 的靜態資源標記為 immutable
    map $arg_v $asset_immutable {
        "" "";
        default ", immutable";
    }

    upstream php-handler {
        server app:9000;
    }

    server {
        listen 80;
        server_name _;

        server_tokens off;

        root /var/www/html;
        index index.php index.html /index.php$request_uri;

        # --- Microsoft WebDAV client (DavClnt) ---
        location = / {
            if ( $http_user_agent ~ ^DavClnt ) {
                return 302 /remote.php/webdav/$is_args$args;
            }
        }

        location = /robots.txt {
            allow all;
            log_not_found off;
            access_log off;
        }

        # --- .well-known ---
        location ^~ /.well-known {
            location = /.well-known/carddav { return 301 /remote.php/dav/; }
            location = /.well-known/caldav  { return 301 /remote.php/dav/; }
            location /.well-known/acme-challenge    { try_files $uri $uri/ =404; }
            location /.well-known/pki-validation    { try_files $uri $uri/ =404; }
            return 301 /index.php$request_uri;
        }

        # --- 安全 headers ---
        add_header Referrer-Policy                   "no-referrer"       always;
        add_header X-Content-Type-Options            "nosniff"           always;
        add_header X-Frame-Options                   "SAMEORIGIN"        always;
        add_header X-Permitted-Cross-Domain-Policies "none"              always;
        add_header X-Robots-Tag                      "noindex, nofollow" always;
        # HSTS 由 Cloudflare 管理,不在 Nginx 設定

        fastcgi_hide_header X-Powered-By;

        # --- 封鎖敏感路徑 ---
        location ~ ^/(?:build|tests|config|lib|3rdparty|templates|data)(?:$|/)  { return 404; }
        location ~ ^/(?:\.|autotest|occ|issue|indie|db_|console)                { return 404; }

        # --- PHP 處理(必須在靜態資源之前)---
        location ~ \.php(?:$|/) {
            rewrite ^/(?!index|remote|public|cron|core\/ajax\/update|status|ocs\/v[12]|updater\/.+|ocs-provider\/.+|.+\/richdocumentscode(_arm64)?\/proxy) /index.php$request_uri;

            fastcgi_split_path_info ^(.+?\.php)(/.*)$;
            set $path_info $fastcgi_path_info;

            try_files $fastcgi_script_name =404;

            include fastcgi_params;
            fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
            fastcgi_param PATH_INFO $path_info;
            fastcgi_param HTTPS on;
            fastcgi_param modHeadersAvailable true;
            fastcgi_param front_controller_active true;

            fastcgi_pass php-handler;
            fastcgi_intercept_errors on;

            # 串流轉發,不暫存到磁碟
            # ⚠ DAVx⁵ 等 HTTP Transfer-Encoding: chunked 客戶端須改為 on
            fastcgi_request_buffering off;
            fastcgi_max_temp_file_size 0;

            fastcgi_connect_timeout 10s;
            fastcgi_send_timeout 3600s;
            fastcgi_read_timeout 3600s;
        }

        # --- 靜態資源 ---
        location ~ \.(?:css|js|mjs|svg|gif|ico|jpg|png|webp|wasm|tflite|map|ogg|flac|mp4|webm)$ {
            try_files $uri /index.php$request_uri;
            add_header Cache-Control                     "public, max-age=15778463$asset_immutable";
            add_header Referrer-Policy                   "no-referrer"       always;
            add_header X-Content-Type-Options            "nosniff"           always;
            add_header X-Frame-Options                   "SAMEORIGIN"        always;
            add_header X-Permitted-Cross-Domain-Policies "none"              always;
            add_header X-Robots-Tag                      "noindex, nofollow" always;
            access_log off;
        }

        # --- 字型(7 天快取)---
        location ~ \.(otf|ttf|woff2?)$ {
            try_files $uri /index.php$request_uri;
            expires 7d;
            access_log off;
        }

        # --- WebDAV 相容性 ---
        location /remote {
            return 301 /remote.php$request_uri;
        }

        location / {
            try_files $uri $uri/ /index.php$request_uri;
        }
    }
}

部署流程

Step 1:建立 Bind Mount 目錄

bash
mkdir -p /volume1/docker/nextcloud/{data,config,custom_apps}
# Docker bind mount 若路徑不存在會自動建立為 root 擁有,www-data(UID 33)無法寫入
# 必須在首次 docker compose up 前手動建立並設定 owner
chown -R 33:33 /volume1/docker/nextcloud/{data,config,custom_apps}

# nextcloud_html 是具名 volume,Docker 自動管理,不需處理
# External Storage(/volume1/data 等)由 Synology ACL 管理,不需 chown

Step 2:啟動

bash
cd /volume1/docker/nextcloud
docker compose up -d --build    # 首次含 build
docker compose logs -f app      # 等待初始化(約 1-3 分鐘)
docker compose ps               # 確認所有服務 running

Step 3:初始化設定

安裝精靈:訪問 https://cloud.yourdomain.com,填入管理員帳密、MariaDB 資訊(使用者 nextcloud、DB nextcloud、主機 db:3306)。

bash
# 所有 occ 必須以 www-data 執行
docker compose exec --user www-data -it app bash

Trusted Domains & Reverse Proxy

bash
php occ config:system:set trusted_domains 0 --value='cloud.yourdomain.com'
php occ config:system:set trusted_domains 1 --value='web'       # go-vod 內部存取必須!
php occ config:system:set trusted_domains 2 --value='localhost'

php occ config:system:set overwriteprotocol --value='https'
php occ config:system:set overwrite.cli.url --value='https://cloud.yourdomain.com'
php occ config:system:set trusted_proxies 0 --value='172.16.0.0/12'
php occ config:system:set trusted_proxies 1 --value='10.0.0.0/8'

Redis(無密碼,官方映像直接使用單反斜線)

bash
php occ config:system:set memcache.local --value='\OC\Memcache\APCu'
php occ config:system:set memcache.distributed --value='\OC\Memcache\Redis'
php occ config:system:set memcache.locking --value='\OC\Memcache\Redis'
php occ config:system:set redis host --value='redis'
php occ config:system:set redis port --value=6379 --type=integer

Cloudflare chunk 限制

bash
php occ config:system:set files.chunked_upload.max_size --value=99614720 --type=integer

預覽設定

bash
php occ config:system:set enable_previews --value=true --type=boolean
php occ config:system:set preview_max_x --value=4096 --type=integer
php occ config:system:set preview_max_y --value=4096 --type=integer
php occ config:system:set jpeg_quality --value=60 --type=integer
php occ config:app:set preview jpeg_quality --value=60
php occ config:system:set preview_ffmpeg_path --value='/usr/bin/ffmpeg'

# OC\Preview\Movie 涵蓋所有 ffmpeg 支援的影片格式(MKV/MP4/AVI 非有效 class)
php occ config:system:set enabledPreviewProviders 0 --value='OC\Preview\PNG'
php occ config:system:set enabledPreviewProviders 1 --value='OC\Preview\JPEG'
php occ config:system:set enabledPreviewProviders 2 --value='OC\Preview\GIF'
php occ config:system:set enabledPreviewProviders 3 --value='OC\Preview\BMP'
php occ config:system:set enabledPreviewProviders 4 --value='OC\Preview\HEIC'
php occ config:system:set enabledPreviewProviders 5 --value='OC\Preview\TIFF'
php occ config:system:set enabledPreviewProviders 6 --value='OC\Preview\Movie'
php occ config:system:set enabledPreviewProviders 7 --value='OC\Preview\MP3'

地區、時區、Cron、維護

bash
php occ config:system:set default_phone_region --value='TW'
php occ config:system:set default_timezone --value='Asia/Taipei'
php occ background:cron
php occ config:system:set maintenance_window_start --value=19 --type=integer
php occ db:add-missing-indices
php occ maintenance:repair --include-expensive

Step 4:Memories App 設置

bash
# 安裝 App
docker compose exec --user www-data -it app php occ app:install memories
docker compose exec --user www-data -it app php occ app:enable memories
docker compose exec --user www-data -it app php occ app:install previewgenerator  # 注意:無底線
docker compose exec --user www-data -it app php occ app:install recognize
docker compose exec --user www-data -it app php occ app:enable photos

# Reverse Geocoding(下載 ~500MB 地理資料,需數分鐘)
docker compose exec --user www-data -it app php occ memories:places-setup
# 若 crash 可加 --transaction-size=5

# 首次掃描 + 索引
docker compose exec --user www-data -it app php occ files:scan --all
docker compose exec --user www-data -it app php occ memories:index

Memories Admin 設定(Settings → Administration → Memories):

設定項
Enable transcoding
Enable external transcoder
Connection addressgo-vod:47788
Enable VA-API
Quality Factor (CRF)24

Step 5:Recognize AI 設定

bash
docker compose exec --user www-data -it app php occ recognize:download-models

# 限制 1 核心 + nice 15,避免拖垮 NAS
docker compose exec --user www-data -it app php occ config:app:set recognize tensorflow.cores --value="1"
docker compose exec --user www-data -it app php occ config:app:set recognize nice_binary --value="/usr/bin/nice"
docker compose exec --user www-data -it app php occ config:app:set recognize nice_value --value="15"

# 首次完整分類(背景執行,斷點續跑)
docker compose exec --user www-data -T app bash -c \
  'nohup php -d memory_limit=2G /var/www/html/occ recognize:classify > /tmp/recognize.log 2>&1 &'

# 查看進度
docker compose exec app tail -f /tmp/recognize.log

Step 6:Recognize Ignore Markers(排除目錄)

對 External Storage 的目錄,標記檔必須存在於 Nextcloud filecache 中才生效:

bash
# Step 1:在 NAS 檔案系統建立空檔案
touch /volume1/data/.nomedia
touch /volume2/data2/.nomedia

# Step 2:讓 Nextcloud 掃描發現標記檔(--shallow 只掃該層)
docker compose exec --user www-data -T app php occ files:scan --path="admin/files/data" --shallow
docker compose exec --user www-data -T app php occ files:scan --path="admin/files/data2" --shallow
標記檔排除範圍
.noimage圖片辨識(物件標籤、人臉、地標)
.nomusic音樂類型辨識
.novideo影片動作辨識
.nomedia以上全部

config.php 範例

php
<?php
$CONFIG = array (
  'trusted_domains' => array (
    0 => 'cloud.yourdomain.com',
    1 => 'web',        // go-vod 透過 http://web:80 存取,必須包含
    2 => 'localhost',
  ),
  'trusted_proxies' => array (
    0 => '172.16.0.0/12',
    1 => '10.0.0.0/8',
  ),
  'overwriteprotocol' => 'https',
  'overwrite.cli.url' => 'https://cloud.yourdomain.com',
  'default_phone_region' => 'TW',
  'default_timezone' => 'Asia/Taipei',
  'maintenance_window_start' => 19,  // UTC 19 = 台灣 03:00
  'files.chunked_upload.max_size' => 99614720,  // 95 MiB(Cloudflare Free 100MB 限制)
  'memcache.local' => '\\OC\\Memcache\\APCu',
  'memcache.distributed' => '\\OC\\Memcache\\Redis',
  'memcache.locking' => '\\OC\\Memcache\\Redis',
  'redis' => array (
    'host' => 'redis',
    'port' => 6379,
  ),
  'enable_previews' => true,
  'enabledPreviewProviders' => array (
    0 => 'OC\\Preview\\PNG',
    1 => 'OC\\Preview\\JPEG',
    2 => 'OC\\Preview\\GIF',
    3 => 'OC\\Preview\\BMP',
    4 => 'OC\\Preview\\HEIC',
    5 => 'OC\\Preview\\TIFF',
    6 => 'OC\\Preview\\Movie',
  ),
  'preview_max_x' => 4096,
  'preview_max_y' => 4096,
  'preview_ffmpeg_path' => '/usr/bin/ffmpeg',
  'memories.vod.disable' => false,    // 官方預設 true,刻意啟用
  'memories.vod.vaapi' => true,
  'memories.vod.external' => true,
  'memories.vod.connect' => 'go-vod:47788',
  'memories.vod.qf' => 24,
);

常用維運指令

bash
# 服務狀態
docker compose ps
docker compose exec app supervisorctl status  # 確認 php-fpm/cron/scan-cron 均 RUNNING

# Logs
docker compose logs -f app
docker compose logs -f go-vod

# 重啟單一程序
docker compose exec app supervisorctl restart cron
docker compose exec app supervisorctl restart scan-cron

# occ(必須 --user www-data)
docker compose exec --user www-data -it app php occ [command]

# 掃描 + 索引
docker compose exec --user www-data -it app php occ files:scan --all
docker compose exec --user www-data -it app php occ memories:index

# Preview Generator(初始化)
docker compose exec --user www-data -it app php occ preview:generate-all
# 宿主機 crontab 定期預生成(加 -T 避免 TTY 問題):
# */10 * * * * docker compose -f /volume1/docker/nextcloud/docker-compose.yaml exec --user www-data -T app php occ preview:pre-generate

# 備份
docker compose exec --user www-data -T app php occ maintenance:mode --on
docker compose exec -T db bash -c 'mysqldump -u root -p"$MYSQL_ROOT_PASSWORD" nextcloud' | gzip > backup_$(date +%Y%m%d).sql.gz
docker compose cp app:/var/www/html/config/config.php ./config-backup.php
docker compose exec --user www-data -T app php occ maintenance:mode --off

常見問題排查

問題原因解法
go-vod 持續 Failed to fetch... Retryingtrusted_domains 未包含 web加入 php occ config:system:set trusted_domains 1 --value='web'
Access through untrusted domaintrusted_domains 缺少域名加入外部域名 + web + localhost
大檔上傳失敗Nginx client_max_body_size 或 Cloudflare limit確認 Nginx 設 16G,PHP env 設 PHP_UPLOAD_LIMIT=16G,chunk ≤ 95 MiB
Incorrect string value(Geocoding)MariaDB 字元集非 utf8mb4MariaDB command 已設,若仍報錯執行 ALTER DATABASE nextcloud CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci
Recognize 管理面板持續顯示 CheckingJ4125 無 AVX,WASM 模式 UI bug已知問題 #1123,不影響功能

相關概念

來源