0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【第1部】LINEグループ専用フォトアルバムを自宅サーバーで作った話 ~構想からMinIOローカルサーバー構築まで~

0
Last updated at Posted at 2026-03-23

はじめに

子どもたちのスポーツチームで写真を共有するのに、LINEアルバムの50枚制限に悩まされた経験はありませんか?

1試合で数百枚の写真を撮影するのに、LINEアルバムは50枚しか入らない。Googleフォトへのリンクを送っても「ログインが面倒」「使い方がわからない」という声が。結局、LINEのトークに写真をバラバラに送って流れていく…。

このフラストレーションから、「LINEグループのメンバーだけが使える、大容量フォトアルバム」 を作ることにしました。

本記事は3部構成の第1部です。

内容
第1部(本記事) 構想からMinIOローカルサーバー構築まで
第2部 Cloudflare Tunnelでルーター穴あけ不要のトンネリング
第3部 UI改善とセキュリティ強化

完成イメージ

最終的にこんなシステムを作りました:

  • LINEグループメンバー限定でアクセス可能
  • 1アルバム最大5000枚の写真・動画を共有
  • PCからドラッグ&ドロップで一括アップロード
  • 高機能フォトビューアー(ピンチズーム、Exif情報、Google Maps連携)
  • LINEへのFlex Message共有
  • 自宅サーバー(MinIO)による低コスト大容量ストレージ
レンタルサーバー (Web/DB)
    ├── PHP API + Vue.js フロントエンド
    ├── MySQL (メタデータ)
    └── SQLite (サムネイル BLOB保存)

自宅サーバー (MinIO + imgproxy)
    ├── MinIO (S3互換オブジェクトストレージ)
    ├── imgproxy (画像リサイズ・変換)
    └── Cloudflare Tunnel (外部公開)

なぜこの技術スタックになったのか

制約条件

プロジェクト開始時のリアルな制約:

  1. レンタルサーバーが既にある(SSHなし、Composerなし、PHP 8.x + MySQL利用可)
  2. 予算はほぼゼロ(月額数百円のレンタルサーバーのみ)
  3. ユーザーの技術力を問わない(LINEを開くだけで使えること)
  4. 大容量ストレージが必要(1イベント500枚×年間数十イベント)

自然と決まった構成

  • LINE LIFF (LINE Front-end Framework):ユーザーにログイン画面を見せず、LINEアプリから直接起動
  • Vue.js (CDN版):ビルド環境不要でレンタルサーバーにそのまま設置可能
  • PHP (フレームワークなし):レンタルサーバーの制約でComposerが使えない
  • MinIO (自宅サーバー):S3互換の無料オブジェクトストレージ

最初は全ファイルをレンタルサーバーに置く想定でしたが、inode制限(30万ファイル) という壁にぶつかり、ストレージを分離する設計に変わっていきました。

第1部:怒涛の初期構築(Day 1〜4)

Day 1:初期コミット — まず動くものを

2026-01-10 Initial commit: LINE Group Photo Sharing System

最初のコミットで既に35ファイル、3,930行のコードを一気に投入しました。AI(生成AI)の力を借りて、基本骨格を一日で構築。

初日に実装したもの:

  • アルバム一覧・詳細・ビューアー機能
  • ドラッグ&ドロップによる写真・動画アップロード
  • 選択モードによる一括削除・一括ダウンロード
  • データベース設計とストレージ処理
  • ドキュメント整備
// src/storage.php - 最初はシンプルなローカルストレージだった
class LocalStorage {
    private string $basePath;
    
    public function store(string $path, $data): bool {
        $fullPath = $this->basePath . '/' . $path;
        // ファイルをそのままディスクに保存
        return file_put_contents($fullPath, $data) !== false;
    }
}

この時点ではまだ「全部レンタルサーバーで完結させる」つもりでした。

Day 1(深夜):機能の肉付け

2026-01-10 23:01 アルバムのドラッグ&ドロップ並び替え (SortableJS統合)
                  アルバム名変更機能
                  包括的なExif情報表示 (GANREF形式準拠)

カメラ好きのメンバーがいるので、Exif情報の表示は最初から作り込みました。カメラ・レンズ情報、撮影設定(F値、SS、ISO)、GPS情報からのGoogle Maps連携まで。

Day 2〜3:共有機能の拡張と現実のバグとの戦い

2026-01-11 共有機能の拡張とバグ修正
2026-01-12 動画アップロード/再生の不具合修正
2026-01-12 アルバムプレビュー反映の修正とUI/UX改善
2026-01-12 UI改善: アルバム画像選択の統合と共有機能の強化

最初のつまずき:動画のハンドリング

画像は簡単だったのですが、動画は別でした。MIME判定、サムネイル生成、ストリーミング再生…想定外のエラーが次々と。特にモバイルでの再生は機種ごとの差異が大きく、苦労しました。

UI/UXのフィードバックサイクル

実際にメンバーに使ってもらうと、「追加」が何の追加かわからないという声が。「写真を追加」に変更。ボタンのレイアウトもモバイルで崩れていたので修正。

📝 学び: 専門用語を避け、アイコン+テキストで表現する。LINEの共有ボタンはロゴの緑色を使うべき。

Day 3:サムネイル非同期生成 — Docker Workerの導入

2026-01-12 サムネイル非同期生成の強化とUI改善
           - バックグラウンドでサムネイル生成を行うワーカーコンテナ(Docker)を追加

大量の写真をアップロードすると、サムネイル生成でタイムアウトする問題が発生。解決策としてDocker Workerコンテナを導入し、バックグラウンドでサムネイルを生成する非同期アーキテクチャに変更。

# docker/docker-compose.yml - Workerサービスの追加
services:
  worker:
    build: .
    volumes:
      - ../:/var/www/html
    command: >
      bash -c "while true; do
        php /var/www/html/public/cron/generate_thumbnails.php;
        sleep 5;
      done"

アップロード完了後、ブラウザ側ではポーリング(3秒間隔)でサムネイルの生成状況を確認し、できたものから順次表示していく仕組みに。

Day 3(大きな決断):Docker環境の分離

2026-01-12 22:24 Docker環境の再構築: アプリサーバーとストレージサーバーの分離

ここでアーキテクチャの大きな転換点が訪れます。

レンタルサーバーのinode制限を調べた結果、写真1枚につきサムネイル+プレビュー+オリジナル for 少なくとも3ファイル消費する計算で、数万枚でinode制限に到達することがわかりました。

解決策:ストレージを自宅サーバーに分離する

[Before] 一体型
  docker-compose.yml → app + db + minio (全部一緒)

[After] 分離型
  docker/docker-compose.yml      → app + db + worker
  homeserver/docker-compose.yml  → minio (独立)
  共有: line_photo_net ネットワーク

この分離により:

  • 開発環境が本番構成を正確に模倣できるようになった
  • ストレージの拡張が独立して可能に
  • 将来的にストレージサーバーだけ物理マシンに移せる

Day 4:MinIO署名付きURL — セキュリティの要

2026-01-13 MinIO署名付きURLアクセス実装

MinIOのバケットはプライベートに設定し、Presigned URL(署名付きURL) でアクセスする方式を採用。

// Presigned URLの概念
// 一定時間だけ有効な、署名付きのアクセスURLを生成
$presignedUrl = $storage->getPresignedUrl(
    'photo-bucket',
    'albums/123/photo.jpg',
    '+1 hour'  // 1時間で無効化
);
// → https://minio.example.com/photo-bucket/albums/123/photo.jpg
//    ?X-Amz-Algorithm=AWS4-HMAC-SHA256
//    &X-Amz-Expires=3600
//    &X-Amz-Signature=abc123...

これにより:

  • ブラウザからMinIOに直接アクセスでき、アプリサーバーの転送量負荷を軽減
  • URLには有効期限があり、漏洩しても時間が経てば無効化される
  • 動画のストリーミング再生も署名付きURLで実現

Day 4〜5:MinIOセットアップガイドの整備

2026-01-14 MinIO/レンタルサーバーセットアップガイド追加、環境変数をMINIO_に統一

環境変数の命名を S3_* から MINIO_* に統一。1,100行以上のセットアップドキュメントを作成。

なぜドキュメントにこだわったか?それは**「忘れる前の自分へのメモ」** です。自宅サーバーの構築は一度やると数ヶ月触らないことがザラ。次にトラブルが起きたときに、自分のドキュメントに助けられる未来が見えていました。

# 作成したドキュメント群
docs/
├── minio_setup/
│   ├── setup_guide.md         # 298行の詳細手順
│   ├── operations_guide.md    # 運用ガイド
│   ├── security_guide.md      # セキュリティ設定
│   ├── backup_restore.md      # バックアップ手順
│   ├── monitoring_guide.md    # 監視設定
│   └── incident_response.md   # インシデント対応
└── レンタルサーバー_setup/
    └── setup_guide.md          # レンタルサーバーへのデプロイ手順

Day 6〜7:imgproxy導入 — 画像変換の切り札

2026-01-16 imgproxy導入とUI機能強化
           - imgproxyの完全導入 (ARM64ローカルビルド, S3プロトコル対応)
           - セキュリティ強化: HMAC-SHA256署名付きURLの実装

サムネイル生成をPHPのGDライブラリで行っていましたが、処理速度と品質に限界が。そこでimgproxyを導入。

imgproxyは、URLベースで画像のリサイズ・フォーマット変換を高速に行えるオープンソースの画像処理サーバーです。

つまずきポイント:ARM64でのビルド

当時の自宅サーバー候補だったARM64マシンでは、imgproxyの公式Dockerイメージがそのまま使えず、ローカルビルドが必要でした。

# ARM64用のビルド
FROM golang:1.21-bullseye AS builder
# ... libvips のビルドから始まる長い道のり

MinIOとimgproxyを連携させ、S3プロトコル経由で画像を取得・変換するパイプラインを構築。

HMAC-SHA256署名URL: imgproxyへの不正なリクエストを防ぐため、署名付きURLを実装。勝手なサイズの画像を生成されるリソース消費攻撃を防止。

Day 7〜8:削除システムと管理機能

2026-01-16 アルバム削除機能
2026-01-16 遅延削除システムの実装とストレージ管理の強化
2026-01-17 バグ修正 - 管理者ゴミ箱機能、MinIO削除、チャンクアップロード

誤操作による写真消失を防ぐため、ゴミ箱方式(論理削除→30日後に物理削除) を実装。管理者は「ゴミ箱を空にする」で即時削除も可能。

MinIOローカルサーバーの全体像

第1部の終わりに、この時点でのアーキテクチャをまとめます:

この時点での課題

  1. 外部アクセス: 自宅サーバーにインターネットからアクセスする方法が未解決
  2. ルーターのポート開放: セキュリティリスクが高い
  3. 動的IP: 固定IPサービスを契約するのはコスト的に避けたい

→ 第2部では、Cloudflare Tunnelでこれらの課題を全て解決します。

まとめ

第1部で達成したこと

  • LINEグループ認証と連携したフォトアルバムの基本機能
  • MinIOによるS3互換ストレージの構築
  • imgproxyによる高速画像変換パイプライン
  • 署名付きURLによるセキュアなファイルアクセス
  • Docker開発環境(アプリ/ストレージの分離構成)

使った技術

レイヤー 技術
フロントエンド Vue.js 3 (CDN), Tailwind CSS, LIFF SDK
バックエンド PHP 8.x (Vanilla), MySQL 8.0
ストレージ MinIO (S3互換), SQLite (サムネイル)
画像処理 imgproxy (ARM64ビルド)
インフラ Docker Compose (分離構成)

AIアシスタント活用のポイント

本プロジェクトでは構想段階からAIコーディングアシスタントを積極的に活用しました。

  • 初期コミットで3,900行: 基本骨格をAIと一緒に一日で構築
  • ドキュメント作成: セットアップガイドやアーキテクチャ図もAIが下書きし、実情に合わせて修正
  • バグの特定: 「動画サムネイルが生成されない」→ 原因特定と修正をAIと対話しながら実施

ただし、AIは万能ではありません。レンタルサーバー固有の制約(inode制限、SSH不可)や、ARM64でのビルドの細かいハマりポイントは、結局自分で調べて解決する必要がありました。


第2部では、この自宅サーバーを安全にインターネットに公開するためのCloudflare Tunnelの導入と、大容量ファイルのアップロード対応について書きます。

📮 第2部:Cloudflare Tunnelでルーター穴あけ不要のトンネリング
📮 第3部:UI改善とセキュリティ強化 (近日公開)

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?