Nextcloud/PhotoPrismでフォトストレージ(オンプレミス)を構築する
🚀始めに
友人と遊んだり旅行に行ったりして写真を撮影したとき、画像データをGoogleフォトに同期していたのですが、Googleフォトの容量が無制限ではなくなったこともあり、代替手段を探していました。
Amazonフォトを利用するのが一番楽で良さそうなのですが、最近Dockerにハマっていることもあり、折角だからDockerでフォトストレージを構築しようと思い挑戦してみました。
「外出先で写真撮影して、ホテルのWi-Fiに接続したら、画像データがフォトストレージに同期される」ことを目標としています。
Googleフォトの代替手段を探している方、Dockerを利用して何か作ってみたい方の参考になれば嬉しいです。
💻マシンスペック
今回利用したマシンのスペックは次の通りです。
Orange Pi 5
SoC: Rockchip RK3588S
メモリ: 16GB
ストレージ: MicroSD 512GB
OS: Armbian 23.02 Jammy Gnome
その他: Docker CE(Community Edition) 23.0.5
http://www.orangepi.org/html/hardWare/computerAndMicrocontrollers/details/Orange-Pi-5.html
https://www.armbian.com/orangepi-5/
※今回はarm64マシンで構築しましたが、一般的なamd64マシンでも問題なく構築できると思います。
🗺システム構成
今回作成したフォトストレージのシステム構成は次の通りです。
システム構成
※一部のアイコンセットにICONS8のアイコンセットを利用しています。
https://icons8.jp/
🌏OSS/サービス
今回利用したOSS/サービスの概要は次の通りです。
・Docker
コンテナ型仮想環境のプラットフォーム。
https://www.docker.com/
・Nextcloud
オンラインストレージとして利用できるOSS。
Googleドライブみたいなやつです。
https://nextcloud.com/
・PhotoPrism
画像管理プラットフォームとして利用できるOSS。
Googleフォトみたいなやつです。
https://www.photoprism.app/
・Traefik
リバースプロキシとして利用できるOSS。
Nginxでも代用とできると思います。
https://traefik.io/traefik/
・Googleドメイン
ドメインレジストラ。
今回はdevドメインが欲しくて利用しましたが、別のサービスを利用しても問題ありません。
https://domains.google/intl/ja_jp/
・MyDNS
無料で利用できるDDNSサービス。
グローバルIPアドレスや各種レコードの登録、SSL証明書の発行など、何かとお世話になります。
https://www.mydns.jp/
・Let's Encrypt
無料で利用できるSSL証明書発行サービス。
3ヶ月で証明書が失効するため、更新する手段を別途用意します。
https://letsencrypt.org/ja/
・Certbot
SSL証明書を発行するOSS。
Certbotを利用してLet's EncryptでSSL証明書を発行します。
https://certbot.eff.org/
🛠構築手順
-
レジストラでドメインを取得する
Googleドメインなどのレジストラでドメインを取得します。
※例としてexample.devを取得したことにします。
GoogleドメインのDNSサーバーをそのまま利用することも可能ですが、後述するグローバルIPアドレスの更新やSSL証明書の発行でMyDNSと連携するので、今回はDNSサーバーにMyDNSのDNSサーバーを指定します。
レジストラの設定
これで、全体構成の「1. 名前解決」ができるようになります。
(と言っても、GoogleドメインはMyDNSを参照してね、と応答するだけですが…。) -
MyDNSにドメイン情報を設定する
まず、グローバルIPアドレスを登録(更新)します。
MyDNSでは様々な方法でグローバルIPアドレスを登録することができますが、今回はHTTPのBASIC認証で登録します。
設定項目と設定値は次の通りです。設定項目 設定値 通知先URL https://ipv4.mydns.jp/login.html
https://ipv6.mydns.jp/login.htmlアカウント MyDNSのID パスワード MyDNSのパスワード 今回はHTTPのBASIC認証でグローバルIPアドレスを登録するためのシェルスクリプトを作成しました。
update_mydns.sh#! /usr/bin/env bash SCRIPT_DIRNAME=$(cd $(dirname ${0}); pwd) cd ${SCRIPT_DIRNAME} # Update MyDNS curl -sLv -u <MyDNSのID>:<MyDNSのパスワード> https://ipv4.mydns.jp/login.html curl -sLv -u <MyDNSのID>:<MyDNSのパスワード> https://ipv6.mydns.jp/login.html
グローバルIPアドレスはルーターの再起動などで変わってしまうので、定期的に更新する(MyDNSに通知する)と良いです。
今回は前述のシェルスクリプトを定期実行するようcronを設定しました。
次に、名前解決したいサブドメインを登録します。
例えば、www.example.dev を名前解決したい場合は、Hostnameにwwwを指定します。
今回は特にこだわりがなかったので、ワイルドカード(*)を指定しました。
ドメイン情報
これで、全体構成の「2. 名前解決」および「7. IPアドレス更新」ができるようになります。 -
SSL証明書を発行する
Let's EncryptからSSL証明書を発行してもらいます。
証明書を発行するために、Certbotを利用します。
また、今回は一般的なHTTPチャレンジではなくDNSチャレンジを利用しました。
(ワイルドカード証明書を発行したかったため。)
チャレンジの種類や証明書の種類については次を参照してください。
https://letsencrypt.org/ja/docs/challenge-types/
https://kinsta.com/jp/blog/types-of-ssl-certificates/#ssl-3
DNSチャレンジを利用してSSL証明書を発行する場合は、ワンタイムパスワードをTXTレコードとしてDNSサーバーに登録する必要があります。
MyDNSでは、ワンタイムパスワードの登録/削除を自動化するスクリプトが用意されています。
https://github.com/disco-v8/DirectEdit/
今回はCertbotのコンテナをビルドして、コンテナ起動時に前述のスクリプトをマウントしてSSL証明書を発行できるようにしました。
ディレクトリ構成は次の通りです。<PROJECT_NAME> ├── .dockerignore ├── docker │ └── certbot.dockerfile ★dockerfile ├── script │ ├── build_image.sh ★Certbotのコンテナをビルドするシェルスクリプト │ ├── run_certbot_wildcard.sh ★SSL証明書を発行するシェルスクリプト │ └── update_mydns.sh ★グローバルIPアドレスを更新するシェルスクリプト └── volume └── certbot └── workspace ★コンテナにマウントするディレクトリ ├── certificate │ └── wildcard ★SSL証明書を配置するディレクトリ ├── hook │ └── wildcard │ └── DirectEdit-master ★MyDNSの自動化ツール │ ├── README.md │ ├── debug.log │ ├── txtdelete.php │ ├── txtedit.conf │ └── txtregist.php └── script └── run_certbot_wildcard.sh ★SSL証明書を発行するシェルスクリプト
まず、Certbotのコンテナをビルドします。
certbot.dockerfile# ============================== # Stage-1 # ============================== FROM ubuntu:jammy as ubuntu-jammy-user ARG DEBIAN_FRONTEND=noninteractive ARG USER_NAME=nobody ARG GROUP_NAME=nogroup ARG PASSWORD=${USER_NAME} ARG USER_ID=1000 ARG GROUP_ID=${USER_ID} SHELL ["/usr/bin/bash", "-c"] # ============================== # Update apt config # ============================== RUN echo "Update apt config" && \ echo "APT::Install-Recommends 0;" > /etc/apt/apt.conf.d/00-no-install-recommends && \ echo "APT::Install-Suggests 0;" > /etc/apt/apt.conf.d/00-no-install-suggests # ============================== # Update package lists # Upgrade packages # ============================== RUN echo "Upgrade packages" && \ apt update && \ apt upgrade -y && \ apt autopurge -y # ============================== # Add general user # ============================== RUN echo "Add general user" && \ if [ "${USER_NAME}" = "nobody" ]; \ then \ echo "Skip adding general user"; \ else \ apt install -y sudo && \ groupadd -g ${GROUP_ID} ${GROUP_NAME} && \ useradd -u ${USER_ID} -g ${GROUP_ID} --groups sudo -s /usr/bin/bash ${USER_NAME} && \ echo ${USER_NAME}:${PASSWORD} | chpasswd && \ echo "${USER_NAME} ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers; \ fi; # ============================== # Stage-2 # ============================== FROM ubuntu-jammy-user as certbot SHELL ["/usr/bin/bash", "-c"] # ============================== # Install certbot # ============================== RUN echo "Install certbot" && \ apt install -y certbot \ php \ php-mbstring # ============================== # Remove unused packages # ============================== RUN echo "Remove unused packages" && \ apt update && \ apt upgrade -y && \ apt autopurge -y && \ apt autoclean && \ rm -rf /var/lib/apt/lists/* USER ${USER_NAME}
build_image.sh#! /usr/bin/env bash SCRIPT_DIRNAME=$(cd $(dirname ${0}) && pwd) DOCKER_PROJECT_PATH=$(cd $(dirname ${0}) && cd ../ && pwd) cd ${DOCKER_PROJECT_PATH} docker image build -t certbot \ -f ${DOCKER_PROJECT_PATH}/docker/certbot.dockerfile . \ --progress plain \ --build-arg USER_NAME=$(id -nu ${USER}) \ --build-arg GROUP_NAME=$(id -ng ${USER}) \ --build-arg USER_ID=$(id -u ${USER}) \ --build-arg GROUP_ID=$(id -g ${USER}) docker system prune -f
次に、MyDNSの自動化ツールの使い方に従ってファイルの修正を行います。
https://github.com/disco-v8/DirectEdit/#使い方usagetextedit.conf<?php // ------------------------------------------------------------ // // txtedit.conf // // ------------------------------------------------------------ ?> <?php $MYDNSJP_URL = 'https://www.mydns.jp/directedit.html'; $MYDNSJP_MASTERID = '<MyDNSのID>'; $MYDNSJP_MASTERPWD = '<MyDNSのパスワード>'; $MYDNSJP_DOMAIN = 'example.dev'; ?>
最後に、SSL証明書を発行します。
run_certbot_wildcard.sh(<PROJECT_NAME>/script/run_certbot_wildcard.sh)#! /usr/bin/env bash SCRIPT_DIRNAME=$(cd $(dirname ${0}) && pwd) DOCKER_PROJECT_PATH=$(cd $(dirname ${0}) && cd ../ && pwd) CERTBOT_VOLUME_PATH=${DOCKER_PROJECT_PATH}/volume/certbot SSL_CERTIFICATE_PATH=<任意のパス> cd ${SCRIPT_DIRNAME} docker container run --rm \ -it \ -v ${CERTBOT_VOLUME_PATH}/workspace:/usr/local/share/workspace \ -v /etc/group:/etc/group:ro \ -v /etc/passwd:/etc/passwd:ro \ -u $(id -u ${USER}):$(id -g ${USER}) \ certbot \ bash -c ' cd /usr/local/share/workspace bash ./script/run_certbot_wildcard.sh sudo cp -rf /etc/letsencrypt/archive/example.dev/fullchain1.pem \ ./certificate/wildcard/fullchain.pem sudo cp -rf /etc/letsencrypt/archive/example.dev/privkey1.pem \ ./certificate/wildcard/privkey.pem ' sudo cp -rf ${CERTBOT_VOLUME_PATH}/workspace/certificate/wildcard ${SSL_CERTIFICATE_PATH}
run_certbot_wildcard.sh(<PROJECT_NAME>/volume/certbot/workspace/script/run_certbot_wildcard.sh)#! /usr/bin/env bash SCRIPT_DIRNAME=$(cd $(dirname ${0}) && pwd) WORKSPACE_PATH=$(cd $(dirname ${0}) && cd ../ && pwd) cd ${SCRIPT_DIRNAME} # Production sudo certbot certonly --noninteractive \ --manual \ --preferred-challenges dns \ --manual-auth-hook ${WORKSPACE_PATH}/hook/wildcard/DirectEdit-master/txtregist.php \ --manual-cleanup-hook ${WORKSPACE_PATH}/hook/wildcard/DirectEdit-master/txtdelete.php \ --domain *.example.dev \ --agree-tos -m <メールアドレス> \ --manual-public-ip-logging-ok
<PROJECT_NAME>/volume/certbot/workspace/certificate/wildcardにfullchain.pemとprivkey.pemが配置されていれば成功です。
今回は別のプロジェクトでもSSL証明書を利用できるように、~/share/certificate/wildcardのようなディレクトリにSSL証明書をコピーしました。これで、全体構成の「8. SSL証明書発行」ができるようになります。
Let's Encryptで発行したSSL証明書は3ヶ月で失効するので、失効する前に前述のrun_certbot_wildcard.shを実行するようにしています。 -
Traefikを構築する
Traefikでリバースプロキシを構築します。
必須ではないですが、Traefikを使うことでSSL証明書を一元管理でき、ホストマシンのポートを余計に潰さずに済みます。
Traefikのドキュメントや有志の記事を参考に構築します。
https://doc.traefik.io/traefik/
https://zenn.dev/pitekusu/books/traefik-pitekusu
(こちらの記事が個人的に大変分かりやすかったです。)
ディレクトリ構成は次の通りです。<PROJECT_NAME> ├── docker │ └── compose.yml ├── script │ ├── docker_compose_down.sh │ └── docker_compose_up.sh ├── symlink │ └── traefik │ └── etc │ └── traefik │ └── certificate │ └── wildcard -> ~/share/certificate/wildcardなど ★SSL証明書へのシンボリックリンク └── volume └── traefik └── etc └── traefik ★コンテナにマウントするディレクトリ ├── configuration │ └── dynamic_configuration.yml ★Traefikの動的設定ファイル └── traefik.yml ★Traefikの静的設定ファイル
Traefikは静的/動的に設定を反映することができます。
静的設定ファイルはコンテナの/etc/traefik/traefik.ymlに書きます。traefik.ymlapi: insecure: true dashboard: true ★ダッシュボード機能を有効化 providers: docker: exposedByDefault: false network: reverse-proxy file: directory: /etc/traefik/configuration ★動的設定ファイルを配置するパス entryPoints: ★エントリーポイント web: address: :80 http: redirections: entryPoint: to: websecure scheme: https websecure: address: :443
動的設定ファイルは静的設定ファイルに書いたパスに配置(マウント)します。
dynamic_configuration.ymltls: certificates: ★SSL証明書のパス - certFile: /etc/traefik/certificate/wildcard/fullchain.pem keyFile: /etc/traefik/certificate/wildcard/privkey.pem
compose.ymlは次の通りです。
compose.ymlservices: traefik: image: traefik:latest container_name: rp-traefik restart: always networks: - reverse-proxy ports: - 60080:80 ★HTTP - 60443:443 ★HTTPS - 58080:8080 ★ダッシュボード volumes: - ../volume/traefik/etc/traefik:/etc/traefik - ../symlink/traefik/etc/traefik/certificate/wildcard/:/etc/traefik/certificate/wildcard/:ro - /var/run/docker.sock:/var/run/docker.sock:ro stdin_open: true tty: true networks: reverse-proxy: external: true
シンボリックリンクをコンテナにマウントする場合は、"/"をつけて実体(リンク先)をマウントするようにしています。
適宜Dockerネットワークの作成を行って、コンテナを起動します。docker_compose_up.sh#! /usr/bin/env bash SCRIPT_DIRNAME=$(cd $(dirname ${0}) && pwd) DOCKER_PROJECT_PATH=$(cd $(dirname ${0}) && cd ../ && pwd) cd ${DOCKER_PROJECT_PATH}/docker DOCKER_PROJECT_NAME=reverse-proxy DOCKER_NETWORK_NAME=${DOCKER_PROJECT_NAME} docker network create ${DOCKER_NETWORK_NAME} docker compose -p ${DOCKER_PROJECT_NAME} up -d --force-recreate
無事コンテナを起動できたら、http://<ホストマシンのIPアドレス>:58080にアクセスしてみてください。
Traefikのダッシュボードが表示されるはずです。
Traefikのダッシュボード
また、自宅ルーターの設定で、80番ポート→ホストマシンの60080番ポート、443番ポート→ホストマシンの60443番ポートにポートフォワードするよう設定してください。
これで、全体構成の「4. ポートフォワード」および「5. リバースプロキシ」ができるようになります。 -
Nextcloudを構築する
大本命のNextcloudを構築します。
Nextcloudは公式でcompose.ymlが用意されているので、そちらを参考にします。
https://github.com/nextcloud/all-in-one/blob/main/docker-compose.yml
ディレクトリ構成は次の通りです。<PROJECT_NAME> ├── docker │ ├── compose.env │ └── compose.yml ├── script │ ├── docker_compose_down.sh │ └── docker_compose_up.sh └── volume ├── mariadb │ └── var │ └── lib │ └── mysql ★コンテナにマウントするディレクトリ └── nextcloud └── var └── www └── html ★コンテナにマウントするディレクトリ
compose.ymlは次の通りです。
compose.ymlservices: nextcloud: image: nextcloud:latest container_name: os-nextcloud depends_on: - mariadb restart: always networks: - online-storage - reverse-proxy labels: - traefik.enable=true # TCP - traefik.tcp.routers.nextcloud.rule=HostSNI(`www.example.dev`) - traefik.tcp.routers.nextcloud.entrypoints=websecure - traefik.tcp.routers.nextcloud.tls=true - traefik.tcp.routers.nextcloud.service=nextcloud - traefik.tcp.services.nextcloud.loadbalancer.server.port=80 - traefik.tcp.services.nextcloud.loadbalancer.proxyprotocol.version=2 # HTTP - traefik.http.routers.nextcloud.rule=Host(`www.example.dev`) - traefik.http.routers.nextcloud.entrypoints=websecure - traefik.http.routers.nextcloud.tls=true - traefik.http.routers.nextcloud.service=nextcloud - traefik.http.services.nextcloud.loadbalancer.server.port=80 volumes: - ../volume/nextcloud/var/www/html:/var/www/html - /etc/group:/etc/group:ro - /etc/passwd:/etc/passwd:ro environment: - MYSQL_USER=<MariaDBのID> - MYSQL_PASSWORD=<MariaDBのパスワード> - MYSQL_DATABASE=<MariaDBのデータベース> - MYSQL_HOST=mariadb stdin_open: true tty: true user: ${USER_ID}:${GROUP_ID} mariadb: image: mariadb:latest container_name: os-mariadb restart: always command: --transaction-isolation=READ-COMMITTED --binlog-format=ROW networks: - online-storage volumes: - ../volume/mariadb/var/lib/mysql:/var/lib/mysql - /etc/group:/etc/group:ro - /etc/passwd:/etc/passwd:ro environment: - MYSQL_ROOT_PASSWORD=<MariaDBのルートパスワード> - MYSQL_USER=<MariaDBのID> - MYSQL_PASSWORD=<MariaDBのパスワード> - MYSQL_DATABASE=<MariaDBのデータベース> stdin_open: true tty: true user: ${USER_ID}:${GROUP_ID} networks: online-storage: external: true reverse-proxy: external: true
リバースプロキシとしてTraefikを利用するので、labelsを沢山書いていますが、リバースプロキシを利用しない場合は、portsを書いて直接ホストマシンにポートフォワードしてください。
また、コンテナにマウントするディレクトリ/ファイルの所有者がrootになるのが嫌だったので、ホストマシンの/etc/groupと/etc/passwdをリードオンリーでマウントし、userでユーザーID/グループIDを指定しました。compose.envUSER_ID=1000 GROUP_ID=1000
適宜Dockerネットワークの作成を行って、コンテナを起動します。
docker_compose_up.sh#! /usr/bin/env bash SCRIPT_DIRNAME=$(cd $(dirname ${0}) && pwd) DOCKER_PROJECT_PATH=$(cd $(dirname ${0}) && cd ../ && pwd) cd ${DOCKER_PROJECT_PATH}/docker DOCKER_PROJECT_NAME=online-storage DOCKER_NETWORK_NAME=${DOCKER_PROJECT_NAME} docker network create ${DOCKER_NETWORK_NAME} docker compose --env-file ${DOCKER_PROJECT_PATH}/docker/compose.env -p ${DOCKER_PROJECT_NAME} up -d --force-recreate
無事コンテナを起動できたら、https://www.example.dev にアクセスして、Nextcloudの初期設定を行ってください。
(NextcloudのID/パスワードを入力する程度だったように記憶しています。)
初期設定が完了すると、Nextcloudのダッシュボードが表示されるはずです。
Nextcloudのダッシュボード
これで、全体構成の「6. 画像データ同期」ができるようになります。 -
クライアントアプリを設定する
スマートフォンで利用するクライアントアプリを設定します。
クライアントアプリはNextcloud公式アプリでも良いですし、サードパーティのアプリがあればそちらでも構いません。
今回はPhotoSync(有料プラグインあり)を利用しました。
WebDAVを利用してNextcloudに画像をアップロードします。
WebDAVのURLはhttps://www.example.dev/remote.php/dav/files/<NextcloudのID>です。
PhotoSyncでは自動転送設定をすることが可能です。
PhotoSyncの自動転送設定
これで、全体構成の「3. 画像データ同期」ができるようになります。 -
PhotoPrismを構築する
おまけ程度ですが、PhotoPrismを構築します。
Traefikと同様必須ではありません。
公式でcompose.ymlが用意されているので、そちらを参考に構築します。
https://github.com/photoprism/photoprism/blob/develop/docker-compose.latest.yml
ディレクトリ構成は次の通りです。<PROJECT_NAME> ├── docker │ ├── compose.env │ └── compose.yml ├── script │ ├── docker_compose_down.sh │ ├── docker_compose_up.sh │ └── run_groundwork.sh ├── symlink │ └── photoprism │ └── photoprism │ └── originals │ └── backup -> <Nextcloudの画像データ> ★Nextcloudの画像データへのシンボリックリンク └── volume ├── mariadb │ └── var │ └── lib │ └── mysql ★コンテナにマウントするディレクトリ └── photoprism └── photoprism ├── originals ★コンテナにマウントするディレクトリ └── storage ★コンテナにマウントするディレクトリ
compose.ymlは次の通りです。
compose.yml# Example Docker Compose config file for PhotoPrism (Linux / AMD64) # # Note: # - Hardware transcoding is only available for sponsors due to the high maintenance and support effort. # - Running PhotoPrism on a server with less than 4 GB of swap space or setting a memory/swap limit can cause unexpected # restarts ("crashes"), for example, when the indexer temporarily needs more memory to process large files. # - If you install PhotoPrism on a public server outside your home network, please always run it behind a secure # HTTPS reverse proxy such as Traefik or Caddy. Your files and passwords will otherwise be transmitted # in clear text and can be intercepted by anyone, including your provider, hackers, and governments: # https://docs.photoprism.app/getting-started/proxies/traefik/ # # Setup Guides: # - https://docs.photoprism.app/getting-started/docker-compose/ # - https://docs.photoprism.app/getting-started/raspberry-pi/ # # Troubleshooting Checklists: # - https://docs.photoprism.app/getting-started/troubleshooting/ # - https://docs.photoprism.app/getting-started/troubleshooting/docker/ # - https://docs.photoprism.app/getting-started/troubleshooting/mariadb/ # # CLI Commands: # - https://docs.photoprism.app/getting-started/docker-compose/#command-line-interface # # All commands may have to be prefixed with "sudo" when not running as root. # This will point the home directory shortcut ~ to /root in volume mounts. services: photoprism: ## Use photoprism/photoprism:preview for testing preview builds: image: photoprism/photoprism:latest container_name: ps-photoprism depends_on: - mariadb ## Don't enable automatic restarts until PhotoPrism has been properly configured and tested! ## If the service gets stuck in a restart loop, this points to a memory, filesystem, network, or database issue: ## https://docs.photoprism.app/getting-started/troubleshooting/#fatal-server-errors restart: always networks: - photo-storage - reverse-proxy labels: - traefik.enable=true # TCP - traefik.tcp.routers.photoprism.rule=HostSNI(`www2.example.dev`) - traefik.tcp.routers.photoprism.entrypoints=websecure - traefik.tcp.routers.photoprism.tls=true - traefik.tcp.routers.photoprism.service=photoprism - traefik.tcp.services.photoprism.loadbalancer.server.port=2342 - traefik.tcp.services.photoprism.loadbalancer.proxyprotocol.version=2 # HTTP - traefik.http.routers.photoprism.rule=Host(`www2.example.dev`) - traefik.http.routers.photoprism.entrypoints=websecure - traefik.http.routers.photoprism.tls=true - traefik.http.routers.photoprism.service=photoprism - traefik.http.services.photoprism.loadbalancer.server.port=2342 security_opt: - seccomp:unconfined - apparmor:unconfined # ports: # - 2342:2342 # HTTP port (host:container) environment: PHOTOPRISM_ADMIN_USER: <PhotoPrismのID> # superadmin username PHOTOPRISM_ADMIN_PASSWORD: <PhotoPrismのパスワード> # initial superadmin password (minimum 8 characters) PHOTOPRISM_AUTH_MODE: password # authentication mode (public, password) PHOTOPRISM_SITE_URL: https://www2.example.dev # server URL in the format "http(s)://domain.name(:port)/(path)" PHOTOPRISM_ORIGINALS_LIMIT: 5000 # file size limit for originals in MB (increase for high-res video) PHOTOPRISM_HTTP_COMPRESSION: gzip # improves transfer speed and bandwidth utilization (none or gzip) PHOTOPRISM_LOG_LEVEL: info # log level: trace, debug, info, warning, error, fatal, or panic PHOTOPRISM_READONLY: false # do not modify originals directory (reduced functionality) PHOTOPRISM_EXPERIMENTAL: false # enables experimental features PHOTOPRISM_DISABLE_CHOWN: false # disables updating storage permissions via chmod and chown on startup PHOTOPRISM_DISABLE_WEBDAV: false # disables built-in WebDAV server PHOTOPRISM_DISABLE_SETTINGS: false # disables settings UI and API PHOTOPRISM_DISABLE_TENSORFLOW: false # disables all features depending on TensorFlow PHOTOPRISM_DISABLE_FACES: false # disables face detection and recognition (requires TensorFlow) PHOTOPRISM_DISABLE_CLASSIFICATION: false # disables image classification (requires TensorFlow) PHOTOPRISM_DISABLE_RAW: false # disables indexing and conversion of RAW files PHOTOPRISM_RAW_PRESETS: false # enables applying user presets when converting RAW files (reduces performance) PHOTOPRISM_JPEG_QUALITY: 85 # a higher value increases the quality and file size of JPEG images and thumbnails (25-100) PHOTOPRISM_DETECT_NSFW: false # automatically flags photos as private that MAY be offensive (requires TensorFlow) PHOTOPRISM_UPLOAD_NSFW: true # allows uploads that MAY be offensive (no effect without TensorFlow) # PHOTOPRISM_DATABASE_DRIVER: sqlite # SQLite is an embedded database that doesn't require a server PHOTOPRISM_DATABASE_DRIVER: mysql # use MariaDB 10.5+ or MySQL 8+ instead of SQLite for improved performance PHOTOPRISM_DATABASE_SERVER: mariadb:3306 # MariaDB or MySQL database server (hostname:port) PHOTOPRISM_DATABASE_NAME: <MariaDBのデータベース> # MariaDB or MySQL database schema name PHOTOPRISM_DATABASE_USER: <MariaDBのID> # MariaDB or MySQL database user name PHOTOPRISM_DATABASE_PASSWORD: <MariaDBのパスワード> # MariaDB or MySQL database user password PHOTOPRISM_SITE_CAPTION: AI-Powered Photos App PHOTOPRISM_SITE_DESCRIPTION: True Alternative for Google Photos # meta site description PHOTOPRISM_SITE_AUTHOR: <任意の名前> # meta site author ## Run/install on first startup (options: update https gpu tensorflow davfs clitools clean): # PHOTOPRISM_INIT: https gpu tensorflow ## Hardware Video Transcoding: # PHOTOPRISM_FFMPEG_ENCODER: software # FFmpeg encoder ("software", "intel", "nvidia", "apple", "raspberry") # PHOTOPRISM_FFMPEG_BITRATE: 32 # FFmpeg encoding bitrate limit in Mbit/s (default: 50) ## Run as a non-root user after initialization (supported: 0, 33, 50-99, 500-600, and 900-1200): PHOTOPRISM_UID: ${USER_ID} PHOTOPRISM_GID: ${GROUP_ID} # PHOTOPRISM_UMASK: 0000 ## Start as non-root user before initialization (supported: 0, 33, 50-99, 500-600, and 900-1200): stdin_open: true tty: true user: ${USER_ID}:${GROUP_ID} ## Share hardware devices with FFmpeg and TensorFlow (optional): # devices: # - /dev/dri:/dev/dri # Intel QSV # - /dev/nvidia0:/dev/nvidia0 # Nvidia CUDA # - /dev/nvidiactl:/dev/nvidiactl # - /dev/nvidia-modeset:/dev/nvidia-modeset # - /dev/nvidia-nvswitchctl:/dev/nvidia-nvswitchctl # - /dev/nvidia-uvm:/dev/nvidia-uvm # - /dev/nvidia-uvm-tools:/dev/nvidia-uvm-tools # - /dev/video11:/dev/video11 # Video4Linux Video Encode Device (h264_v4l2m2m) working_dir: /photoprism # do not change or remove ## Storage Folders: "~" is a shortcut for your home directory, "." for the current directory volumes: # /host/folder:/photoprism/folder # Example - ../volume/photoprism/photoprism/originals:/photoprism/originals # Original media files (DO NOT REMOVE) # - /example/family:/photoprism/originals/family # *Additional* media folders can be mounted like this - ../symlink/photoprism/photoprism/originals/backup/:/photoprism/originals/backup/:ro # *Additional* media folders can be mounted like this # - ~/Import:/photoprism/import # *Optional* base folder from which files can be imported to originals - ../volume/photoprism/photoprism/storage:/photoprism/storage # *Writable* storage folder for cache, database, and sidecar files (DO NOT REMOVE) ## Database Server (recommended) ## see https://docs.photoprism.app/getting-started/faq/#should-i-use-sqlite-mariadb-or-mysql mariadb: ## If MariaDB gets stuck in a restart loop, this points to a memory or filesystem issue: ## https://docs.photoprism.app/getting-started/troubleshooting/#fatal-server-errors restart: always image: mariadb:latest container_name: ps-mariadb networks: - photo-storage security_opt: # see https://github.com/MariaDB/mariadb-docker/issues/434#issuecomment-1136151239 - seccomp:unconfined - apparmor:unconfined command: mysqld --innodb-buffer-pool-size=512M --transaction-isolation=READ-COMMITTED --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci --max-connections=512 --innodb-rollback-on-timeout=OFF --innodb-lock-wait-timeout=120 ## Never store database files on an unreliable device such as a USB flash drive, an SD card, or a shared network folder: volumes: - ../volume/mariadb/var/lib/mysql:/var/lib/mysql # DO NOT REMOVE environment: MARIADB_AUTO_UPGRADE: 1 MARIADB_INITDB_SKIP_TZINFO: 1 MARIADB_DATABASE: <MariaDBのデータベース> MARIADB_USER: <MariaDBのID> MARIADB_PASSWORD: <MariaDBのパスワード> MARIADB_ROOT_PASSWORD: <MariaDBのルートパスワード> stdin_open: true tty: true user: ${USER_ID}:${GROUP_ID} networks: photo-storage: external: true reverse-proxy: external: true
ちょっと長いですが、やっていることはNextcloudと大きく変わりません。
適宜Dockerネットワークの作成を行って、コンテナを起動します。docker_compose_up.sh#! /usr/bin/env bash SCRIPT_DIRNAME=$(cd $(dirname ${0}) && pwd) DOCKER_PROJECT_PATH=$(cd $(dirname ${0}) && cd ../ && pwd) cd ${DOCKER_PROJECT_PATH}/docker DOCKER_PROJECT_NAME=photo-storage DOCKER_NETWORK_NAME=${DOCKER_PROJECT_NAME} docker network create ${DOCKER_NETWORK_NAME} docker compose --env-file ${DOCKER_PROJECT_PATH}/docker/compose.env -p ${DOCKER_PROJECT_NAME} up -d --force-recreate
無事コンテナを起動できたら、https://www2.example.dev にアクセスしてください。
PhotoPrismのログイン画面が表示されるので、ログインします。
起動したばかりだと画像データのインデックスが作成されていないので、GUIから作成します。
PhotoPrismでインデックスを作成
画像データが多いとそれだけ時間がかかります、気長に待ちましょう。
インデックスが作成されると、場所やラベルから写真を検索できるようになります。
PhotoPrismにおける場所検索
PhotoPrismにおけるラベル検索
さすがにGoogleフォトほど使いやすくはありませんね…🫠
🌕終わりに
今回はNextcloud/PhotoPrismでフォトストレージ(オンプレミス)を構築しました。
「外出先で写真撮影して、ホテルのWi-Fiに接続したら、画像データがフォトストレージに同期される」という目標も達成できましたし、システム構成やネットワーク構成を考える時間、Dockerで試行錯誤する時間も今振り返ればとても楽しいものでした。
AmazonやGoogleのフォトストレージを利用する方が圧倒的に楽ですが、誰かの役に立てれば嬉しいです。
皆様のますますのご活躍と、健やかなDockerライフを心よりお祈り申し上げます。
新宿で食べた煮干し中華そば
Discussion