こんにちは!プロダクトデザインとフロントエンドの境界線を溶かし、開発者とデザイナーが同じ言語で対話できる世界を作っているエンジニアの先輩です。
デザインツールといえばFigmaがデファクトスタンダードになっていますが、オープンソースであり、完全にセルフホスト可能、かつSVGネイティブな次世代のデザイン・プロトタイピングツール「Penpot」の波に、そろそろ乗ろうとしている方も多いのではないでしょうか。
今回は、Penpotを自社サーバーやプライベートクラウドにDockerで立ち上げた際、多くのエンジニアが最初に直面する「画像アセットの保存先問題」を華麗に解決する極意をお伝えします。
これをマスターすれば、ローカルストレージの容量圧迫や、複数コンテナ(マルチインスタンス)環境での「画像が消える・同期されない」という悪夢から解放されますよ。さあ、一緒に深掘りしていきましょう!
—
なぜ、Penpotの画像ストレージを外部化する必要があるのか?
PenpotをDocker(Docker Compose)でサクッと立ち上げると、ユーザーがキャンバスにアップロードした画像やアセットは、デフォルトではコンテナ内のローカルファイルシステムに保存されます。
これの何が問題か、ピンときますか?
1. マルチインスタンス化の壁: チーム開発が活発になり、Penpotのバックエンド(`penpot-backend`)を負荷分散のために複数台にスケールアウトした瞬間、Aのコンテナにアップロードされた画像が、Bのコンテナから見えなくなるという「画像迷子現象」が発生します。
2. データ永続性とバックアップの複雑化: コンテナの破棄やマイグレーションのたびに、ローカルボリュームの退避と移行に怯えることになります。
3. ストレージコストの最適化: 成長するデザイン組織において、重い画像アセットをオブジェクトストレージに逃がすことは、インフラコストの観点からもマストな選択です。
これを解決するのが、AWS S3、またはS3互換APIを持つCloudflare R2へのストレージ移行です。
—
基礎知識:Penpotが求めるストレージの仕様
Penpotのバックエンドは、アセットの保存に Clojure 製の堅牢なアーキテクチャを採用しています。環境変数(Environment Variables)を数行書き換えるだけで、ストレージのバックエンドをローカルからS3互換のオブジェクトストレージへシームレスに切り替えることができるよう設計されています。
今回は、AWS S3はもちろん、エグレス料金(転送量料金)が無料でモダンなインフラとして大人気のCloudflare R2をターゲットにした設定をベースに解説します。
—
実践!S3/R2連携のステップ・バイ・ステップ
それでは、実際にPenpotのDocker環境(`docker-compose.yml`)をいじっていきましょう。
1. オブジェクトストレージ側の準備(Cloudflare R2の例)
- CloudflareダッシュボードからR2バケット(例: `penpot-assets-production`)を作成します。
- S3互換APIトークンを発行し、以下の3つを手元にメモします。
- `Access Key ID`
- `Secret Access Key`
- `Endpoint URL` (例: `https://
.r2.cloudflarestorage.com`)
2. `docker-compose.yml` の環境変数チューニング
Penpotのバックエンドコンテナ(通常は `penpot-backend`)に対して、ストレージ関連の環境変数を流し込みます。
以下の設定例を見てください。コメントで何を意味しているか丁寧に解説しています。
version: ‘3.8’
services:
# …(他のサービス:frontend, postgres, redisなどは省略)…
penpot-backend:
image: penpotapp/backend:latest
restart: always
ports:
- “6001:6001”
environment:
- PENPOT_PUBLIC_URI=https://design.yourcompany.com
- PENPOT_DATABASE_URI=postgresql://penpot:penpot@penpot-postgres/penpot
- PENPOT_REDIS_URI=redis://penpot-redis:6379
# ==========================================
# ここからがストレージ外部化のキモです!
# ==========================================
# ストレージプロバイダーを s3 に指定
- PENPOT_STORAGE_BACKEND=s3
# 接続先のS3互換エンドポイント (Cloudflare R2の場合はアカウントIDを含むURL)
- PENPOT_S3_ENDPOINT=https://
.r2.cloudflarestorage.com
# 作成したバケット名
- PENPOT_S3_BUCKET=penpot-assets-production
# 認証情報 (R2の場合はAPIトークンから発行された値)
- PENPOT_S3_ACCESS_KEY=
- PENPOT_S3_SECRET_KEY=
# リージョン (R2の場合は ‘auto’ でOKです)
- PENPOT_S3_REGION=auto
# パススタイルを使用するかどうか (R2やMinIOの場合は true が必須)
- PENPOT_S3_PATH_STYLE=true
depends_on:
- penpot-postgres
- penpot-redis
> 💡 プロのワンポイントアドバイス:
> Cloudflare R2やMinIOなどのS3互換ストレージを使う場合、`PENPOT_S3_PATH_STYLE=true` を指定し忘れると、名前解決のバグでうまくバケットにアクセスできず盛大にエラーを吐きます。ここ、テストに出るくらい重要です!
—
精度高い「HelloWorld的」動作確認の儀式
設定ファイルを書き換えたら、いよいよコンテナを再起動して魔法がうまくかかっているか確かめましょう。
1. コンテナの再ビルド&起動
docker-compose down
docker-compose up -d
2. ログのリアルタイム監視
バックエンドが正しく起動し、エラーなくS3/R2とハンドシェイクできているかをログで確認します。
docker-compose logs -f penpot-backend
- ここで `storage initialized with s3 backend` のようなログ(またはエラーが出ないこと)を確認できれば第一関門突破です。
3. UIからの実体テスト(HelloWorld)
- ブラウザでPenpotを開き、適当なワークスペースにログインします。
- アイコンや高解像度のPNG/JPEG画像をキャンバスにドラッグ&ドロップでアップロードしてみてください。
- アップロードが瞬時に完了し、デザインボード上に美しく表示されたら成功です。
- 仕上げとして、Cloudflare R2(またはAWS S3)のバケット管理画面をブラウザで覗いてみてください。 ランダムなハッシュ名で画像ファイルがストレージ上に直接アップロードされていれば、完全勝利です!
—
まとめ:スケーラブルなデザインインフラを手に入れよう
今回は、Penpotのセルフホスト環境における画像アセットの外部ストレージ移行と、マルチインスタンス化を見据えたアーキテクチャの極意を解説しました。
- デフォルトのローカルストレージ依存から脱却し、S3互換ストレージ(AWS / R2)へ切り替える。
- `docker-compose.yml` に数行の環境変数を正しくマッピングする。
- パススタイル(`path-style=true`)などの細かなハマりどころをクリアする。
これをマスターしておけば、今後チームメンバーが増えてPenpotのコンテナを冗長化(マルチインスタンス化)させても、デザインアセットが消える恐怖とは無縁になります。
開発者とデザイナーがストレスなくデザインのアイデアを爆発させられる環境を、あなたの手でぜひ構築してみてください。毎日のインフラ管理とプロトタイピング作業が、劇的に軽やかになりますよ!