Claude CodeでsystemdのUnitファイルを作成する手順
Claude CodeにsystemdのUnitファイルを生成させ、Type=やRestart=の意味を確認しながら配置・検証・登録まで進める手順です。
Claude CodeにsystemdのUnitファイルを書かせると、雛形の生成自体はすぐに終わります。ただしExecStart=の絶対パス要件やType=の選び方を把握していないと、生成された内容をそのまま検証できません。この記事では、サービス用のUnitファイルを例に、生成から配置・検証・登録までを手順化します。
Claude CodeでsystemdのUnitファイルを書くとはどんな作業か
systemdは、Linuxシステムでプロセス1番(PID 1)として動作するシステム・サービスマネージャーです。Unitファイルはiniスタイルのテキストファイルで、サービスやソケット、マウントポイントなど、systemdが管理する対象の設定を記述します。サービスを対象にしたUnitファイルは.serviceという拡張子を持ち、共通の[Unit]・[Install]セクションと、サービス固有の[Service]セクションで構成されます。
Claude CodeはUnitファイル専用の生成機能を持たず、他の設定ファイルを書かせる作業と同じ、通常のコーディング支援としてiniスタイルのテキストを組み立てます。したがって精度を左右するのは、実行したいコマンドの絶対パス・実行ユーザー・再起動時の挙動といった条件を、どれだけ具体的に伝えるかです。対象コマンドや条件を先に固めてから生成させるという進め方は、Claude Codeでk6の負荷テストスクリプトを作成する手順で紹介したテストスクリプトの生成でも同じです。Claude Code自体の機能や料金はClaude Code(クロードコード)とはにまとめています。
前提条件
systemdはUbuntu・Debian・Fedora・RHEL系を含む主要なLinuxディストリビューションで標準採用されているため、追加インストールは通常不要です。バージョンは次のコマンドで確認できます。
systemctl --versionシステム全体のサービスとして/etc/systemd/system/に登録するにはroot権限が必要です。一般ユーザー権限のまま動かしたい場合は、systemctl --userを使うユーザーモードを選び、Unitファイルは~/.config/systemd/user/に置きます。--userは呼び出したユーザー自身のサービスマネージャーに話しかけるオプションで、システム全体への影響を避けられます。この記事では以降、システム全体への登録を前提に説明します。
手順1: サービスUnitの雛形をClaude Codeに生成させる
まず、実行したいコマンドの絶対パスと、落ちたときにどう扱いたいかを具体的に伝えます。
/opt/myapp/bin/workerを実行し続けるsystemdのserviceユニットファイルを書いて。設定は/etc/myapp/config.yamlを読む。プロセスが落ちたら自動で再起動させたいこの指示から、Claude Codeは次のような雛形を生成します。
[Unit]
Description=My App Worker
After=network.target
[Service]
Type=exec
ExecStart=/opt/myapp/bin/worker --config /etc/myapp/config.yaml
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.targetDescription=はsystemctl statusなどに表示される人間向けの識別名で、ユニット名を単に言い換えるのではなく、そのサービスが何かを示す短い文にします。After=network.targetは起動順序だけを指定する設定で、ネットワークの準備が完了した後に起動してほしいという意図を表します。順序を揃えたいだけならAfter=、そのユニットの起動自体を前提にしたいならWants=(緩い依存)かRequires=(強い依存)を使い分けます。Wants=で指定した対象が起動に失敗しても、このユニット自体は起動を続けられるのに対し、Requires=は対象の失敗が伝播しうる点が違いです。
[Install]セクションのWantedBy=multi-user.targetは、systemctl enableを実行したときにmulti-user.target.wants/へ作られるシンボリックリンク先を指定しており、起動時に自動起動させるかどうかを決めます。
手順2: [Service]セクションの主要ディレクティブを調整する
生成された雛形のType=とRestart=は、サービスの性質に合わせて調整が必要です。公式ドキュメントは、ExecStart=で起動する処理が長時間稼働するプロセスならType=execを推奨しています。Type=simple(デフォルト)は、ExecStart=で指定したバイナリのfork()直後にサービスを起動済みとみなすため、指定した実行ユーザーが存在しないなどの理由でバイナリが実行できなかった場合でも、systemctl start自体は成功として報告されます。Type=execはバイナリのexecve()成功まで待つため、この種の起動失敗を確実に検知できます。
| Type= | 起動完了のタイミング | 向いている用途 |
|---|---|---|
| simple(既定) | 起動完了のタイミングプロセスのfork直後 | 向いている用途起動確認が不要な常駐プロセス |
| exec | 起動完了のタイミングバイナリのexecve成功後 | 向いている用途長期稼働サービス(公式推奨) |
| forking | 起動完了のタイミング親プロセスの終了後 | 向いている用途従来型のUNIXデーモン |
| oneshot | 起動完了のタイミングメインプロセスの終了後 | 向いている用途一度だけ実行するタスク |
| notify / notify-reload | 起動完了のタイミングREADY=1通知の受信後 | 向いている用途起動完了を自己申告するデーモン |
| dbus | 起動完了のタイミングD-Busバス名の取得後 | 向いている用途D-Busインターフェースを持つサービス |
Restart=は、どの終了状態で再起動するかを決めます。on-failureは非ゼロの終了コードやシグナルによる異常終了、タイムアウト、ウォッチドッグ超過で再起動しますが、終了コード0などのクリーンな終了では再起動しません。alwaysはクリーンな終了かどうかを問わず常に再起動する点が違いで、プロセスが自分の判断で正常終了しても再起動が続きます。ただしalwaysを含むどの設定でも、systemctl stopのようなsystemd自身の操作でサービスを止めた場合は再起動しません。長期稼働サービスには、エラー時の自動復旧を目的としてon-failureを使うことが公式ドキュメントで推奨されています。RestartSec=は再起動までの待機時間で、既定値は100ミリ秒です。
ExecStart=に指定するコマンドの先頭要素は、絶対パスかスラッシュを含まないファイル名のいずれかである必要があります。相対パスは受け付けられず、ファイル名だけを書いた場合は/usr/local/bin/や/usr/bin/など、コンパイル時に決まった検索パスから解決されます。実行ユーザーを変えたい場合はUser=(グループはGroup=)を指定します。システムサービスの既定の実行ユーザーはrootのため、権限を絞りたい場合は明示的に指定します。環境変数をまとめて渡したい場合はEnvironmentFile=で外部ファイルを読み込めます。
本番環境で動かす場合は、[Service]セクションに簡単なハードニング設定を加えておくと安全性が上がります。NoNewPrivileges=trueを指定すると、setuidビットなどによる権限昇格をプロセスとその子プロセス全体で禁止できます。ProtectSystem=strictを指定すると、/usr/や/etc/を含むファイルシステム全体を読み取り専用でマウントし、意図しない書き込みを防ぎます。書き込みが必要なディレクトリだけReadWritePaths=で個別に許可します。PrivateTmp=trueを指定すると、/tmp/と/var/tmp/が他のプロセスと共有されない専用の名前空間に切り替わり、サービス停止時に一時ファイルも自動的に削除されます。これらはいずれも真偽値で指定でき、公式ドキュメント上の既定値はすべてfalseです。
相対パスで設定ファイルや出力先を参照させたい場合は、WorkingDirectory=で実行時のカレントディレクトリを明示しておくと、ExecStart=側の指定がシンプルになります。指定しなければ、システムサービスでは/が既定のカレントディレクトリとして使われます。指定先のディレクトリがまだ存在しない可能性があるなら、先頭に-を付けておくと、ディレクトリが無くても起動失敗として扱われなくなります。
手順3: 配置してsystemdに登録・検証する
生成したUnitファイルを/etc/systemd/system/myapp.serviceのように保存したら、まず文法エラーやexec対象の有無をsystemd-analyze verifyで確認します。
sudo systemd-analyze verify /etc/systemd/system/myapp.servicesystemd-analyze verifyは、未知のディレクティブ・起動に必要な依存の欠落・ExecStart=に指定したコマンドが実行できないことなどを警告として表示します。エラーが出なければ、systemdに新しいUnitファイルの存在を認識させます。
sudo systemctl daemon-reloaddaemon-reloadはUnitファイルを再読み込みし、依存関係のツリーを再構築するコマンドで、Unitファイルを新規作成したときや編集したときは必ず実行します。次に、起動時の自動起動を有効化しつつ、今すぐ起動します。
sudo systemctl enable --now myapp.serviceenableは[Install]セクションに基づいてシンボリックリンクを作るだけの操作で、start(起動)とは独立しています。enableしただけでは今すぐには起動せず、--nowを付けるか別途startを実行しない限りプロセスは動きません。状態はsystemctl statusで確認します。
systemctl status myapp.serviceログを継続的に見たい場合はjournalctlを使います。
journalctl -u myapp.service -f生成したUnitファイルをリポジトリで管理している場合は、変更をコミットしてPRにまとめる流れも任せられます。差分の要約からPR作成までの手順はClaude CodeでPRを作成する手順にまとめています。
よくあるつまずき
ExecStart=に相対パスを書いてしまう。仕様上、コマンドの先頭要素は絶対パスかスラッシュを含まないファイル名でなければならず、./bin/workerのような相対パス表記は受け付けられません。Claude Codeに生成させた後も、この一点は必ず目で確認します。
Type=simpleのまま起動失敗に気づけない。指定した実行ユーザーが存在しない、実行ファイルが見つからないといった理由でバイナリが起動できなくても、Type=simpleではsystemctl startが成功として報告されることがあります。起動の成否を確実に検知したい長期稼働サービスではType=execを検討します。
enableとstartの違いを混同する。enableは次回起動時からの自動起動を設定するだけで、今すぐの起動は行いません。今すぐ動かしたいときは--nowを付けるか、startを別途実行します。
Unitファイルを編集した後にdaemon-reloadを忘れる。編集後にdaemon-reloadを実行しないと、systemdは古い定義のままサービスを扱い続け、変更が反映されません。
バッチ的に正常終了するプロセスにRestart=alwaysを付けてしまう。alwaysはクリーンな終了(終了コード0など)でも再起動するため、処理が終わるたびに正常終了するタイプのプロセスに使うと、意図せず再起動を繰り返します。一定時間だけ動いて自分で終了する処理にはon-failureを選びます。
まとめ
Claude CodeでsystemdのUnitファイルを書くときは、対象コマンドの絶対パス・実行ユーザー・再起動条件を先に固めて生成させ、systemd-analyze verifyで文法を確認し、daemon-reloadを経てenable --nowで登録するという順で進めると迷いにくくなります。Type=とRestart=は生成後に必ず目的に合っているかを確認し、enableとstartが別の操作である点も意識しておくと、動かないままはまり込む場面を減らせます。