TL;DR
- GitHub Actionsでブログ投稿を完全自動化し、毎日の手動作業をゼロにする方法を解説
- 冪等性・コスト管理・エラーハンドリングの3つを押さえれば、個人開発でも安定運用できる
- 無料枠2000分/月の中で余裕を持って運用するための具体的なテクニックを紹介
The Pain: CI/CDで「動かない」よくある3つの罠
ローカルで完璧に動くスクリプトも、クラウド(GitHub Actions)に上げると動かないことがあります。筆者も実際にこのブログの自動投稿パイプラインを構築する過程で、以下の問題に何度もつまずきました。
- タイムゾーンの問題: 投稿日時がずれる。JSTのつもりがUTCで動いていた、というのは定番の落とし穴
- 環境変数の不備: APIキーが見つからない。ローカルの
.envに頼りきりだとCI上で即エラー - 実行時間の超過: 無料枠(2000分/月)を使い切ってしまう。特にAI生成を組み合わせるとレスポンス待ちが長引くケースも
CI/CDパイプラインの遅さや不安定さに悩んでいる方は、CIが遅い原因を分解する ― 並列化・キャッシュ・テスト分割で待ち時間を半分にする方法も合わせて参考にしてください。
The Solution: 賢いWorkflow設計
ブログ自動化において重要なのは「冪等性(何度実行しても同じ結果になること)」と「無駄の排除」です。
この2つを実現するために、以下の3つの柱でWorkflowを設計します。
- Cron Schedule: 定期実行のトリガー設定。毎日決まった時間に自動で記事を生成・投稿する
- Environment Secrets: APIキーの安全な管理。GitHub Secretsを使い、コードに秘密情報を含めない
- Cost Saving: 変更がないときは処理をスキップする。無駄な実行を省くことで無料枠を守る
AI生成とCIを組み合わせる際の品質担保
AIエージェントが生成したコンテンツをそのまま公開するのはリスクがあります。CI上で品質ゲートを設けることで、一定基準を満たさない記事の自動公開を防止できます。具体的なリスクベースCI設計については、AI生成PRの品質ゲートを自動化する:リスクベースCI設計で詳しく解説しています。
The Implementation: Workflow定義
以下は、このブログを支える .github/workflows/auto-post.yml の構成例です。
name: Auto Post Article
on:
schedule:
# 毎日 日本時間の朝9時 (UTC 0:00)
- cron: "0 0 * * *"
workflow_dispatch: # 手動実行用
jobs:
post-article:
runs-on: ubuntu-latest
timeout-minutes: 5 # 暴走防止
env:
TZ: "Asia/Tokyo" # タイムゾーン固定
GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}
NOTION_TOKEN: ${{ secrets.NOTION_TOKEN }}
NOTION_DATABASE_ID: ${{ secrets.NOTION_DATABASE_ID }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: 9
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 20
cache: "pnpm"
- name: Install Dependencies
run: pnpm install --frozen-lockfile
- name: Generate & Post
run: |
# ドライランで記事生成
CONTENT=$(node scripts/generate_gemini.mjs '{"keyword":"最新テック"}')
# Notionへ投稿
node scripts/post_notion.mjs "$CONTENT"
コスト管理のテクニック
GitHub Actionsの無料枠は月2000分です。毎日5分走らせても 5 * 30 = 150分 なので十分余裕がありますが、以下の工夫でさらに節約できます。
- timeout-minutes: 必ず設定しましょう。無限ループなどの事故で数百分を浪費するのを防ぎます
- Cache:
setup-nodeのキャッシュ機能を使い、pnpm installの時間を短縮します - Skip Logic: もし「今日は書くネタがない」と判定器が返した場合は、後続ステップを実行しないように
if条件をつけます
Skip Logic の実装には「副作用なしで判定結果だけを返す専用スクリプト」を別途用意するのが安全です。以下は擬似コードで、実在スクリプト名はプロジェクトに合わせて置換してください。
# Skip Logic の実装例(擬似コード)
- name: Check content availability
id: check
run: |
# 副作用なしで {"hasContent": true/false} だけを stdout に返す
# 専用スクリプトを用意する(dry-run モードや stdout-only モード推奨)
RESULT=$(node scripts/check_content_availability.mjs --keyword "最新テック")
HAS_CONTENT=$(echo "$RESULT" | jq -r '.hasContent')
echo "skip=$([ "$HAS_CONTENT" = "true" ] && echo "false" || echo "true")" >> "$GITHUB_OUTPUT"
- name: Generate and publish article
if: steps.check.outputs.skip != 'true'
run: node scripts/generate_gemini.mjs --keyword "最新テック"
ポイント:
- 判定と生成を分離する(判定ステップは副作用なしにする)
GITHUB_OUTPUTにマルチライン値を渡す場合は delimiter 構文(echo "key<<EOF" >> $GITHUB_OUTPUT)を使う- 記事本文や JSON のような複雑なデータは output 経由ではなく一時ファイル経由で受け渡す
コスト管理の指標として何を追うべきか迷ったら、開発生産性指標の歩き方:チームに合った「定規」の選び方がCI/CDの計測観点でも参考になります。
The Takeaway: サーバーレスな執筆チーム
これで、以下のサイクルが完成しました。
- Notion API (A-1): 落ちない投稿スクリプト
- Robust Parser (A-2): 崩れない生成ロジック
- GitHub Actions (A-3): 止まらない定期実行
サーバーを一切管理することなく、APIとGitHubだけで完結する「サーバーレスなAI執筆チーム」。これが個人開発におけるブログ運用の最適解の一つです。
さらにこの仕組みをAIエージェントで発展させたい方は、以下の記事も参考にしてください。
- AIエージェントによるブログ自動運用の教科書 -- マルチエージェントで戦略的コンテンツ生成を実現する全体像
- Claude Codeで「高品質」なブログを量産する自動化パイプラインの作り方 -- 品質ガバナンスを組み込んだ量産パイプラインの具体的な構築手順
次のシリーズでは、このAIチームの「知能」部分、つまりエージェントの制御手法(SDD)について深掘りします。
