Penpot本番スケールアウト戦略:Redisセッション共有とWebSocket安定稼働の完全実録
チームの規模が拡大し、デザインシステムが全社の単一情報源(SSOT)として機能し始めると、ローカルや単一サーバー(Docker Composeのデフォルト構成)でのPenpot運用には必ず限界が訪れる。開発者、デザイナー、プロダクトマネージャーが同時にキャンバスにアクセスし、リアルタイムでコンポーネントをいじる現場において、サーバーのダウンタイムやセッションロストは致命傷だ。
本稿では、Penpotをマルチノード環境へスケールアウトさせ、高負荷に耐えうる堅牢なプロダクション環境を構築するための実践知を、インフラストラクチャとデザインプロセスの双方を知り尽くしたテックリードの視点から余すところなく解説する。
—
1. なぜ「単一コンテナ」の限界が訪れるのか
公式のDocker Compose構成は、開発や小規模検証には最適だが、本番運用では以下のボトルネックに直面する。
- セッションの局所化: 複数コンテナ構成(ロードバランサー配下)にした際、ユーザーの認証状態(JWTやセッションCookie)が特定のコンテナのメモリ上にのみ保持されていると、リクエストのたびにログアウトさせられる「セッション迷子」が発生する。
- WebSocketの切断: Penpotの真骨頂であるリアルタイム・コラボレーションは、Erlang/Elixir(Backend)ベースのWebSocket通信に依存している。ステートフルなコネクションを維持するため、ロードバランサー側での適切なSticky SessionまたはIP Hash、そしてタイムアウト設定が不可欠となる。
これを解決するのが、Redisによるセッションの外部化と、複数バックエンドノードの水平分散(スケールアウト)だ。
—
2. アーキテクチャ全体像
[ Users / Clients ]
│
▼
┌──────────────┐
│ Load Balancer│ (Nginx / ALB: WebSocket Upgrade 対応)
└──────┬───────┘
├────────────────────────┐
▼ ▼
┌──────────────┐ ┌──────────────┐
│ Penpot Node 1│ │ Penpot Node 2│ (Elixir Backend / Clojure Frontend)
└──────┬───────┘ └──────┬───────┘
│ │
└───────────┬────────────┘
▼
┌──────────────┐
│ Redis Cluster│ (Session Store & Pub/Sub)
└──────┬───────┘
▼
┌──────────────┐
│ PostgreSQL │ (Database)
└──────────────┘
—
3. 実践:Redisを活用したセッション共有とスケールアウト設定
ここからは、実際に本番環境へ投入するための設定ファイルを公開する。
環境変数(`.env`)と、ロードバランサー(Nginx)の構成がキモとなる。
A. 環境変数 (`.env.production`) のベストプラクティス
Penpotのバックエンド(`penpot-backend`)に対し、Redisの接続情報を正しくルーティングする。
==========================================
Database Configuration
==========================================
PENPOT_DATABASE_URI=postgresql://penpot:secret_password@postgres-cluster.internal:5432/penpot
PENPOT_DATABASE_POOL_MAX=20
==========================================
Redis Configuration (セッションおよびPub/Sub共有の要)
==========================================
Redisを単体またはSentinel構成で指定
PENPOT_REDIS_URI=redis://:redis_password@redis-cluster.internal:6379/0
==========================================
Server & Public URIs
==========================================
PENPOT_PUBLIC_URI=https://design.company.internal
PENPOT_TELEMETRY_ENABLED=false
複数ノード間で暗号化キーを統一(セッション復号の整合性を担保)
PENPOT_SECRET_KEY=super_secure_random_string_of_at_least_64_bytes_here
B. Docker Composeによる複数バックエンド構成 (`docker-compose.scale.yml`)
1つのフロントエンドに対して、バックエンド(`penpot-backend`)を複数台立ち上げ、Redisを介して状態を同期させる構成だ。
version: ‘3.8’
services:
penpot-frontend:
image: penpotapp/frontend:latest
restart: always
ports:
- “8080:8080”
environment:
- PENPOT_FLAGS=enable-registration
depends_on:
- penpot-backend-1
- penpot-backend-2
# バックエンドノード 1
penpot-backend-1:
image: penpotapp/backend:latest
restart: always
environment:
<<: penpot-backend-env
PORT: 6060
depends_on:
- redis
- postgres
# バックエンドノード 2(水平スケール用)
penpot-backend-2:
image: penpotapp/backend:latest
restart: always
environment:
<<: penpot-backend-env
PORT: 6060
depends_on:
- redis
- postgres
redis:
image: redis:7-alpine
restart: always
command: redis-server –requirepass redis_password
volumes:
- redis_data:/data
volumes:
redis_data:
共通環境変数の定義アンカー
x-backend-env: &penpot-backend-env
PENPOT_DATABASE_URI: postgresql://penpot:secret_password@postgres/penpot
PENPOT_REDIS_URI: redis://:redis_password@redis:6379/0
PENPOT_SECRET_KEY: “super_secure_random_string_of_at_least_64_bytes_here”
—
4. ロードバランサー(Nginx)設定:WebSocket安定稼働の急所
Penpotのリアルタイム同期を死守するためには、Nginxのリバースプロキシ設定において、HTTPアップグレード(WebSocket)のヘッダー転送とタイムアウトの延長が絶対条件となる。
以下の設定ファイルをロードバランサーに適用せよ。
events {
worker_connections 1024;
}
http {
upstream penpot_backends {
# セッションの負荷分散(必要に応じてip_hashも検討可能だがRedis共有によりround-robinで動作)
server penpot-backend-1:6060;
server penpot-backend-2:6060;
keepalive 32;
}
server {
listen 80;
server_name design.company.internal;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
server_name design.company.internal;
# SSL証明書設定(省略)
# ssl_certificate /path/to/cert.pem;
# ssl_certificate_key /path/to/key.pem;
client_max_body_size 100M; # アセットアップロード(画像等)の制限緩和
location / {
proxy_pass http://penpot-frontend; # フロントエンドコンテナへ
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# バックエンドAPIおよびWebSocketへのルーティング
location /api/ {
proxy_pass http://penpot_backends;
proxy_http_version 1.1;
# WebSocket Upgrade 必須設定
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection “upgrade”;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# リアルタイム通信が途切れないようタイムアウトを長く設定
proxy_read_timeout 86400s;
proxy_send_timeout 86400s;
}
}
}
—
5. 現場の生産性を爆発させる「Penpot」プロフェッショナル設定
インフラが整ったところで、次は「デザイン・開発の現場」における生産性最大化の知見を共有する。Penpotを単なる「Figmaのオープンソース版」として使っているうちは、ツールの真価を引き出せていない。
🚀 開発スピードを劇的に高める隠れたキーボードショートカット
コンポーネント指向でUIを組み立てるエンジニアが覚えるべき、操作の高速化コマンド:
- `Shift + R`: ルーラーの表示/非表示(レイアウトグリッドとの整合性確認)
- `Alt + ドラッグ (要素間)`: 正確なCSSフレックスボックス・グリッドを想定したマージン・パディングの測定
- `Ctrl + G` / `Ctrl + Shift + G`: グループ化 / グループ解除(コンポーネント化前の構造整理)
- `F`: フレームの作成(レスポンシブデザインの起点)
🧩 チーム開発で絶対に導入すべき運用ルール
1. デザインシステムのSSOT(Single Source of Truth)化
- カラーパレット、タイポグラフィ、スペーシングトークンは、必ずPenpotの「Libraries(ライブラリ)」機能を通じてチーム全体で共有する。
- トークン名は、Tailwind CSSやCSS Variables(カスタムプロパティ)の命名規則と完全に一致させること(例: `color-primary-500`, `spacing-base`)。これにより、デザイナーからエンジニアへのハンドオフが「目視による数値入力」から「トークンの参照」へと劇的に進化する。
2. コンポーネントの命名規則の厳格化
- アトミックデザインに基づき、プレフィックスを徹底する。
- `atom/` (ボタン、インプット等)
- `molecule/` (検索バー、カード等)
- `organism/` (ヘッダー、フッター等)
—
6. まとめ:セルフホスト運用の要諦
Penpotのセルフホスト環境におけるRedisを活用したスケールアウトは、単に「サーバーを2つに増やす」という作業ではない。それは、デザインとコードの境界線をシームレスにつなぎ、チーム全体の開発アジリティを担保するためのインフラストラクチャ・デザインそのものである。
本稿で解説したRedisによるセッション共有、適切なWebSocketプロキシ設定、そしてコンポーネント駆動の運用ルールを組織にインストールし、ストレスフリーでスケーラブルなプロダクト開発環境を手に入れてほしい。