最初の目標はシンプルだった。Yohaku テーマと Mix Space Core を使って、長く書き続けられる個人サイトを作ることだ。
最終構成
現在の構成は次のとおり。
画像では省略しているが、意図的に分離していることが2つある。コンテンツは常に Mix Space の管理画面で更新し、インフラとソースコードは Git と CI で管理する。こうすることで、記事を書くたびにイメージを再ビルドする必要がなく、フロントエンドを変更したときだけリリース処理が走る。
現在の構成は次のとおり。
8cat.life:Yohaku フロントエンド。Tencent Cloud のライトサーバー上の Docker コンテナで稼働。api.8cat.life:Mix Space Core。Caddy がリバースプロキシとして Core サービスに接続。- Core の依存:PostgreSQL、Redis、オブジェクトストレージ COS。
- フロントエンドイメージ:CNB で Docker イメージをビルドし、Tencent Cloud のプライベートイメージレジストリ TCR にプッシュ。
- 自動デプロイ:CNB が Tencent Cloud の TAT を介してライトサーバー内の自動化エージェントを呼び出し、指定イメージをプルしてフロントエンドコンテナを再構築。
この設計の要点は「イメージのビルド」と「イメージの実行」を分離することだ。CNB がビルドを担当し、サーバーはビルド済みのバージョンをプルして実行するだけ。
試行錯誤の記録
1. 当初は EdgeOne Pages に直接デプロイするつもりだった
Yohaku は Next.js の SSR プロジェクトだ。EdgeOne Pages は Next.js を認識できるが、実際にビルドすると Next.js 16 の Turbopack/webpack プロセスがプラットフォームのメモリ制限で強制終了される。ログには SIGKILL と約 6 GiB のメモリ上限が見える。
ビルドを Turbopack から webpack に切り替えても終了は防げない。さらに EdgeOne のランタイムは、このプロジェクトの middleware/proxy との互換性に問題が出ることもあった。そこで、本番ブランチでルーティングを無理に書き換えてプラットフォームに合わせるのはやめた。
判断: EdgeOne は静的サイトや軽量フレームワークには向いているが、現状の Yohaku の SSR ビルド規模はこの経路に合わない。テスト用ブランチは残しておき、本番環境は Docker に切り替えた。
2. GitHub Actions ではビルドできるが、最終的なリリース経路には不向き
GitHub Actions で Yohaku のビルドは成功した。ソースコード、サブモジュール、Next.js のビルド自体に根本的な問題はないことが確認できた。
しかし、イメージを GHCR に置くと、Tencent Cloud のサーバーが越境イメージをプルするのが遅い。さらに GitHub Runner が直接 SSH でサーバーにログインしてリリースする方式は、未知の IP アラートや長期的な認証情報の管理問題を招く。
判断: GitHub はソースコードのメインリポジトリとして維持し、イメージのビルドとサーバーへのリリースは Tencent Cloud エコシステム内で完結させる。
3. CNB、TCR、TAT で自動デプロイを実現
最終的に CNB で Docker イメージをビルドし、成果物をプライベート TCR にプッシュした。イメージには不変のコミット SHA タグと latest の両方を使い、サーバーは実際には SHA タグをデプロイする。これで「サイトがどのコミットを実行しているか」を追跡できる。リリースは「サーバーにコードを再コンパイルさせる」のではなく、検証済みの完成イメージを取得させるものだ。
このステップでもいくつかハマりどころがあった。
- CNB が npm の国内ミラーを使うと、一部のパッケージがミラーサイトに未同期で 404 になる。こうしたパッケージは公式 npm registry にフォールバックさせる必要がある。
- Docker ビルド中の
git: not foundはインストール段階のフックによるもの。コンテナビルドには.gitディレクトリがないため、これは無害なノイズであり、ビルド失敗の原因ではない。 - TAT で最初は誤った CVM スタイルのインスタンス ID を使っていた。ライトサーバーでは実際には
lhins-...形式のインスタンス ID が必要。 - TAT の CAM 権限には
RunCommandに加えて、invocation とタスク状態の照会権限も必要。どちらかが欠けると「コマンドは送信されたがパイプラインは失敗する」という状態になる。
最終的に、CNB のビルド完了後に TAT を呼び出し、サーバーに次の処理を実行させる:指定 SHA のイメージをプルし、compose サービスを更新し、yohaku コンテナを再構築する。
コンテンツとテーマ設定
Mix Space の「ページ」は通常の CMS コンテンツであり、Yohaku のテーマファイルではない。そのため、フッターの /about、/about-site、/message の3つのリンクが最初 404 になった理由は単純だ。対応するページがまだ作成されていなかったのだ。
現在は作成済み:
- 私について:
/about - このサイトについて:
/about-site - メッセージ:
/message
一方、Yohaku のホームページとフッターのビジュアル設定は、管理画面の「コードスニペット」にある theme/shiro JSON にあり、プロジェクトルートの theme.json ではない。ここでホームページのタイトル、紹介文、一言、favicon、フッターリンクを設定する。
画像、favicon、メール
- 画像は Tencent Cloud の COS を使用。Core は現在 S3 接続設定を1セット使い、ブログ本文の画像とコメント画像は異なるオブジェクトパスプレフィックスで分離している。アクセスキーはそのバケットに必要な最小権限のみ付与。
- favicon は旧サイトから移行し、フロントエンドはブラウザで拡大縮小できるよう 512px の原図を提供。テーマに旧パスが残っている場合、フロントエンドは互換性のある形でこの高解像度リソースにフォールバックする。
- メール購読は Resend を使用。送信ドメインは
updates.8cat.lifeで、例えば送信元アドレスはnewsletter@updates.8cat.life。API キーは Core の管理画面設定にのみ保存され、フロントエンドやコードリポジトリには入らない。
まだ覚えておくべき制限
Google コメントログイン
Google OAuth のブラウザでの認可は完了できるが、サーバー側でも認可コードをトークンに交換するために oauth2.googleapis.com にアクセスする必要がある。Tencent Cloud サーバー内の Core コンテナからこのアドレスの 443 ポートへの接続がタイムアウトし、最終的に invalid_code が表示される。
これは Google のコールバック URL が不足しているわけではない。Google ログインを有効にするには、Core が安定して Google にアクセスできる HTTPS プロキシを設定する必要がある。現時点では、利用可能な GitHub ログインを残しておく方が適切だ。
import { createServer } from 'node:http'
const server = createServer() // [!code highlight]
server.listen(3000)
console.log('ready')