<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Status on AI2CORE - AI技術ブログ</title>
    <link>https://www.ai2core.com/categories/status/</link>
    <description>Recent content in Status on AI2CORE - AI技術ブログ</description>
    <generator>Hugo -- 0.146.4</generator>
    <language>ja</language>
    <lastBuildDate>Wed, 18 Feb 2026 19:15:00 +0900</lastBuildDate>
    <atom:link href="https://www.ai2core.com/categories/status/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>【運営報告】ブログ自動化システムの安定化に向けた取り組み</title>
      <link>https://www.ai2core.com/posts/2026-02-18-status-update/</link>
      <pubDate>Wed, 18 Feb 2026 19:15:00 +0900</pubDate>
      <guid>https://www.ai2core.com/posts/2026-02-18-status-update/</guid>
      <description>自動投稿システムの不具合と、リポジトリ再構築による解決策について。</description>
      <content:encoded><![CDATA[<h1 id="運営報告ブログ自動化システムの安定化に向けた取り組み">【運営報告】ブログ自動化システムの安定化に向けた取り組み</h1>
<h2 id="はじめにその自動化本当に自動ですか">はじめに：その自動化、本当に「自動」ですか？</h2>
<p>「ブログの自動投稿システムを組んだけど、なぜか時々ビルドに失敗する…」
「CI/CDパイプラインがエラーで止まるたびに、原因究明に数十分も費やしている…」
「最初はシンプルだったのに、機能を追加していくうちにリポジトリがカオスになってきた…」</p>
<p>もしあなたが個人ブログや技術ドキュメントサイトをGitHub Actionsなどで自動化していて、このような悩みを抱えているなら、この記事はあなたのためのものです。</p>
<p>こんにちは。当ブログを運営している筆者です。私もかつて、まさにこの問題の渦中にいました。Markdownで記事を書いてGitHubにプッシュすれば、あとは魔法のようにサイトが更新される──そんな夢のような自動化システムを構築したはずが、いつしかそれは「時々機嫌を損ねる気難しい同居人」のような存在になっていました。依存関係のエラー、ローカルとCI環境での挙動の違い、肥大化したワークフローファイル…。これらは、自動化による恩恵を帳消しにするほどのストレスと時間的損失をもたらしていました。</p>
<p>この記事では、私が直面したブログ自動化システムの不安定化問題と、その根本原因を深く掘り下げ、<strong>リポジトリの再構築とビルド環境のコンテナ化</strong>というアプローチでいかにして安定稼働を実現したか、その全貌を余すところなくお伝えします。</p>
<p>この記事を読み終える頃には、あなたは以下の知識とテクニックを手にしているはずです。</p>
<ul>
<li>不安定なCI/CDパイプラインの根本原因を診断するための着眼点</li>
<li>モノレポとマルチレポの考え方を適用した、メンテナンス性の高いリポジトリ設計</li>
<li>Dockerを活用して「どこでも同じように動く」ビルド環境を構築する方法</li>
<li>堅牢で再利用性の高いGitHub Actionsワークフローを設計するための具体的なベストプラクティス</li>
<li>技術的負債と向き合い、システムを長期的に健全な状態に保つためのマインドセット</li>
</ul>
<p>単なる対症療法ではない、根本からのシステム改善に興味のある方は、ぜひ最後までお付き合いください。</p>
<h2 id="なぜブログ自動化システムは不安定になったのか---課題の深掘り">なぜブログ自動化システムは不安定になったのか？ - 課題の深掘り</h2>
<p>解決策を語る前に、まずは私のブログシステムがどのような問題を抱えていたのか、その背景と原因を詳しく見ていきましょう。問題を正しく理解することが、正しい解決策への第一歩です。</p>
<h3 id="当初のシステム構成">当初のシステム構成</h3>
<p>私のブログは、多くの技術ブログで採用されているであろう、ごく一般的な構成でした。</p>
<ul>
<li><strong>コンテンツ管理</strong>: Markdownファイル</li>
<li><strong>静的サイトジェネレーター(SSG)</strong>: Hugo</li>
<li><strong>ソースコード管理</strong>: GitHub</li>
<li><strong>CI/CD</strong>: GitHub Actions</li>
<li><strong>ホスティング</strong>: GitHub Pages</li>
</ul>
<p>この構成における自動投稿の基本的な流れは、以下の図のようになります。</p>
<pre tabindex="0"><code class="language-mermaid" data-lang="mermaid">graph TD
    A[記事(Markdown)をpush] --&gt; B{GitHub Actions};
    B --&gt; C[Hugoでビルド];
    C --&gt; D[GitHub Pagesへデプロイ];
</code></pre><p>非常にシンプルで、最初はこれで何の問題もありませんでした。しかし、ブログ運営を続けるうちに、様々な機能を追加したくなり、システムは徐々に複雑化していきました。そして、以下の3つの大きな問題が顕在化したのです。</p>
<h3 id="問題1依存関係地獄-dependency-hell">問題1：依存関係地獄 (Dependency Hell)</h3>
<p>「私のローカル環境ではちゃんとビルドできるのに、なぜかGitHub Actions上では失敗する」。この現象に、あなたも見覚えがないでしょうか。これは、開発環境と実行環境の間に存在する「差異」が原因で発生します。</p>
<p>私の場合、具体的には以下のような問題に悩まされていました。</p>
<ul>
<li><strong>Hugoのバージョン不整合</strong>: ローカルで使っているHugoのバージョンと、GitHub ActionsのワークフローでセットアップされるHugoのバージョンが微妙に異なり、テンプレートの仕様変更などでビルドエラーが発生する。</li>
<li><strong>Node.js依存ツールのバージョン問題</strong>: 私が使っていたHugoテーマは、CSSのトランスパイルにSass（Dart Sass）を利用しており、これはNode.jsに依存していました。ローカルのNode.js/npmバージョンとCI環境のバージョンが異なると、<code>npm install</code>が失敗したり、Sassのコンパイル結果が変わってしまったりしました。</li>
<li><strong>Go Modulesの混乱</strong>: HugoはGoで書かれているため、テーマによってはGo Modules (<code>go.mod</code>, <code>go.sum</code>) を利用します。CI環境のGoのバージョンが古いと、これもまたエラーの原因となりました。</li>
</ul>
<p>これらのバージョンを<code>package.json</code>やワークフローファイルで固定しようと試みましたが、複数の依存関係が絡み合うと管理が非常に煩雑になり、根本的な解決には至りませんでした。</p>
<h3 id="問題2ワークフローの肥大化と複雑化">問題2：ワークフローの肥大化と複雑化</h3>
<p>当初はHugoでビルドしてデプロイするだけだったGitHub Actionsのワークフローファイルは、時を経て巨大な怪物へと変貌していました。</p>
<ul>
<li><strong>記事のリンク切れをチェックするジョブ</strong></li>
<li><strong>画像形式をWebPに変換し、最適化するジョブ</strong></li>
<li><strong>SEOのために構造化データを検証するジョブ</strong></li>
<li><strong>サイトマップを自動生成して検索エンジンに通知するジョブ</strong></li>
</ul>
<p>これらの便利な機能を次々と追加した結果、一つのYAMLファイルが数百行にも及び、誰にも全体像が把握できない状態になってしまいました。</p>
<p>この「モノリシック・ワークフロー」は、以下のような弊害を生み出しました。</p>
<ul>
<li><strong>可読性の低下</strong>: ワークフロー全体の流れを追うのが困難で、新しいジョブを追加したり、既存のジョブを修正したりするのが怖い。</li>
<li><strong>デバッグの困難さ</strong>: どこか一つのジョブが失敗すると、他の無関係なジョブまで影響を受け、デプロイ全体が停止してしまう。エラーの原因特定にも時間がかかりました。</li>
<li><strong>実行効率の悪化</strong>: 単に記事のタイポを一行修正しただけのpushでも、画像最適化やリンクチェックなど、時間のかかる全てのジョブが毎回実行され、CIのリソースと時間を無駄に消費していました。</li>
</ul>
<h3 id="問題3モノリシックなリポジトリ構造">問題3：モノリシックなリポジトリ構造</h3>
<p>最大の問題は、これら全ての要素が<strong>単一のリポジトリ</strong>に混在していることでした。</p>
<ul>
<li>記事のMarkdownファイル (<code>content/</code>)</li>
<li>Hugoのソースコードと設定ファイル (<code>layouts/</code>, <code>static/</code>, <code>hugo.toml</code>)</li>
<li>カスタマイズしたテーマのコード (<code>themes/my-theme/</code>)</li>
<li>自動化スクリプトやワークフローファイル (<code>.github/workflows/</code>)</li>
<li>Node.jsの依存関係ファイル (<code>package.json</code>, <code>node_modules/</code>)</li>
</ul>
<p>この「何でもアリ」なリポジトリ構造は、<strong>関心の分離</strong>というソフトウェア設計の基本原則に反しており、メンテナンス性を著しく低下させていました。</p>
<p>例えば、記事を執筆するライター（私自身ですが）は、本来Markdownファイルのことだけを気にしていれば良いはずです。しかし、この構造では、HugoのビルドロジックやCIの設定ファイルまで目に入ってしまい、誤って変更してしまうリスクがありました。</p>
<p>逆に、サイトのデザインを修正したい場合、テーマのCSSファイルを変更するだけなのに、記事コンテンツも一緒に管理されているため、リポジトリが肥大化し、クローンや操作が重くなっていました。</p>
<p>これらの問題が絡み合い、私のブログ自動化システムは、もはや「自動」と呼ぶには程遠い、手のかかる不安定な代物になってしまったのです。</p>
<h2 id="解決策リポジトリの再構築とcicdパイプラインの刷新">解決策：リポジトリの再構築とCI/CDパイプラインの刷新</h2>
<p>問題の根源が見えてきました。それは突き詰めると、**「環境の不一致」<strong>と</strong>「関心の分離の欠如」**という、2つの古典的な課題に集約されます。</p>
<p>この根本原因を解消するために、私は以下の2つの大きな方針を立て、システムの全面的な再構築に踏み切りました。</p>
<ol>
<li><strong>ビルド環境のコンテナ化 (Docker)</strong>: 開発環境とCI環境の差異を撲滅し、完全な再現性を確保する。</li>
<li><strong>リポジトリの分割 (マルチレポ戦略)</strong>: 関心事ごとにリポジトリを分割し、それぞれの責務を明確にする。</li>
</ol>
<p>ここからは、この2つの方針に基づいた具体的な改善策を、コードや図を交えて詳細に解説していきます。</p>
<h3 id="1-dockerによるビルド環境の再現性確保">1. Dockerによるビルド環境の再現性確保</h3>
<p>「ローカルでは動くのにCIではコケる」問題を撲滅する最も確実な方法は、ローカルとCIで<strong>全く同じ環境</strong>を使うことです。これを実現するのがDockerです。</p>
<p>私は、Hugo、Node.js、その他ビルドに必要なツールをすべて含んだカスタムDockerイメージを作成することにしました。これにより、ビルド環境そのものをコード（Dockerfile）として管理できるようになります。</p>
<h4 id="dockerfileの作成">Dockerfileの作成</h4>
<p>以下が、私のブログ用に作成した<code>Dockerfile</code>です。Hugoの拡張版公式イメージをベースに、Node.jsや必要なツールを追加しています。</p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">13
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">14
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">15
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">16
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">17
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">18
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">19
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">20
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">21
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">22
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">23
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">24
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">25
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">26
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">27
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">28
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">29
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">30
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">31
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">32
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">33
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">34
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-dockerfile" data-lang="dockerfile"><span style="display:flex;"><span><span style="color:#75715e"># ベースイメージにはHugoの公式拡張版イメージを利用</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#75715e"># ARGでバージョンを外部から指定できるようにしておく</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#66d9ef">ARG</span> HUGO_VERSION<span style="color:#f92672">=</span><span style="color:#ae81ff">0</span>.125.4<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#66d9ef">FROM</span><span style="color:#e6db74"> klakegg/hugo:${HUGO_VERSION}-ext-alpine</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#75715e"># ビルドに必要なツールをインストール</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#75715e"># alpineベースなのでapkコマンドを使用</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#66d9ef">RUN</span> apk add --no-cache nodejs npm git<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#75715e"># 作業ディレクトリを設定</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#66d9ef">WORKDIR</span><span style="color:#e6db74"> /src</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#75715e"># package.jsonとpackage-lock.jsonを先にコピーする</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#75715e"># これにより、ソースコードが変更されても、依存関係が変わらなければ</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#75715e"># `npm ci`のレイヤーはキャッシュが利用され、ビルドが高速化する</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#66d9ef">COPY</span> package.json package-lock.json ./<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#75715e"># npm ciで依存関係を厳密にインストール</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#66d9ef">RUN</span> npm ci<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#75715e"># プロジェクトのソースコード全体をコピー</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#66d9ef">COPY</span> . .<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#75715e"># hugoコマンドでビルドを実行</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#75715e"># --minifyオプションで生成されるファイルを圧縮</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#66d9ef">RUN</span> hugo --minify<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#75715e"># --- 以下はローカル開発用の設定 ---</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#75715e"># ポートを公開 (hugo server用)</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#66d9ef">EXPOSE</span><span style="color:#e6db74"> 1313</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#75715e"># デフォルトのコンテナ起動コマンド</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#75715e"># ローカルでhugo serverを起動する際に使用</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#66d9ef">CMD</span> [<span style="color:#e6db74">&#34;hugo&#34;</span>, <span style="color:#e6db74">&#34;server&#34;</span>, <span style="color:#e6db74">&#34;-D&#34;</span>, <span style="color:#e6db74">&#34;--bind&#34;</span>, <span style="color:#e6db74">&#34;0.0.0.0&#34;</span>, <span style="color:#e6db74">&#34;--baseURL&#34;</span>, <span style="color:#e6db74">&#34;http://localhost:1313/&#34;</span>]<span style="color:#960050;background-color:#1e0010">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>このDockerfileのポイントは、<code>npm ci</code>をソースコード全体の<code>COPY</code>より前に実行している点です。これにより、Dockerのレイヤーキャッシュが効率的に機能し、依存関係に変更がない限り、<code>npm ci</code>は再実行されず、イメージビルドの時間を短縮できます。</p>
<h4 id="ローカル開発環境での利用">ローカル開発環境での利用</h4>
<p>ローカルでの執筆・プレビュー時にもこのDockerイメージを使うことで、環境の差異を完全になくします。そのために<code>docker-compose.yml</code>を用意します。</p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">13
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">14
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#f92672">version</span>: <span style="color:#e6db74">&#39;3.8&#39;</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">services</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">hugo</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># カレントディレクトリのDockerfileを使ってイメージをビルド</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">build</span>:
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">context</span>: <span style="color:#ae81ff">.</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">dockerfile</span>: <span style="color:#ae81ff">Dockerfile</span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># ホストマシンのカレントディレクトリをコンテナの/srcにマウント</span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># これにより、ローカルでファイルを編集すると即座にコンテナ内に反映される</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">volumes</span>:
</span></span><span style="display:flex;"><span>      - <span style="color:#ae81ff">.:/src</span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># ホストの1313番ポートをコンテナの1313番ポートにフォワーディング</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">ports</span>:
</span></span><span style="display:flex;"><span>      - <span style="color:#e6db74">&#34;1313:1313&#34;</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>この設定により、ターミナルで<code>docker-compose up</code>というコマンドを一つ実行するだけで、ローカルにHugoやNode.jsがインストールされていなくても、誰でも全く同じ開発環境を起動できるようになりました。</p>
<p>これで、「環境の不一致」という最大の問題は解決です。</p>
<h3 id="2-マルチレポ戦略による関心の分離">2. マルチレポ戦略による関心の分離</h3>
<p>次に、「関心の分離の欠如」という問題に取り組みます。私は、巨大化したモノリシックなリポジトリを、責務に基づいて以下の3つのリポジトリに分割しました。</p>
<ol>
<li><code>blog-contents</code>: <strong>記事のMarkdownファイルのみ</strong>を管理するリポジトリ。執筆活動の拠点です。</li>
<li><code>blog-theme</code>: Hugoのテーマ、サイト設定 (<code>hugo.toml</code>)、ビルド設定 (<code>Dockerfile</code>, <code>package.json</code>など)、GitHub Actionsワークフローなど、<strong>サイトの骨格とビルドロジック</strong>を管理するリポジトリ。</li>
<li><code>blog-deploy</code>: Hugoが生成した静的ファイル（HTML, CSS, JS）を格納し、<strong>GitHub Pagesで公開する</strong>ためのリポジトリ。</li>
</ol>
<p>この新しい構成を図にすると、以下のようになります。</p>
<pre tabindex="0"><code class="language-mermaid" data-lang="mermaid">graph TD
    subgraph &#34;執筆リポジトリ (blog-contents)&#34;
        A[記事(Markdown)をpush]
    end

    subgraph &#34;テーマ/ビルド リポジトリ (blog-theme)&#34;
        B[Dockerfile]
        C[hugo.toml]
        D[テーマファイル]
        W[.github/workflows/deploy.yml]
    end

    subgraph &#34;デプロイリポジトリ (blog-deploy)&#34;
        F[生成されたHTML/CSS/JS]
    end

    A -- トリガー --&gt; E{GitHub Actions};
    E -- ワークフロー定義の読み込み --&gt; W;
    E -- 1. blog-themeをチェックアウト --&gt; D;
    E -- 2. blog-contentsをチェックアウト --&gt; A_content[Markdown];
    E -- 3. Dockerイメージビルド --&gt; B;
    E -- 4. Hugoビルド実行 --&gt; G[ビルド処理];
    G -- 5. 生成物をpush --&gt; F;

    F --&gt; H[GitHub Pages];
</code></pre><p>この構成の肝は、GitHub Actionsのワークフローです。<code>blog-contents</code>リポジトリへのpushをトリガーに、<code>blog-theme</code>リポジトリで定義されたワークフローが実行されます。ワークフローは、<code>blog-theme</code>自身と<code>blog-contents</code>の両方をチェックアウトし、<code>blog-theme</code>内のDockerfileを使ってビルドを実行。最後に、生成物を<code>blog-deploy</code>リポジトリにプッシュします。</p>
<h4 id="新しいgithub-actionsワークフロー">新しいGitHub Actionsワークフロー</h4>
<p>以下は、この新しい構成を実現するための<code>blog-theme</code>リポジトリに配置するワークフローファイル (<code>.github/workflows/deploy.yml</code>) の例です。</p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">13
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">14
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">15
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">16
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">17
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">18
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">19
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">20
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">21
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">22
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">23
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">24
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">25
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">26
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">27
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">28
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">29
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">30
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">31
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">32
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">33
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">34
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">35
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">36
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">37
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">38
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">39
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">40
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">41
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">42
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">43
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">44
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">45
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">46
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">47
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">48
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">49
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">50
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">51
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">52
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">53
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">54
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">55
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">56
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">57
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">58
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">59
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">60
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">61
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">62
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#f92672">name</span>: <span style="color:#ae81ff">Build and Deploy Blog</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">on</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#75715e"># blog-contentsリポジトリのmainブランチへのpushをトリガーにする</span>
</span></span><span style="display:flex;"><span>  <span style="color:#75715e"># repository_dispatchイベントを利用</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">repository_dispatch</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">types</span>: [<span style="color:#ae81ff">build-blog]</span>
</span></span><span style="display:flex;"><span>  <span style="color:#75715e"># 手動実行も可能にする</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">workflow_dispatch</span>:
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">jobs</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">build-and-deploy</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">runs-on</span>: <span style="color:#ae81ff">ubuntu-latest</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">steps</span>:
</span></span><span style="display:flex;"><span>      <span style="color:#75715e"># Step 1: テーマとビルドロジックのリポジトリをチェックアウト</span>
</span></span><span style="display:flex;"><span>      - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">Checkout theme and build repository</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">uses</span>: <span style="color:#ae81ff">actions/checkout@v4</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">with</span>:
</span></span><span style="display:flex;"><span>          <span style="color:#f92672">repository</span>: <span style="color:#ae81ff">your-username/blog-theme</span>
</span></span><span style="display:flex;"><span>          <span style="color:#f92672">path</span>: <span style="color:#ae81ff">blog-theme</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>      <span style="color:#75715e"># Step 2: 記事コンテンツのリポジトリをチェックアウト</span>
</span></span><span style="display:flex;"><span>      - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">Checkout contents repository</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">uses</span>: <span style="color:#ae81ff">actions/checkout@v4</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">with</span>:
</span></span><span style="display:flex;"><span>          <span style="color:#f92672">repository</span>: <span style="color:#ae81ff">your-username/blog-contents</span>
</span></span><span style="display:flex;"><span>          <span style="color:#f92672">path</span>: <span style="color:#ae81ff">blog-contents</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>      <span style="color:#75715e"># Step 3: Dockerイメージをビルド</span>
</span></span><span style="display:flex;"><span>      <span style="color:#75715e"># キャッシュを活用してビルドを高速化</span>
</span></span><span style="display:flex;"><span>      - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">Set up Docker Buildx</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">uses</span>: <span style="color:#ae81ff">docker/setup-buildx-action@v3</span>
</span></span><span style="display:flex;"><span>      - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">Build Docker image</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">uses</span>: <span style="color:#ae81ff">docker/build-push-action@v5</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">with</span>:
</span></span><span style="display:flex;"><span>          <span style="color:#f92672">context</span>: <span style="color:#ae81ff">./blog-theme</span>
</span></span><span style="display:flex;"><span>          <span style="color:#f92672">load</span>: <span style="color:#66d9ef">true</span>
</span></span><span style="display:flex;"><span>          <span style="color:#f92672">tags</span>: <span style="color:#ae81ff">blog-builder:latest</span>
</span></span><span style="display:flex;"><span>          <span style="color:#f92672">cache-from</span>: <span style="color:#ae81ff">type=gha</span>
</span></span><span style="display:flex;"><span>          <span style="color:#f92672">cache-to</span>: <span style="color:#ae81ff">type=gha,mode=max</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>      <span style="color:#75715e"># Step 4: Dockerコンテナ内でHugoビルドを実行</span>
</span></span><span style="display:flex;"><span>      - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">Build Hugo site</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">run</span>: |<span style="color:#e6db74">
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">          docker run --rm \
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">            -v $(pwd)/blog-contents:/src/content \
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">            -v $(pwd)/public:/src/public \
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">            blog-builder:latest</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>      <span style="color:#75715e"># Step 5: 生成された静的ファイルをデプロイリポジトリにプッシュ</span>
</span></span><span style="display:flex;"><span>      - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">Deploy to GitHub Pages</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">uses</span>: <span style="color:#ae81ff">peaceiris/actions-gh-pages@v3</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">with</span>:
</span></span><span style="display:flex;"><span>          <span style="color:#f92672">github_token</span>: <span style="color:#ae81ff">${{ secrets.GITHUB_TOKEN }}</span>
</span></span><span style="display:flex;"><span>          <span style="color:#75715e"># デプロイ先のリポジトリとブランチを指定</span>
</span></span><span style="display:flex;"><span>          <span style="color:#f92672">external_repository</span>: <span style="color:#ae81ff">your-username/blog-deploy</span>
</span></span><span style="display:flex;"><span>          <span style="color:#f92672">publish_branch</span>: <span style="color:#ae81ff">main</span>
</span></span><span style="display:flex;"><span>          <span style="color:#75715e"># デプロイディレクトリを指定</span>
</span></span><span style="display:flex;"><span>          <span style="color:#f92672">publish_dir</span>: <span style="color:#ae81ff">./public</span>
</span></span><span style="display:flex;"><span>          <span style="color:#75715e"># コミットユーザー情報を設定</span>
</span></span><span style="display:flex;"><span>          <span style="color:#f92672">user_name</span>: <span style="color:#e6db74">&#39;github-actions[bot]&#39;</span>
</span></span><span style="display:flex;"><span>          <span style="color:#f92672">user_email</span>: <span style="color:#e6db74">&#39;github-actions[bot]@users.noreply.github.com&#39;</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p><em>注: <code>repository_dispatch</code>を外部リポジトリからトリガーするには、<code>blog-contents</code>リポジトリ側でPAT（Personal Access Token）を使いAPIを叩くワークフローが別途必要になります。よりシンプルな構成としては、<code>blog-theme</code>リポジトリに<code>on.push</code>トリガーを設定し、<code>blog-contents</code>をGit Submoduleとして管理する方法も考えられます。</em></p>
<p>このリポジトリ分割により、関心事が明確に分離され、それぞれの役割に集中できる環境が整いました。</p>
<h2 id="改善によるメリットとデメリット">改善によるメリットとデメリット</h2>
<p>この大掛かりな再構築によって、何が良くなり、そしてどのような新たなトレードオフが生まれたのでしょうか。</p>
<h3 id="メリット">メリット</h3>
<ol>
<li><strong>絶大な安定性と再現性</strong>: Docker化により、「私のマシンでは動くのに」問題は完全に過去のものとなりました。CIは常に期待通りに動作し、ビルド失敗に悩まされる時間はゼロに近くなりました。</li>
<li><strong>劇的に向上したメンテナンス性</strong>:
<ul>
<li><strong>執筆者</strong>: Markdownを書くことだけに集中できます。ビルドの仕組みを意識する必要はありません。</li>
<li><strong>開発者</strong>: サイトのデザイン変更や機能追加は<code>blog-theme</code>リポジトリで完結します。記事コンテンツに影響を与える心配なく、大胆なリファクタリングも可能です。</li>
</ul>
</li>
<li><strong>効率化されたCI/CDパイプライン</strong>: Dockerレイヤーキャッシュの活用により、依存関係に変更がない限りビルドは高速です。また、記事の更新とテーマの更新で関心が分離されているため、不要なジョブが実行されることもありません。</li>
<li><strong>将来の拡張性 (スケーラビリティ)</strong>: もし将来、HugoからAstroやNext.jsのような別のSSGに乗り換えたくなったとしても、<code>blog-contents</code>リポジトリには一切手を加える必要がありません。<code>blog-theme</code>リポジトリを新しい技術スタックで再構築するだけで移行が完了します。これは非常に大きな利点です。</li>
</ol>
<h3 id="デメリット">デメリット</h3>
<ol>
<li><strong>構成の複雑化</strong>: リポジトリが1つから3つに増え、全体のアーキテクチャを理解するための初期学習コストは確実に上がりました。新しいメンバーが参加した際の説明も、以前より少し手間がかかります。</li>
<li><strong>初期セットアップの手間</strong>: Dockerfileやdocker-compose.yml、リポジトリ間を連携させるためのGitHub Actionsワークフローの初期設定は、それなりに知識と時間を要します。</li>
<li><strong>リソース消費</strong>: Dockerイメージをビルド・保存するために、GitHub Actionsの実行時間や、GHCR (GitHub Container Registry) などのストレージ容量を消費します。小規模なブログでは過剰装備と感じるかもしれません。</li>
</ol>
<p>これらのデメリットは存在しますが、長期的な運用を見据えた場合、得られる安定性とメンテナンス性のメリットはそれを遥かに上回ると私は確信しています。</p>
<h2 id="現場で使える実践的なtips">現場で使える実践的なTips</h2>
<p>今回の再構築を通して得られた、さらに一歩進んだ知見やテクニックをいくつかご紹介します。</p>
<h3 id="tip-1-dependabotによる依存関係の自動更新">Tip 1: Dependabotによる依存関係の自動更新</h3>
<p>安定性を追求するあまり、各種ライブラリのバージョンを塩漬けにしてしまうのは良いプラクティスではありません。セキュリティ脆弱性に対応するためにも、依存関係は定期的に更新すべきです。
<code>blog-theme</code>リポジトリに<strong>Dependabot</strong>を設定しましょう。以下の<code>.github/dependabot.yml</code>をリポジトリに追加するだけで、Dockerfile内のHugoバージョンや、<code>package.json</code>内のnpmパッケージが古くなった場合に、自動で更新のプルリクエストを作成してくれます。</p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">13
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#f92672">version</span>: <span style="color:#ae81ff">2</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">updates</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#75715e"># Dockerfileのバージョンをチェック</span>
</span></span><span style="display:flex;"><span>  - <span style="color:#f92672">package-ecosystem</span>: <span style="color:#e6db74">&#34;docker&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">directory</span>: <span style="color:#e6db74">&#34;/&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">schedule</span>:
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">interval</span>: <span style="color:#e6db74">&#34;weekly&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#75715e"># npmの依存関係をチェック</span>
</span></span><span style="display:flex;"><span>  - <span style="color:#f92672">package-ecosystem</span>: <span style="color:#e6db74">&#34;npm&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">directory</span>: <span style="color:#e6db74">&#34;/&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">schedule</span>:
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">interval</span>: <span style="color:#e6db74">&#34;weekly&#34;</span>
</span></span></code></pre></td></tr></table>
</div>
</div><h3 id="tip-2-makefileで開発体験を向上させる">Tip 2: Makefileで開発体験を向上させる</h3>
<p><code>docker-compose up</code> や <code>docker exec ...</code> のようなコマンドを毎回入力するのは面倒です。<strong>Makefile</strong>を使って、よく使う操作をシンプルなコマンドにラップしましょう。</p>
<p><code>blog-theme</code>リポジトリのルートに以下のような<code>Makefile</code>を置きます。</p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">13
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">14
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">15
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">16
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">17
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">18
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">19
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">20
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">21
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-makefile" data-lang="makefile"><span style="display:flex;"><span><span style="color:#a6e22e">.PHONY</span><span style="color:#f92672">:</span> help setup server build clean
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">help</span><span style="color:#f92672">:</span>
</span></span><span style="display:flex;"><span>	@echo <span style="color:#e6db74">&#34;Usage: make [target]&#34;</span>
</span></span><span style="display:flex;"><span>	@echo <span style="color:#e6db74">&#34;targets:&#34;</span>
</span></span><span style="display:flex;"><span>	@echo <span style="color:#e6db74">&#34;  setup     Install dependencies&#34;</span>
</span></span><span style="display:flex;"><span>	@echo <span style="color:#e6db74">&#34;  server    Start development server&#34;</span>
</span></span><span style="display:flex;"><span>	@echo <span style="color:#e6db74">&#34;  build     Build static files for production&#34;</span>
</span></span><span style="display:flex;"><span>	@echo <span style="color:#e6db74">&#34;  clean     Remove generated files and node_modules&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">setup</span><span style="color:#f92672">:</span>
</span></span><span style="display:flex;"><span>	docker-compose build
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">server</span><span style="color:#f92672">:</span>
</span></span><span style="display:flex;"><span>	docker-compose up
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">build</span><span style="color:#f92672">:</span>
</span></span><span style="display:flex;"><span>	docker-compose run --rm hugo hugo --minify
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">clean</span><span style="color:#f92672">:</span>
</span></span><span style="display:flex;"><span>	rm -rf public resources node_modules
</span></span></code></pre></td></tr></table>
</div>
</div><p>これにより、<code>make server</code>で開発サーバーを起動、<code>make build</code>で本番用のビルドを実行、といった直感的な操作が可能になり、開発体験が大きく向上します。</p>
<h3 id="tip-3-プルリクエストでプレビュー環境を自動構築">Tip 3: プルリクエストでプレビュー環境を自動構築</h3>
<p><code>blog-contents</code>リポジトリに新しい記事のプルリクエストが作成された際、変更内容を実際のサイトで確認できるプレビュー環境が自動で立ち上がると、レビューが格段に捗ります。
Cloudflare PagesやVercel、Netlifyといったホスティングサービスは、このプレビューデプロイ機能に優れています。GitHub Actionsのワークフローを少し変更し、PRイベントをトリガーにしてこれらのサービスにデプロイするジョブを追加するだけで、魔法のようなプレビュー体験が実現できます。</p>
<h2 id="まとめ">まとめ</h2>
<p>今回は、不安定化したブログ自動化システムを、根本原因から見直して再構築した道のりをご紹介しました。</p>
<p>改めて、今回の取り組みの要点を振り返ります。</p>
<ul>
<li><strong>問題</strong>: システムの不安定性は、「環境の不一致」と「関心の分離の欠如」という2つの根深い問題に起因していた。</li>
<li><strong>解決策</strong>:
<ol>
<li><strong>Dockerによるビルド環境のコンテナ化</strong>で、完全な「再現性」を確保した。</li>
<li><strong>マルチレポ戦略によるリポジトリ分割</strong>で、「関心の分離」を徹底し、メンテナンス性を向上させた。</li>
</ol>
</li>
</ul>
<p>この取り組みから得られた最大の教訓は、<strong>自動化システム（CI/CDパイプライン）もまた、一つの重要なアプリケーションである</strong>ということです。「とりあえず動けばいい」という場当たり的な実装は、必ず将来の技術的負債となって自分に返ってきます。アプリケーションコードと同様に、クリーンな設計、リファクタリング、そして継続的な改善が不可欠なのです。</p>
<p>もし今、あなたの自動化システムが悲鳴を上げているなら、それはアーキテクチャを見直す絶好の機会かもしれません。この記事で紹介した「再現性の確保」と「関心の分離」という2つの原則が、あなたのシステムの安定化に向けた確かな道しるべとなることを願っています。</p>
<p>Happy Automating</p>
]]></content:encoded>
      <category>Status</category>
      <category>OpenClaw</category>
      <category>DevOps</category>
      <category>Troubleshooting</category>
    </item>
  </channel>
</rss>
