Penpotのフォント管理の闇を断つ:セルフホスト環境におけるカスタム日本語フォントの完全制覇
テックリードの皆さん、日々のデザインシステム運用の裏側で、こんな「闇」に直面していないだろうか。
SaaS版ではなくDocker等でPenpotを完全セルフホストしている。セキュリティ要件もクリアし、社内ネットワーク内で爆速で動いている。しかし、いざデザインシステムを構築し、カスタム日本語フォント(Inter, Noto Sans JP, あるいは社内独自のコーポレートフォント)を適用しようとした瞬間、地獄の扉が開く。
- 「ローカル環境では表示されるのに、別のメンバーのPCやブラウザから見るとフォントがフォールバック(MS Gothicやメイリオ)に化ける」
- 「SVGエクスポートやPDFプレビューを走らせた途端、日本語が豆腐(□)の群れに変わる」
- 「CSSやコンポーネントをコードに落とし込む際、デザイン上のフォントウェイト(400, 500, 700など)と、レンダリング結果が完全にズレる」
クローズドなセルフホスト環境において、フォント管理は単なる「装飾の問題」ではない。デザインとコードの乖離を防ぎ、チーム全体の開発ベロシティを死守するためのインフラストラクチャーである。
今回は、Penpotのコンテナアーキテクチャの内部構造まで踏み込み、カスタムWebフォントをセルフホスト環境に完全同期させ、文字化けやレンダリングの闇を永遠に断つための実践的知見を授けよう。
—
1. なぜセルフホストPenpotのフォント設定は「闇」なのか?
SaaS版のPenpotであれば、クラウド側があらかじめ用意した豊富なフォントアセットやGoogle Fontsとの連携が自動的に処理される。しかし、セルフホスト(Docker Compose等)環境では、コンテナの孤立性が牙をむく。
Penpotはフロントエンド(`penpot-frontend`)とバックエンド(`penpot-backend`)、そしてSVGを画像等に変換するレンダラー(`penpot-exporter`)がそれぞれ独立したコンテナとして稼働している。つまり、「自分が使っているブラウザにフォントが入っているか」だけではなく、「Penpotのバックエンドおよびエクスポーターコンテナの内部ファイルシステムに、対象のフォントファイル(WOFF2/TTF)が正しくマウントされ、認識されているか」がすべてなのだ。
このパイプラインのどこか一つでも不整合があると、画面上は綺麗に見えても、エクスポート時や別環境での同期時に必ず破綻する。
—
2. 実践:カスタムフォントをセルフホストPenpotへ完全同期する手順
ここからは、Docker環境(Docker Compose)を前提として、独自の日本語フォント(例として `Inter` および `Noto Sans JP`、または社内用カスタムフォント)をPenpotにインジェクトし、チーム全体で完全共有する手順を解説する。
Step 1: フォントアセットの準備とコンテナへのマウント
まず、ホストマシン側にフォント格納用のディレクトリを作成し、必要なウェイトのフォントファイル(推奨:`.woff2`形式)を配置する。`.woff2`は圧縮率が高く、Web環境でのパフォーマンスに優れる。
プロジェクトのルート(`docker-compose.yml`がある階層)に以下のようなディレクトリを切る。
.
├── docker-compose.yml
└── fonts/
├── NotoSansJP-Regular.woff2
├── NotoSansJP-Medium.woff2
└── NotoSansJP-Bold.woff2
次に、`docker-compose.yml`を修正し、`penpot-frontend` と `penpot-exporter`(存在する場合)に対して、このローカルの `fonts` ディレクトリをコンテナ内のフォント認識パスへボリュームマウントする。
設定ファイルのベストプラクティス (`docker-compose.yml`)
version: ‘3.8’
services:
# … 他のサービス設定(db, redisなど)は省略 …
penpot-frontend:
image: penpotapp/frontend:latest
ports:
- “8080:8080”
environment:
- PENPOT_PUBLIC_URI=http://localhost:8080
volumes:
# ホスト側のfontsディレクトリをコンテナ内のカスタムフォント領域へマウント
- ./fonts:/usr/share/fonts/truetype/custom-fonts:ro
depends_on:
- penpot-backend
penpot-backend:
image: penpotapp/backend:latest
environment:
- PENPOT_DATABASE_URI=postgresql://…
- PENPOT_REDIS_URI=redis://…
volumes:
# エクスポートやバックエンド処理での文字化けを防ぐため同様にマウント
- ./fonts:/usr/share/fonts/truetype/custom-fonts:ro
penpot-exporter:
image: penpotapp/exporter:latest
volumes:
# SVGやPDF変換時の文字化け(豆腐現象)を防ぐための必須マウント
- ./fonts:/usr/share/fonts/truetype/custom-fonts:ro
Step 2: フォントキャッシュの更新とコンテナ内認識の確認
コンテナを再ビルド・起動したら、バックエンドおよびエクスポーターコンテナ内でフォントキャッシュが正しく生成されているかを確認する。これが抜けると、Penpotのフォントピッカーにカスタムフォントがリストアップされない。
コンテナにアタッチ
docker exec -it penpot-backend /bin/sh
フォントキャッシュの強制的リフレッシュ(Debian/Ubuntuベースの場合)
fc-cache -f -v
正しく認識されているか確認
fc-list :lang=ja
このコマンドの出力結果に `Noto Sans JP` が含まれていれば、システムレベルでのインジェクションは成功している。
—
3. Penpot設定とチーム共有のベストプラクティス
コンテナ側の準備が整ったら、次はPenpotのUI上、およびデザインシステムとしての設定だ。ここで属人性を排除し、チーム全員が同じフォント環境をシームレスに使えるようにする。
チーム開発におけるフォント設定の共有ルール
1. ローカルPCへのフォントインストールを強制しない(セルフホストの利点を活かす)
デザイナーやフロントエンドエンジニアが各自のPCにフォントを手動インストールする運用は今すぐ廃止する。バージョン違いによるレンダリング崩れの温床になる。Penpotのセルフホストサーバー側でWebフォントとして配信・制御するフローを徹底する。
2. フォントウェイトの命名規則をコードと完全一致させる
デザインシステム側のスタイル定義(CSS変数 / Design Tokens)と、Penpot上のスタイル名を完全に同期させる。
実用的なデザイントークン(JSON)の構成例
チーム間でデザインシステムをコードに同期させる際、以下のJSONを共通のリポジトリで管理し、Style Dictionary等を通じてCSS/SCSSへ自動変換する。
{
“$schema”: “https://raw.githubusercontent.com/amazon-faber/style-dictionary/main/schemas/style-dictionary.schema.json”,
“font”: {
“family”: {
“primary”: { “value”: “‘Noto Sans JP’, sans-serif”, “comment”: “セルフホスト環境の標準日本語フォント” },
“mono”: { “value”: “‘JetBrains Mono’, monospace”, “comment”: “コード・数値用フォント” }
},
“weight”: {
“regular”: { “value”: “400” },
“medium”: { “value”: “500” },
“bold”: { “value”: “700” }
}
}
}
—
4. プロの隠し武器:開発スピードを最大化するキーボードショートカット&プラグイン
テックリードとして、チーム全体のオペレーションコストを極限まで下げるために叩き込んでおくべきPenpotのショートカットと、ワークフローを加速させるアプローチを紹介する。
開発スピードを劇的に高めるキーボードショートカット(Linux / macOS共通)
マウスオペレーションを排除し、コードを書くようなスピードでレイアウトを組み上げるための必須キーマップ。
| ショートカット (Mac / Win/Linux) | 動作・機能 | テックリードの解説 |
| :— | :— | :— |
| `Shift + A` | Auto Layoutの適用 / 解除 | コンポーネント構造化の生命線。要素のラップやパディング調整を瞬時に行う。 |
| `Option + Drag` (Mac) / `Alt + Drag` (Win) | オブジェクトの複製 | デザイントークンやテキストスタイルのバリエーション展開に必須。 |
| `Cmd + Shift + L` (Mac) / `Ctrl + Shift + L` | ロック / アンロックのトグル | 複雑なレイアウトで背景やベースグリッドの誤選択を防ぐ。 |
| `1` / `2` / `3` / `4` | ズーム操作 (`1`: 全体表示, `2`: 選択範囲, `3`: 50%, `4`: 100%) | フォントのディテールやカーニングを確認する際の手動ズームのロスをゼロにする。 |
セルフホスト環境で入れるべき「神プラグイン・拡張アプローチ」
SaaS版とは異なり、セルフホスト環境では外部ストアとの連携に制限がある場合がある。しかし、Penpotのオープンソースとしての強みを活かし、以下のワークフローを導入することで開発効率は跳ね上がる。
1. Penpot Exporter CLI (自製スクリプトによる自動化)
Penpotの公式APIを叩き、デザイン上のテキストスタイルやフォント定義の変更を検知して、前述の `fonts/` やデザイントークンJSONを自動でCI/CDパイプライン上で同期する社内スクリプト(Node.js/Python)を組み込む。これにより、デザイナーがフォントを更新した瞬間、エンジニアの手元にもそれが反映される仕組みが完成する。
—
5. ありがちなトラブルシューティングと処方箋
最後に、セルフホスト環境のフォント運用で必ずと言っていいほど遭遇するトラブルと、その即効性のある解決策を提示する。
トラブルA:画面上は日本語が表示されるが、SVG/PDFエクスポート時に文字が消失・化ける
- 原因: ブラウザのレンダリングエンジン(Blink / WebKit)はシステムフォントを機転で補完してくれるが、サーバーサイドの `penpot-exporter` コンテナには該当フォントのメタデータやグリフ(文字グリフ情報)がロードされていない。
- 処方箋:
1. `docker-compose.yml` において、`penpot-exporter` サービスに対して必ず `fonts/` ディレクトリがボリュームマウントされているか確認する。
2. コンテナ内で `fc-cache -fv` がビルド時に実行されるよう、カスタムDockerfileを作成してイメージをラップする。
カプセル化したカスタムDockerfileの例
FROM penpotapp/exporter:latest
ホスト側からビルド時にフォントをコンテナ内に焼き込む場合
COPY ./fonts/ /usr/share/fonts/truetype/custom-fonts/
RUN fc-cache -f -v
トラブルB:チームメンバー間でフォントのウェイト(太さ)が異なって見える
- 原因: 各自がローカル環境に持っている同名フォント(例: 過去にインストールした古い Noto Sans JP)のバージョンが競合し、CSSのフォントマッチング優先順位がローカル側で優先されてしまっている。
- 処方箋:
Penpotのプロジェクト設定、および対応するフロントエンドのCSSにおいて、フォントファミリ名を一意のカスタム名(例: `CompanyCustomSans`)として定義し、フォントファイルのメタデータ自体を書き換えてからセルフホストサーバーに投入する(FontForge等を使用)。これにより、ローカル環境のフォントノイズを完全に遮断できる。
—
結びに代えて
セルフホストによるPenpotの運用は、データ主権やネットワークの最適化において最強の選択肢だ。しかし、フォントという「表現の根幹」において妥協すると、デザインとコードの間に目に見えない亀裂を生み出すことになる。
コンテナのレイヤーを理解し、フォントアセットのパイプラインをインフラストラクチャーとしてコード管理下に置くこと。それこそが、チーム全体の生産性を極限まで高める、真のテックリードの仕事である。
さあ、今すぐ `docker-compose.yml` を開き、そのフォントの闇を断ち切れ。