はじめに
子どもたちのスポーツチームで写真を共有するのに、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 (外部公開)
なぜこの技術スタックになったのか
制約条件
プロジェクト開始時のリアルな制約:
- レンタルサーバーが既にある(SSHなし、Composerなし、PHP 8.x + MySQL利用可)
- 予算はほぼゼロ(月額数百円のレンタルサーバーのみ)
- ユーザーの技術力を問わない(LINEを開くだけで使えること)
- 大容量ストレージが必要(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部の終わりに、この時点でのアーキテクチャをまとめます:
この時点での課題
- 外部アクセス: 自宅サーバーにインターネットからアクセスする方法が未解決
- ルーターのポート開放: セキュリティリスクが高い
- 動的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改善とセキュリティ強化 (近日公開)