記事を1本足したいだけなのに、本番のデータベースをまるごと入れ替える。これが、地味にこわい作業です。手が滑れば公開中の記事も設定も巻き込みます。そこでこのブログでは、テーマと記事でデプロイの経路を分けました。記事はSSH越しにWP-CLIで1本ずつ足す方式です。実際に本番へ流している運用スクリプトごと、その手順を書きます。
結論:テーマはGit、記事はWP-CLI。本番デプロイの経路を分ける
答えはシンプルで、変更の性質でルートを2つに割ります。テーマ(PHPやCSS)はGitで管理してGitHub Actionsから自動配布。記事(DBの中身)はGitに載らないので、SSH+WP-CLIで個別に足す。全サイトを毎回コピーするのではなく、増えた記事だけを本番に「追記」するイメージです。これで既存の公開記事や設定に触れずに済みます。

なぜ全サイト移行(db import)を毎回やらないのか
ローカルと本番を同期する定番は、DBをまるごと書き出して取り込み、URLを置換する方法です。初回の移行ならこれで正解でした。ただ、すでに公開して運用が回っているサイトに毎回これをやるのは、リスクが釣り合いません。
本番のDBには、ローカルには無いものが増えていきます。あとから直した誤字、コメント、各種プラグインが書き込んだ設定。ローカルのDBで上書きすると、それらが吹き飛びます。wp search-replace でのURL置換もサイト全体に及ぶので、1箇所ミスれば影響は全ページです。記事を1本足すだけなら、その記事だけを触るのが素直でした。ここは慎重にいきたいところです。
実装①:SSHで本番に入りWP-CLIを叩く
まず本番サーバーにSSHで入ります。鍵認証にしておくと、無人のスクリプトからも呼べます。ホスト名やユーザー名は各自の環境のものに置き換えてください(以下はマスクした例です)。
# 鍵認証でSSH(ポートはXserverの標準 10022)
ssh -i ~/.ssh/deploy_key -p 10022 xsXXXXX@your-server.xsrv.jp
# サーバー側でWP-CLIが通るか確認
cd ~/your-site/public_html
wp core version # 例: 7.0
共有サーバーだと wp にパスが通っていないことがあります。その場合は ~/bin/wp のようにフルパスで呼ぶか、エイリアスを張っておくと楽でした。
実装②:記事を冪等にpublishする(slug存在チェック)
本番デプロイのスクリプトで一番大事にしたのが「何度流しても二重作成しない」ことです。バッチが途中で失敗して流し直すことは、普通に起きます。そこで wp post create の前に、同じ slug の記事が本番にあるか必ず確認し、あればスキップします。実際の関数がこれです。
publish() { # $1=slug $2=title $3=date $4=tags $5=本文html $6=featured
local existing
existing=$(wp post list --post_type=post --post_status=any
--name="$1" --field=ID)
if [ -n "$existing" ]; then
echo "SKIP $1 (既に本番に存在 ID=$existing)"; return 0 # ← 冪等の要
fi
local id
id=$(wp post create "$5" --post_type=post --post_status=publish
--post_title="$2" --post_name="$1" --post_date="$3"
--tags_input="$4" --porcelain)
id=$(echo "$id" | tr -cd '0-9') # porcelainのIDだけ取り出す
wp post term set "$id" category wordpress # カテゴリを設定
wp media import "$6" --post_id="$id" --featured_image # アイキャッチ
echo "PUBLISHED $1 -> ID $id"
}
--porcelain を付けると、作成した投稿IDだけが返ります。そのIDにカテゴリを結び付け、アイキャッチを wp media import --featured_image で登録するところまでを1つの関数にまとめました。流れを図にするとこうです。

実装③:画像の配置とローカルURLの相対化
本文に画像を含む記事では、2つ下ごしらえが要ります。1つは本文中のURL。ローカルの http://*.local のまま送ると本番で画像が割れます。転送前に、本文のURLをルート相対(/wp-content/uploads/…)に直しておきました。
もう1つは画像ファイルそのもの。uploads の年月フォルダに実ファイルを置きます。スクリプトでは、転送済みの画像を正規パスへコピーしてから記事を作りました。
place_img() { # $1=srcfile $2=dest-rel-path
dest="wp-content/uploads/$2"
mkdir -p "$(dirname "$dest")"
cp -f "$1" "$dest"
}
place_img "$IMG/diagram_01.png" "2026/07/diagram_01.png"
ちなみにアイキャッチは wp media import がメディアライブラリに登録してくれますが、本文中の画像は「実ファイルの配置+本文のURL」を自分でそろえる必要があります。ここを混同すると画像が割れるので、地味ですが分けて考えると安全でした。
公開後の検証とロールバック
本番デプロイは、流して終わりにしません。公開直後に curl でページを取り、ステータスと中身を確認します。
# HTTP 200 か、タイトル・画像が出ているか
curl -sI https://example.com/wordpress/your-slug/ | head -1
curl -s https://example.com/wordpress/your-slug/ | grep -o '<h1[^>]*>[^<]*' | head -1
wp rewrite flush # パーマリンクを最後にもう一度確定
問題があったときの戻し方も、経路ごとに決めておきました。テーマの不具合はGitで git revert すればActionsが正しい状態を配り直します。記事のほうは、作った投稿を wp post delete --force で消すか、いったん下書きに落として直します。全DBを触っていないので、戻す範囲もその記事だけです。
この方法が向かない場合
この「1本ずつ」方式にも、向かない場面があります。
- 初回のまるごと移行:ローカルで作り込んだサイトを一気に本番化するなら、素直にDB export/import + search-replace のほうが速いです
- 大量の既存記事を一括で直す:全記事の文字列を置換するような作業は、
wp search-replaceの出番です。1本ずつ足す方式とは目的が違います - WordPress管理画面で書く運用:本番で直接書いているなら、そもそもデプロイという発想が要りません
逆に、ローカルで検証してから本番へ小さく足していく運用には、この方式が合っています。同じテーマでの実装はオリジナルテーマを自作する記事やセキュリティ対策の記事に、ほかの実装は爆速開発・環境構築のカテゴリにまとめています。
まとめ
- 本番デプロイは、テーマ(Git+Actions)と記事(SSH+WP-CLI)で経路を分ける
- 稼働中サイトに db import を毎回やらない。記事は
wp post createで1本ずつ足す - slug の存在チェックで冪等化し、流し直しても二重作成しない
- 本文URLはルート相対化、画像は uploads へ配置、最後に rewrite flush
- 公開後は curl で確認。戻すのもその記事だけで済む
本番デプロイは、経路を分けて小さく安全にするだけで、日々の更新がぐっと気楽になります。こうした公開・運用のフローづくりを企業サイトで設計・構築する仕事は、運営元のNEORIVAでも承っています。手作業の本番反映に不安がある方は、気軽に相談してください。参考にした公式ドキュメントは wp post create、wp media import、wp search-replace です。