サーバ上の開発環境を Claude Code で宣言的に構築・運用
概要
自分の開発サーバ(Ubuntu 24.04)を丸ごと Ansible で管理しています。Claude Code の実行環境、Docker、tmux、リバースプロキシなど、サーバ上の構成要素すべてを宣言的に定義し、ansible-playbook 一発で再現できる状態にしました。
この記事では、なぜ Ansible を選んだのか、Claude Code との組み合わせでどう機能したのかを振り返ります。
背景
これまでは開発サーバを手動でコマンドを打って変更を加えていくことがほとんどでしたが、あまりちゃんとメモを取らない場合が多く、サーバに対してどのような修正を加えたのかがわからなくなっていました。dotfiles などでの管理を試したこともありましたが、手間がかかるのでついついアドホックな変更を入れてしまい管理できなくなっていました。
今回、Claude Code と IaC ツールを組み合わせて、サーバへの変更をすべてコードで管理すればこれまで挫折していたサーバの管理をちゃんとできるのではないかと考えました。Claude Code はプランニング・実装・実行・デバッグを自律的に繰り返せます。ゴールを示すだけでほぼ自律的に変更が完了し、現在の状態から過去の変更を含めてすべてが git 上に記録として残るため、環境の見通しもよくなりました。
今のサーバの状態に対する疑問があった場合も、自分で調査せずに Claude Code に質問することで調べることができるようになっています。
何を作ったのか
1 台の開発サーバの構成を丸ごと IaC(Infrastructure as Code)化しました。エントリーポイントは setup.sh で、Ansible 未インストールの場合はインストールした上で playbook を実行します。
#!/bin/bash
set -e
if ! command -v ansible-playbook &> /dev/null; then
echo "Installing Ansible..."
sudo apt-get update
sudo apt-get install -y ansible
fi
CHECK_MODE=""
if [[ "$1" == "--check" ]]; then
CHECK_MODE="--check --diff"
echo "Running in check mode (dry run)..."
fi
cd "$(dirname "$0")"
ansible-playbook playbook.yml -i inventory/localhost.yml $CHECK_MODE
playbook は 以下のロールで構成されています。シークレット管理、SSH 鍵、各種ランタイム、Claude Code 本体、tmux設定、リバースプロキシまで、サーバに必要なものがすべて含まれています。
- name: Configure development server
hosts: localhost
become: no
roles:
- infisical
- github-ssh
- nodejs
- claude-code
- docker
- containerlab
- qemu
- tmux
- dev-tools
- claude-code-ui
- ttyd
- caddy
hosts: localhost かつ become: no がデフォルトです。SSH 越しのリモート管理ではなく、サーバ自身が自分自身を構成するセルフプロビジョニングの形を取っています。
なぜ Ansible なのか
「1 台のサーバの構成管理」という要件に対して、いくつかの選択肢を検討しました。
シェルスクリプト は最も手軽ですが、冪等性(何度実行しても同じ結果になること)を自前で担保する必要があります。条件分岐が増えるにつれて保守性が下がり、ロールが10以上の規模では現実的ではありません。
Docker / コンテナ はアプリケーション単位の隔離には優れています。ただ、systemd サービスの管理やホストレベルの設定など、サーバ全体の構成管理には向きません。実際にいくつかのサービスは Docker で動かしていますが、その Docker 自体のセットアップも含めて管理する必要があります。
Nix / NixOS は再現性の面では最強の選択肢といわれています。しかし、Nixは私が全く経験がないため学習コストが高すぎると判断しました。Claude Codeで実装するとはいえ、実装したものの確認ができないのは良くないと考え、候補から外しました。
Terraform / OpenTofu はクラウドリソースの管理が本領です。local-exec プロバイダーでローカル操作も可能ですが、状態管理(state)の仕組みが 1 台のサーバの構成管理には過剰でした。実際にシークレット管理で OpenTofu を試しましたが、state の管理コストに見合わず Infisical に移行しました。また、アプリケーションの設定などサーバ内部の管理は基本的にスコープ外となるため、今回の目的をTerraformだけで実装することは難しいと考えています。
Ansible は、これらと比較して以下の点でこの用途に合っていました。
- YAML で宣言的に書ける
- 冪等性がモジュールレベルで組み込まれている
-
hosts: localhostでローカル実行が自然にできる - ロール単位でモジュール分割できる
- エージェントレス(対象サーバに追加のデーモンが不要)
- 実務での経験がある
Ansible をローカルホストで使う際の特徴
Ansible はリモートサーバの管理に使われることが多いですが、ローカル実行にも適しています。connection: local を指定すれば SSH を経由せず直接実行されます。
一方で、注意点もあります。意図しない特権操作による事故を防ぐため become: no をデフォルトにしており、特権が必要なタスク(パッケージインストールなど)では個別に become: yes を指定します。サーバ自身が自分を構成する形なので、playbook の実行中にサービスが再起動されると影響を受ける可能性もあります。
また、Ansible 自体のインストールだけは Ansible では管理できません。いわゆる鶏と卵の問題です。これは setup.sh で最低限カバーしています。
Claude Code で Ansible を書く・運用する
Ansible の YAML ベースの宣言的な記述は、Claude Code との相性が良いと感じています。リポジトリ全体で約 79 コミットのうち 8 割以上が Claude Code との共同作業によるものです。
相性が良い理由はいくつかあります。まず、Ansible は IaC ツールのデファクトスタンダードであり、Claude が十分に学習しているため、ロール構造やモジュールの使い方を正確に理解した上でコードを生成してくれます。次に、Claude Code はターミナル上で ansible-playbook を直接実行し、その出力を読んでエラーを修正するサイクルを自律的に回せます。「ロールを書く → 実行 → エラーを見て修正」のループを人間が介在せずに進められるのは、チャットベースの AI にコードを生成させて手動で貼り付ける場合とは大きく異なります。
さらに、運用面でも Claude Code は役立ちます。サーバの状態に疑問があるときに「この設定はどのロールで管理されている?」「このサービスが起動しない原因は?」と聞くと、Ansible のコードとサーバの実際の状態を突き合わせて調査してくれます。
CLAUDE.md によるガイドライン
プロジェクトの CLAUDE.md に以下のようなロール開発のガイドラインを記載しています。シークレットについては、なくても playbook を止めず、警告を収集して最後にまとめて表示する設計です。このパターンを CLAUDE.md に書いておくと、Claude Code が新しいロールを作る際に同じ流れに従ったコードを生成してくれます。
## Role開発ガイドライン
### 新しいroleの追加
1. `roles/<role-name>/tasks/main.yml` を作成
2. `playbook.yml` の `roles:` に追加
3. 必要に応じて `group_vars/all.yml` に変数追加
### シークレット管理
- シークレットは Infisical Cloud で管理
- `infisical` roleがCLIインストールとログイン状態チェックを行い、`infisical_logged_in` factを設定
- roleは `infisical secrets get SECRET_NAME --plain --silent` でシークレットを取得
- roleは `infisical secrets set SECRET_NAME=value` でシークレットを保存
- Infisical未ログイン時やシークレット不足時は `setup_warnings` に警告を追加
# Infisical未ログイン時の警告パターン
- name: Collect warning if Infisical is not logged in
when: not infisical_logged_in
ansible.builtin.set_fact:
setup_warnings: "{{ setup_warnings + [warning_msg] }}"
vars:
warning_msg: |
[my-role] Infisical not logged in. Skipping secret setup.
To fix: infisical login
# シークレット取得パターン
- name: Get secret from Infisical
when: infisical_logged_in
ansible.builtin.command:
cmd: infisical secrets get MY_SECRET --plain --silent
register: my_secret_result
changed_when: false
failed_when: false
- name: Collect warning if secret is missing
when: infisical_logged_in and my_secret_result.rc != 0
ansible.builtin.set_fact:
setup_warnings: "{{ setup_warnings + [warning_msg] }}"
vars:
warning_msg: |
[my-role] Secret MY_SECRET not found in Infisical.
To fix: infisical secrets set MY_SECRET=value
冪等性と権限についても方針を明示しています。
### 冪等性
- すべてのタスクは繰り返し実行しても安全であること
- `creates:`, `when:`, `changed_when:` を適切に使用
- インストール済みチェックを行い、不要な再実行を避ける
### 権限
- デフォルトは `become: no`(現在のユーザーで実行)
- sudo が必要なタスクのみ `become: yes` を指定
短い記述ですが、これがあるとないとでは生成されるコードの一貫性が大きく変わります。CLAUDE.mdの記載自体もroleの更新に合わせて更新していきます。Infisicalについての記載もシークレットの管理をInfisicalに変更後にClaude Codeが追加しています。
設計ドキュメント先行のワークフロー
docs/plans/ に設計ドキュメントを先に書き、それを元に Claude Code が実装するワークフローを採用しています。たとえば、Infisical への移行時には「変更前のコード → 変更後のコード」を設計書に明記し、Claude Code にそのまま実装させました。設計ドキュメント自体もClaude Codeと対話することで生成しています。
「このサービスをセットアップするロールを作って」と依頼すると、ロールのディレクトリ構成からタスク定義、ハンドラーまで一式生成してくれます。
この組み合わせのメリット・デメリット
メリット
-
環境の復元性: 環境を壊しても
ansible-playbook一発で復元できます。実際に何度か環境を作り直しており、この安心感は大きいです - 新規ロール追加の速さ: Claude Code が Ansible のロール構造を理解しているため、新しいツールの追加が速いです
- 可視性: 宣言的なので「サーバに何が入っているか」がコードを見ればわかります
- 変更の追跡: すべての変更が git で管理されており、いつ何を変えたかが明確です
デメリット・課題
- 品質チェックの必要性: Claude Code が生成したタスクはすべて実行が成功したとしても、期待通りの設定変更がされていない場合があります。例えばCabbyのように設定ファイルの変更後にサービスのreloadが必要なのに、notify の処理が漏れており、設定が反映されないケースが何度か発生しました
-
実行時間: ロールが増えると全体の実行時間が伸びます。毎回すべてのロールを実行するため、変更がない状態でも約 1 分かかります。
--tagsによる部分実行で短縮できますが、まだ導入していません - オーバーエンジニアリング感: 1 台のサーバに Ansible は大げさに見えます。ただ、ロール数が10以上になった今では、シェルスクリプトで管理していた場合を想像すると、この判断は正しかったと感じています。また、Claude Codeが実装することを考えると、私自身のエンジニアリングコストはそれほど発生しないため、メリットのわりにコストは低かったと考えています(Claudeの料金を考えなければ...)。
まとめ
1 台の開発サーバでも、構成要素が増えてくると宣言的な管理の恩恵は大きくなります。Ansible はこの規模感にちょうど良い選択でした。
そして、Ansible の宣言的な記述と Claude Code の組み合わせは、予想以上に生産的でした。Claude Code がロールの生成から実行・デバッグまで自律的に回せること、CLAUDE.md と設計ドキュメントでプロジェクトの規約を明文化できることが、このワークフローの核です。
個別のロールの詳細や、シークレット管理の変遷については、別の記事で書く予定です。
Discussion