
NetBoxとは、IPアドレス・VLAN・機器・ラック・配線といったネットワークの「あるべき姿」を1か所に記録するオープンソースの台帳アプリケーション(IPAM/DCIM)です。稼働状況の監視やDHCPによるアドレス配布はしません。人が決めた設計をデータとして持ち、画面とREST APIの両方から同じデータを読み書きできる点が、ExcelでのIPアドレス管理との決定的な違いです。
対象は、ExcelのIP台帳が限界に近づいている情シスと、自宅ラボのサブネットを整理したい人です。執筆時点の最新版NetBox 4.7.1(netbox-dockerイメージ5.1.1)をDocker Composeで起動し、1拠点・2VLAN・2サブネット・スイッチ1台・サーバー1台の小さなオフィスを登録して、REST APIで次の空きIPを払い出すところまで進めます。出力と所要時間は、4 vCPUの検証環境で実際に動かしたときの値です。
この記事でわかること
- Excel台帳・phpIPAM・NetBoxの判断表と、NetBoxが過剰になる条件
- NetBox 4.7.1をPostgreSQL 16・Redis 7・バックグラウンドワーカーと一緒に動かす
compose.yamlと、各設定を選んだ理由 - 初回起動に約8分かかった内訳と、ヘルスチェックの
start_periodを600秒にした根拠 - 拠点→VLAN→プレフィックス→機器→IPの順に登録するスクリプトと、
available-ipsで空きIPを払い出す方法 - 重複IP・予約レンジ・VLAN IDの重複がどこまで弾かれるか(実際のエラーメッセージつき)
NetBoxとは:台帳であって監視ツールではない
NetBoxが扱うデータは大きく2系統です。IPAM(IP Address Management)はプレフィックス(10.10.10.0/24 のようなサブネット)、その中のIPアドレス、VLAN、VRFを扱い、DCIM(Data Center Infrastructure Management)は拠点、ラック、機器、インターフェース、ケーブルを扱います。両者はつながっていて、IPアドレスは機器のインターフェースに割り当て、プレフィックスは拠点やVLANに紐づけます。「このIPは何に使われているか」をたどると、サブネット→IP→インターフェース→機器→拠点まで一本の線でつながるのが台帳としての価値です。
できないことも先に書いておきます。pingやSNMPによるネットワークの走査はしません。入っているのは人やスクリプトが登録した内容だけで、実機と食い違っていてもNetBoxは気づきません。DHCPサーバーやDNSサーバーの機能もないため、台帳の内容をそれらに反映するにはAPIを読む仕組み(シェルスクリプト、Ansibleなど)を別に用意します。実機の状態を見張るのは監視ツール(例:PrometheusとGrafana)の担当で、NetBoxは「設計上こうなっているはず」を持つ側、と役割を分けて考えると導入後に迷いません。
動作要件は執筆時点でPostgreSQL 15以上、Redis 6.0以上です。4.7でPostgreSQL 14とRedis 5.xのサポートが終わり、階層構造のデータにPostgreSQLの ltree 拡張を使うようになりました。拡張はNetBoxのDBユーザーがデータベースに対する CREATE 権限を持っていれば自動で入るため、公式postgresイメージで POSTGRES_USER をそのままNetBox用ユーザーにする構成なら追加作業はありません。本体はApache License 2.0で公開されています。開発元のNetBox Labsが有償のクラウド版なども提供していますが、価格と契約条件は公式サイトで確認してください。
Excel台帳・phpIPAM・NetBoxの判断表
選ぶ基準は機能の多さではなく、「台帳が壊れる原因をどこまで機械が防いでくれるか」です。Excel台帳が壊れる典型は、行のコピーで重複IPが生まれる、変更した人と日時が残らない、機器側の情報と別ファイルになって更新が片方だけになる、の3つです。下の表はこれに自動化と運用の手間を足して並べています。
| 観点 | Excel/共有スプレッドシート | phpIPAM | NetBox 4.7 |
|---|---|---|---|
| 重複・範囲外の入力 | 条件付き書式や数式を自作する。行コピーで簡単にすり抜ける | サブネット内の重複を拒否する | 既定で重複IP・重複プレフィックスを拒否(ENFORCE_GLOBAL_UNIQUE の既定値がTrue)。予約レンジ内の登録やVLAN ID 4095も400エラー |
| 変更履歴 | 版の履歴を掘る。誰がどのセルを変えたかは追いにくい | 変更ログがある | 全オブジェクトに変更前後の値つき履歴。既定の保持は90日(CHANGELOG_RETENTION) |
| 空きIPの払い出し | 目視で探す | 画面とAPIで空きを探せる | available-ips が排他ロックを取って次の空きを確保して返す |
| 機器・ポートとの紐づけ | 列を増やして手入力 | 機器・ラックの管理はあるが、インターフェース単位の配線モデルは持たない(執筆時点) | インターフェース、ケーブル、ラック搭載位置、電源まで台帳にできる |
| 実機の走査(ping等) | なし | サブネットのスキャン機能がある | なし(外部ツールで補う) |
| 導入と維持 | 不要 | PHPとMySQL/MariaDBの構成 | 本体+ワーカー+PostgreSQL+Redis。検証環境の実測で合計約0.9GBのメモリ |
phpIPAMは今回の検証環境では動かしていないので、表の機能は導入前に公式ドキュメントで確かめてください。「IPアドレスだけを管理したい」「スキャンで実際に使われているIPを拾いたい」「サーバーのメモリに余裕がない」がそろうなら、phpIPAMのほうが軽く済みます。
逆にNetBoxが過剰なのは、/24が1〜2個、担当者が1人、機器の入れ替えが年に数回、自動化の予定もない環境です。この条件ならExcelで足ります。ただし1行1IPとし、列は「アドレス/ホスト名/用途/担当者/最終更新日」を固定して、サブネットごとにシートを分けないでください(分けるとCOUNTIFによる重複チェックがシートをまたげません)。一方、拠点が2つ以上かVLANが5本以上ある、監査や障害対応で「誰がいつ変えたか」を聞かれる、スクリプトから空きIPを取りたい、機器のどのポートに何がつながるかを残したい、のどれか1つに当てはまった時点でNetBoxへ移る価値があります。
DockerでNetBoxを動かすcompose.yaml
netbox-dockerプロジェクトが配布するイメージ netboxcommunity/netbox を使い、compose.yamlは自分で書きます。タグは v4.7.1-5.1.1(NetBoxの版-イメージの版)のように固定します。latest はイメージを取り直したときに次の版へ進み、起動時にDBのマイグレーションまで自動で走るので、何年も使う台帳には向きません。作業ディレクトリは ~/netbox とし、Docker Composeの基本はDocker Compose入門の内容を前提にします。
services:
netbox:
image: netboxcommunity/netbox:v4.7.1-5.1.1
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
environment: &netbox-env
DB_HOST: postgres
DB_NAME: netbox
DB_USER: netbox
DB_PASSWORD: ${DB_PASSWORD}
REDIS_HOST: redis
REDIS_PASSWORD: ${REDIS_PASSWORD}
REDIS_DATABASE: "0"
REDIS_CACHE_HOST: redis
REDIS_CACHE_PASSWORD: ${REDIS_PASSWORD}
REDIS_CACHE_DATABASE: "1"
SECRET_KEY: ${SECRET_KEY}
API_TOKEN_PEPPER_1: ${API_TOKEN_PEPPER_1}
SKIP_SUPERUSER: "false"
SUPERUSER_NAME: admin
SUPERUSER_EMAIL: [email protected]
SUPERUSER_PASSWORD: ${SUPERUSER_PASSWORD}
SUPERUSER_API_KEY: ${SUPERUSER_API_KEY}
SUPERUSER_API_TOKEN: ${SUPERUSER_API_TOKEN}
TIME_ZONE: Asia/Tokyo
GRANIAN_WORKERS: "2"
CENSUS_REPORTING_ENABLED: "false"
ports:
- "127.0.0.1:18000:8080"
volumes:
- netbox-media:/opt/netbox/netbox/media
- netbox-reports:/opt/netbox/netbox/reports
- netbox-scripts:/opt/netbox/netbox/scripts
mem_limit: 900m
restart: unless-stopped
healthcheck:
test: curl -f http://localhost:8080/login/ || exit 1
start_period: 600s
interval: 30s
timeout: 3s
retries: 5
netbox-worker:
image: netboxcommunity/netbox:v4.7.1-5.1.1
command: /opt/netbox/venv/bin/python /opt/netbox/netbox/manage.py rqworker
depends_on:
netbox:
condition: service_healthy
environment: *netbox-env
mem_limit: 400m
restart: unless-stopped
postgres:
image: postgres:16-alpine
environment:
POSTGRES_DB: netbox
POSTGRES_USER: netbox
POSTGRES_PASSWORD: ${DB_PASSWORD}
volumes:
- postgres-data:/var/lib/postgresql/data
mem_limit: 200m
restart: unless-stopped
healthcheck:
test: pg_isready -U netbox -d netbox
interval: 10s
timeout: 5s
retries: 5
redis:
image: redis:7-alpine
command: redis-server --requirepass ${REDIS_PASSWORD} --save 60 1 --appendonly no
volumes:
- redis-data:/data
mem_limit: 100m
restart: unless-stopped
healthcheck:
test: redis-cli -a ${REDIS_PASSWORD} --no-auth-warning ping | grep -q PONG
interval: 10s
timeout: 5s
retries: 5
volumes:
postgres-data:
redis-data:
netbox-media:
netbox-reports:
netbox-scripts:パスワードや鍵は同じディレクトリの .env に置き、Composeの変数展開で渡します。SECRET_KEY と API_TOKEN_PEPPER_1 はどちらも50文字以上でないと起動時の検証で落ちるので、64文字になる token_urlsafe(48) で作ります。SUPERUSER_API_KEY(12文字)と SUPERUSER_API_TOKEN(40文字)は、NetBoxが自分でトークンを作るときと同じ長さと文字種(英数字)にそろえています。
mkdir -p ~/netbox && cd ~/netbox
umask 077
cat > .env <<EOF
DB_PASSWORD=$(python3 -c 'import secrets; print(secrets.token_urlsafe(24))')
REDIS_PASSWORD=$(python3 -c 'import secrets; print(secrets.token_urlsafe(24))')
SECRET_KEY=$(python3 -c 'import secrets; print(secrets.token_urlsafe(48))')
API_TOKEN_PEPPER_1=$(python3 -c 'import secrets; print(secrets.token_urlsafe(48))')
SUPERUSER_PASSWORD=$(python3 -c 'import secrets; print(secrets.token_urlsafe(18))')
SUPERUSER_API_KEY=$(tr -dc 'A-Za-z0-9' </dev/urandom | head -c 12)
SUPERUSER_API_TOKEN=$(tr -dc 'A-Za-z0-9' </dev/urandom | head -c 40)
EOF
ls -l .env # -rw------- になっていれば umask が効いているイメージ内の /etc/netbox/config/configuration.py が環境変数を読んでNetBoxの設定に変換する仕組みなので、効くのはこのファイルが読む変数名だけです。値ごとに理由を書いておきます。
DB_HOST/REDIS_HOST:Composeの内部DNSがサービス名をコンテナのIPに解決するので、IPアドレスではなくサービス名を書きます。REDIS_DATABASE: "0"とREDIS_CACHE_DATABASE: "1":NetBoxはRedisをジョブのキューとキャッシュの2用途に使います。1台のRedisでもDB番号を分けておけば、キャッシュだけをFLUSHDBで消してもキューに積まれたジョブは残ります。Redis側の--save 60 1は、60秒の間に1件でも変更があればディスクに書き出す設定で、再起動時にキューの中身を失いにくくします。SECRET_KEY:セッションとCSRFトークンの署名に使う値です。変えると全ユーザーがログアウトされます。API_TOKEN_PEPPER_1:4.5で入った新形式のAPIトークン(v2)は、トークン本体ではなく「トークン+この値」から計算したHMAC-SHA256の値だけをDBに保存します。未設定でもNetBoxは起動しますが、v2トークンを1つも作れません。執筆時点のイメージが環境変数から読むのは1番だけで、鍵を入れ替えるために2番以降を足すときは追加の設定ファイルが必要です。SKIP_SUPERUSER: "false"とSUPERUSER_*:初回起動時に管理者とそのAPIトークンを作ります。管理者が既にいれば何もしません。ここで作る管理者にはパスワードの強度チェックがかかりませんが、あとで画面から変えるときは12文字以上で数字・大文字・小文字を各1文字以上含む必要があります。GRANIAN_WORKERS: "2":4.7のイメージはアプリケーションサーバーがgranianで、既定は4ワーカーです。4ワーカーではコンテナが約827MiBを使い、2に減らすと525〜575MiBでした。数人で使う台帳なら2で足ります。CENSUS_REPORTING_ENABLED: "false":既定では日次の保守ジョブが、NetBoxの版・Pythonの版・SECRET_KEYから計算したIDを開発元に送ります。外向きの通信を増やしたくない社内環境では切ります。インターネットに出られない環境ならISOLATED_DEPLOYMENT: "true"を足すと、この送信に加えてインターネット前提の機能(プラグインカタログの取得など)もまとめて止まります。ports: "127.0.0.1:18000:8080":LANには直接公開しません。社内から使うときはCaddyなどのリバースプロキシでTLSを終端し、そのホスト名をALLOWED_HOSTSに入れます。イメージの既定値は*(すべて許可)なので、公開するときは絞ります。environment: &netbox-envと*netbox-env:YAMLのアンカーで、ワーカーに本体と同じ環境変数を渡しています。片方だけ直して接続先がずれる事故を防げます。netbox-workerのcommandとdepends_on:commandを指定するとイメージ既定の起動スクリプト(DB待ち、マイグレーション、管理者作成)を通らずにrqworkerだけが起動します。マイグレーションは本体の起動時にしか走らないので、本体がhealthyになるまでワーカーを待たせ、テーブルがそろう前に動き出さないようにしています。
入れていない設定もあります。DEBUG: "true" はエラー画面に設定値やスタックトレースを出すので使いません。LOGIN_REQUIRED: "false" は未ログインでも閲覧できるようにする設定で、台帳にはホスト名や用途が並ぶため社内でも有効にしません。起動のたびに FutureWarning: LOGIN_REQUIRED is deprecated という警告が出ますが、これはイメージ側の設定ファイルがこの変数を読んでいるためで、動作には影響しません。
IPの登録・検索・APIだけならワーカーなしでも動きます(検索インデックスは保存時に更新され、ワーカーがない状態で登録したIPもインデックスに入っていました)。それでもワーカーを入れているのは、変更履歴の90日超の削除や期限切れセッションの掃除を行う日次の「System Housekeeping」ジョブがワーカーでしか実行されないからです。ワーカーなしで機器を登録したときに積まれた「Render config context」ジョブは、ワーカーを起動した瞬間に処理されました。ワーカーのメモリは実測で約250MiBです。
初回起動は約8分:マイグレーションとstart_periodの関係
起動して、本体のログで進み具合を見ます。空のDBに対して336件のマイグレーションを順に当てるので、初回だけ時間がかかります。
docker compose up -d
docker compose logs --timestamps netbox | grep -E '⚙️|✅|💡|Listening'netbox-1 | 2026-09-24T06:47:47.589550762Z ⚙️ Applying database migrations
netbox-1 | 2026-09-24T06:54:24.094782911Z ⚙️ Running trace_paths
netbox-1 | 2026-09-24T06:54:33.626074279Z ⚙️ Removing stale content types
netbox-1 | 2026-09-24T06:54:41.668211710Z ⚙️ Removing expired user sessions
netbox-1 | 2026-09-24T06:54:49.541804837Z ⚙️ Building search index (lazy)
netbox-1 | 2026-09-24T06:55:03.684126671Z 💡 Superuser Username: admin, E-Mail: [email protected],
netbox-1 | 2026-09-24T06:55:03.684129121Z 💡 API Token: use with 'Bearer nbt_vvzv06g5h3Wg.<Your token>'
netbox-1 | 2026-09-24T06:55:04.724223884Z ✅ Initialisation is done.
netbox-1 | 2026-09-24T06:55:04.914427057Z [INFO] Listening at: http://:::8080ログの時刻はUTCです。コンテナの起動は06:47:28で、マイグレーションだけで6分37秒、ログイン画面が200を返すまで約7分40秒かかりました。環境は4 vCPU(AMD EPYC 7713)で、同じホストで他のコンテナも動いていてロードアベレージは3.6前後でした。1回目の試行(granianワーカー4)では、マイグレーション7分29秒、ログイン画面まで約8分40秒です。2回目以降の起動は manage.py migrate --check が通るのでマイグレーションが丸ごと飛ばされ、docker compose restart netbox からログイン画面まで35秒でした。
| 起動の種類 | マイグレーション | ログイン画面が200を返すまで |
|---|---|---|
| 初回(granianワーカー4) | 7分29秒 | 約8分40秒 |
| 初回(granianワーカー2) | 6分37秒 | 約7分40秒 |
| 2回目以降(restart) | なし | 35秒 |
start_period: 600s はこの実測から決めました。1回目は start_period: 120s、interval: 15s で試したところ、マイグレーションの途中で猶予が切れて Up 8 minutes (unhealthy) になりました。Dockerはunhealthyのコンテナを自動で再起動しませんが、condition: service_healthy を条件にしているワーカーなどは起動されないままになります。600秒の猶予が切れたあとは30秒×5回の失敗でunhealthyになるので、初回起動に最大で約12分半まで耐えられます。
初回のマイグレーション中は manage.py migrate のプロセスだけで約820MBを使い、コンテナ全体では900MiBの上限に対して約850MBまで上がりました。mem_limit を小さくしたいときは、初回起動が終わってから下げてください。起動後の常用時は、本体(granianワーカー2)が525〜575MiB、ワーカーが約250MiB、PostgreSQLが50〜80MiB、Redisが約5MiBでした。
NetBoxの使い方:台帳に登録する順番
ブラウザで http://127.0.0.1:18000/ を開き、admin と .env の SUPERUSER_PASSWORD でログインします。ブラウザの言語が日本語なら、メニューやボタンは日本語で表示されます(「組織」「デバイス」「IPAM」など)。一部に「Hide Depth Indicators」のような英語が残ります。
登録する順番は、オブジェクト同士の参照関係で決まります。IPアドレスはインターフェースに、インターフェースは機器に割り当て、機器は拠点・機種・役割を必須項目として持ちます。逆順に作ろうとすると、たとえば役割を省いた機器の登録は {"role":["This field is required."]} で拒否されます。今回登録する設計は次のとおりです(例)。
| 対象 | 値(例) | NetBoxでの登録先 |
|---|---|---|
| 拠点 | 名古屋オフィス | 組織 → サイト |
| VLAN | 10 servers/20 clients(VLANグループ nagoya-office) | IPAM → VLAN |
| 拠点全体 | 10.10.0.0/16 | プレフィックス(ステータス:コンテナ) |
| サーバー用 | 10.10.10.0/24 | プレフィックス(VLAN 10) |
| クライアント用 | 10.10.20.0/24 | プレフィックス(VLAN 20) |
| ネットワーク機器用の予約 | 10.10.10.2〜10.10.10.9 | IPアドレス範囲(予約済) |
| DHCPプール | 10.10.20.100〜10.10.20.199 | IPアドレス範囲 |
| 機器 | sw-01(L3スイッチ)、srv-01(サーバー) | デバイス |
| ゲートウェイ | 10.10.10.1、10.10.20.1 | sw-01のVlan10/Vlan20に割り当て |
| サーバー | 10.10.10.10 | srv-01のeth0に割り当て |
/16をステータス「コンテナ」で作るのは、IPを直接置かない入れ物であることを示すためです。コンテナの使用率は配下の/24の合計から計算され、この例では0.8%(512/65,536)と表示されました。VLANの設計そのものはVLANでネットワークを分離する手順で扱っています。画面から1件ずつ入れても構いませんが、10件を超えるならAPIでまとめて入れたほうが入力ミスが減ります。
APIトークン(v2)を発行する
4.7のAPIトークンは nbt_<キー>.<トークン> という形で、Authorization: Bearer ヘッダーで送ります。.env の値を使うと、初回起動で作られた管理者のトークンは nbt_${SUPERUSER_API_KEY}.${SUPERUSER_API_TOKEN} です。スクリプト用には別のトークンを用意し、管理者のものは初期構築だけに使うのがおすすめです。ユーザー名とパスワードからトークンを発行するエンドポイントがあるので、パスワードをコマンド履歴に残さないよう read -s で受け取って渡します。
read -rs NB_PASS
jq -n --arg u admin --arg p "$NB_PASS" '{username: $u, password: $p, description: "seed script"}' \
| curl -s -X POST http://127.0.0.1:18000/api/users/tokens/provision/ \
-H 'Content-Type: application/json' -H 'Accept: application/json' --data @- \
| jq '{version, key, token, write_enabled, expires}'{
"version": 2,
"key": "mxTlUJ0X7mMZ",
"token": "(英数字40文字)",
"write_enabled": true,
"expires": null
}token の平文が返るのはこの応答だけで、あとで /api/users/tokens/ を見ても "token": null になっています。DBにはハッシュしか残らないためで、控え忘れたら作り直すしかありません。パスワードを間違えると {"detail":"Invalid username/password"}(HTTP 403)です。DHCPサーバーの設定を台帳から生成するような読み取り専用の用途には、write_enabled をfalseにしたトークンを使います。実際に読み取り専用トークンで空きIPの払い出し(POST)を試すと、{"detail":"You do not have permission to perform this action."} で拒否され、同じトークンでのGETは通りました。
REST APIで小さなオフィスをまとめて登録する
先ほどの表をそのまま登録するスクリプトです。curl と jq を使います。post 関数は作成されたオブジェクトのIDだけを返し、NetBoxが400などで拒否したら応答本文を表示してそこで止まります。途中で止まれば、どの入力が悪いかがNetBoxのメッセージでそのまま分かります。
#!/usr/bin/env bash
# 小さなオフィス(1拠点・2VLAN・2サブネット・機器2台)をNetBox 4.7のREST APIで登録する
# 使い方: NETBOX_TOKEN='nbt_<key>.<token>' ./seed.sh
set -euo pipefail
NB=${NETBOX_URL:-http://127.0.0.1:18000}/api
: "${NETBOX_TOKEN:?NETBOX_TOKEN を設定してください}"
HDR=(-H "Authorization: Bearer $NETBOX_TOKEN" -H 'Content-Type: application/json' -H 'Accept: application/json')
# POSTして、作られたオブジェクトのidだけを出力する。失敗したら応答本文を出して止まる
post() {
local out
out=$(curl -sS -X POST "$NB$1" "${HDR[@]}" --data "$2")
jq -e '.id' <<<"$out" 2>/dev/null || { echo "POST $1 failed: $out" >&2; return 1; }
}
patch() {
curl -sS -o /dev/null --fail-with-body -X PATCH "$NB$1" "${HDR[@]}" --data "$2"
}
# 拠点とVLANグループ(VLAN IDの重複チェックはグループ単位)
SITE=$(post /dcim/sites/ '{"name":"名古屋オフィス","slug":"nagoya-office","status":"active"}')
VG=$(post /ipam/vlan-groups/ "{\"name\":\"nagoya-office\",\"slug\":\"nagoya-office\",\"scope_type\":\"dcim.site\",\"scope_id\":$SITE}")
R_SRV=$(post /ipam/roles/ '{"name":"サーバー","slug":"servers"}')
R_CLI=$(post /ipam/roles/ '{"name":"クライアント","slug":"clients"}')
V10=$(post /ipam/vlans/ "{\"vid\":10,\"name\":\"servers\",\"site\":$SITE,\"group\":$VG,\"role\":$R_SRV,\"status\":\"active\"}")
V20=$(post /ipam/vlans/ "{\"vid\":20,\"name\":\"clients\",\"site\":$SITE,\"group\":$VG,\"role\":$R_CLI,\"status\":\"active\"}")
# プレフィックス: /16をコンテナにして、その下に/24を2つ
post /ipam/prefixes/ "{\"prefix\":\"10.10.0.0/16\",\"status\":\"container\",\"scope_type\":\"dcim.site\",\"scope_id\":$SITE,\"description\":\"名古屋オフィス 全体\"}" >/dev/null
P_SRV=$(post /ipam/prefixes/ "{\"prefix\":\"10.10.10.0/24\",\"status\":\"active\",\"scope_type\":\"dcim.site\",\"scope_id\":$SITE,\"vlan\":$V10,\"role\":$R_SRV,\"description\":\"サーバーセグメント\"}")
P_CLI=$(post /ipam/prefixes/ "{\"prefix\":\"10.10.20.0/24\",\"status\":\"active\",\"scope_type\":\"dcim.site\",\"scope_id\":$SITE,\"vlan\":$V20,\"role\":$R_CLI,\"description\":\"クライアントセグメント(DHCP)\"}")
# IPレンジ: mark_populated=true で範囲内の個別登録と払い出しを止める
post /ipam/ip-ranges/ '{"start_address":"10.10.10.2/24","end_address":"10.10.10.9/24","status":"reserved","mark_populated":true,"description":"ネットワーク機器用に予約"}' >/dev/null
# DHCPプール: mark_utilized=true で使用率の計算上は全部使用中として数える
post /ipam/ip-ranges/ '{"start_address":"10.10.20.100/24","end_address":"10.10.20.199/24","status":"active","mark_populated":true,"mark_utilized":true,"description":"DHCPプール"}' >/dev/null
# 機器: メーカー → 機種 → 役割 → 機器 → インターフェース の順
MFR=$(post /dcim/manufacturers/ '{"name":"Generic","slug":"generic"}')
DT_SW=$(post /dcim/device-types/ "{\"manufacturer\":$MFR,\"model\":\"L3 switch 24-port\",\"slug\":\"l3-switch-24\",\"u_height\":1}")
DT_SRV=$(post /dcim/device-types/ "{\"manufacturer\":$MFR,\"model\":\"1U server\",\"slug\":\"server-1u\",\"u_height\":1}")
DR_SW=$(post /dcim/device-roles/ '{"name":"コアスイッチ","slug":"core-switch","color":"2196f3"}')
DR_SRV=$(post /dcim/device-roles/ '{"name":"サーバー","slug":"server","color":"4caf50"}')
SW=$(post /dcim/devices/ "{\"name\":\"sw-01\",\"device_type\":$DT_SW,\"role\":$DR_SW,\"site\":$SITE,\"status\":\"active\"}")
SRV=$(post /dcim/devices/ "{\"name\":\"srv-01\",\"device_type\":$DT_SRV,\"role\":$DR_SRV,\"site\":$SITE,\"status\":\"active\"}")
IF_V10=$(post /dcim/interfaces/ "{\"device\":$SW,\"name\":\"Vlan10\",\"type\":\"virtual\"}")
IF_V20=$(post /dcim/interfaces/ "{\"device\":$SW,\"name\":\"Vlan20\",\"type\":\"virtual\"}")
post /dcim/interfaces/ "{\"device\":$SW,\"name\":\"Gi1/0/1\",\"type\":\"1000base-t\",\"mode\":\"access\",\"untagged_vlan\":$V10,\"description\":\"to srv-01 eth0\"}" >/dev/null
IF_ETH0=$(post /dcim/interfaces/ "{\"device\":$SRV,\"name\":\"eth0\",\"type\":\"1000base-t\"}")
# IPアドレスをインターフェースに割り当て、機器の代表IP(primary_ip4)にする
GW10=$(post /ipam/ip-addresses/ "{\"address\":\"10.10.10.1/24\",\"status\":\"active\",\"assigned_object_type\":\"dcim.interface\",\"assigned_object_id\":$IF_V10,\"description\":\"ゲートウェイ\"}")
post /ipam/ip-addresses/ "{\"address\":\"10.10.20.1/24\",\"status\":\"active\",\"assigned_object_type\":\"dcim.interface\",\"assigned_object_id\":$IF_V20,\"description\":\"ゲートウェイ\"}" >/dev/null
IP_SRV=$(post /ipam/ip-addresses/ "{\"address\":\"10.10.10.10/24\",\"status\":\"active\",\"assigned_object_type\":\"dcim.interface\",\"assigned_object_id\":$IF_ETH0,\"dns_name\":\"srv-01.office.internal\",\"description\":\"ファイルサーバー\"}")
patch /dcim/devices/$SW/ "{\"primary_ip4\":$GW10}"
patch /dcim/devices/$SRV/ "{\"primary_ip4\":$IP_SRV}"
echo "site=$SITE prefix_servers=$P_SRV prefix_clients=$P_CLI switch=$SW server=$SRV"chmod +x seed.sh
export NETBOX_TOKEN='nbt_<キー>.<トークン>'
./seed.sh
./seed.sh # 2回目はわざと実行して、止まり方を確認するsite=2 prefix_servers=5 prefix_clients=6 switch=3 server=4
POST /dcim/sites/ failed: {"name":["site with this name already exists."],"slug":["site with this slug already exists."]}1回目は2.8秒で終わりました。表示されるIDは環境によって変わります(この出力は一度データを消してから流し直したときのものです)。2回目は最初の拠点の作成で止まり、以降は何も作られません。ただしトランザクションではないので、途中の10件目で失敗すれば9件目までは残ります。その状態で再実行すると最初の拠点で止まるため、重複はしない代わりに続きも作られません。流し直すときは、作成済みの分を画面で消すか、失敗した行から後ろだけを実行します。3.x向けの古いサンプルを流用するときは、プレフィックスの site が4.2で scope_type/scope_id に、機器の device_role が4.0で廃止されて role だけになった点に注意してください。
次の空きIPを払い出す:available-ips
ここからがExcelでは難しい部分です。プレフィックスごとに /available-ips/ というエンドポイントがあり、GETで空きの一覧、POSTで次の空きを確保して登録します。
NB=http://127.0.0.1:18000/api
H=(-H "Authorization: Bearer $NETBOX_TOKEN" -H 'Content-Type: application/json' -H 'Accept: application/json')
PFX=$(curl -s "${H[@]}" "$NB/ipam/prefixes/?prefix=10.10.10.0/24" | jq '.results[0].id')
# 空きを3つ見るだけ(何も確保しない)
curl -s "${H[@]}" "$NB/ipam/prefixes/$PFX/available-ips/?limit=3" | jq -r '.[].address'
# 次の空きを1つ確保して、NASとして登録する
curl -s "${H[@]}" -X POST "$NB/ipam/prefixes/$PFX/available-ips/" \
--data '{"status":"active","dns_name":"nas-01.office.internal","description":"NAS"}' \
| jq '{id, address, status: .status.value, dns_name}'
# クライアント側でプリンター2台分をまとめて確保する(配列で送る)
PCLI=$(curl -s "${H[@]}" "$NB/ipam/prefixes/?prefix=10.10.20.0/24" | jq '.results[0].id')
curl -s "${H[@]}" -X POST "$NB/ipam/prefixes/$PCLI/available-ips/" \
--data '[{"description":"printer-01"},{"description":"printer-02"}]' \
| jq -r '.[] | "\(.address) \(.description)"'10.10.10.11/24
10.10.10.12/24
10.10.10.13/24
{
"id": 15,
"address": "10.10.10.11/24",
"status": "active",
"dns_name": "nas-01.office.internal"
}
10.10.20.2/24 printer-01
10.10.20.3/24 printer-02サーバー側の最初の空きが .2 ではなく .11 なのは、.1 がゲートウェイ、.2〜.9 が予約レンジ、.10 がsrv-01で埋まっているからです。複数の担当者やスクリプトが同時に払い出しても重ならないよう、NetBoxはこの処理をPostgreSQLのアドバイザリロック(netbox/constants.py で available-ips に割り当てられたキー100200)で直列化しています。実際に5本のPOSTを同時に投げて確かめました。
for i in 1 2 3 4 5; do
curl -s "${H[@]}" -X POST "$NB/ipam/prefixes/$PFX/available-ips/" \
--data "{\"status\":\"reserved\",\"description\":\"parallel-$i\"}" | jq -r .address &
done; wait
# 試験用に作った5件を一括削除する
curl -s "${H[@]}" "$NB/ipam/ip-addresses/?description__ic=parallel" | jq -c '[.results[] | {id}]' > ids.json
curl -s -o /dev/null -w '%{http_code}\n' "${H[@]}" -X DELETE "$NB/ipam/ip-addresses/" --data @ids.json10.10.10.12/24
10.10.10.13/24
10.10.10.14/24
10.10.10.15/24
10.10.10.16/24
2045本とも別々のアドレスが返り、重複はありませんでした。Excelで同じことをすると、2人が同時に「次の空き」を見て同じ行を使う事故が起きます。一括削除は {"id": …} の配列を本文にしたDELETEで、応答は204です。
IPアドレス範囲の2つのフラグは、払い出しと使用率の計算を別々に制御します。mark_populated をtrueにした範囲は、全アドレスが埋まっているものとして扱われ、空きの計算から外れ、範囲内への個別登録も拒否されます。mark_utilized をtrueにした範囲は、使用率の計算で全部使用中として数えます。プレフィックス一覧の使用率は、予約レンジ(mark_populated だけtrue)を持つ10.10.10.0/24が1.2%(3/254)、DHCPプール(両方true)を持つ10.10.20.0/24が40.6%(プール100+ゲートウェイ1+プリンター2で103/254)でした。判断基準は、DHCPプールは両方true、将来用に空けておく予約は mark_populated だけtrue、ラベルとして範囲を書いておきたいだけなら両方falseです。
重複と入力ミスはどこまで弾かれるか
台帳の信頼性は「間違った入力をどれだけ拒否するか」で決まるので、よくあるミスを実際にAPIで投げて応答を確かめました。VRFを使っていない状態(NetBoxの表示では「Global」)での結果です。
| 試した操作 | 結果 | NetBoxの応答 |
|---|---|---|
| 登録済みの10.10.10.10/24をもう一度登録 | 400 | Duplicate IP address found in global table: 10.10.10.10/24 |
| 同じアドレスを10.10.10.10/32で登録 | 400 | 上と同じ。マスク長が違っても同じホストとして検出 |
| 予約レンジ内の10.10.10.5/24を登録 | 400 | Cannot create IP address 10.10.10.5/24 inside range 10.10.10.2-9/24. |
| 10.10.10.0/24をもう一度登録 | 400 | Duplicate prefix found in global table: 10.10.10.0/24 |
| DHCPプールと重なる .150〜.160 のレンジを登録 | 400 | Defined addresses overlap with range 10.10.20.100-199/24 in VRF None |
| VLAN ID 4095を登録 | 400 | Ensure this value is less than or equal to 4094. |
| VLANグループなしで、同じ拠点にVID 10をもう1つ登録 | 201 | 作成されてしまう |
| VLANグループ内でVID 10をもう1つ登録 | 400 | VLAN with this Group and VLAN ID already exists. |
IPとプレフィックスの重複は、ENFORCE_GLOBAL_UNIQUE の既定値がTrueなので何も設定しなくても拒否されます。拠点ごとにVRFを分けて同じアドレス帯を使う設計にした場合は、VRFごとの enforce_unique(日本語画面では「重複を禁止する」、既定はオン)で重複の扱いが決まります。見落としやすいのがVLANで、VIDの重複を検査する単位はVLANグループです。拠点にだけ紐づけたVLANは同じVIDをいくつでも作れてしまうので、VLANを作る前にスイッチのL2ドメインごとにVLANグループを作ってください。seed.shもその順番にしてあります。
バックアップと復元の確認
台帳の中身はPostgreSQLにあります。pg_dump のカスタム形式(-Fc)で取り、別名のDBに戻して件数を数えるところまでを1セットにしておくと、「取れているつもり」のバックアップを防げます。
docker compose exec -T postgres pg_dump -U netbox -d netbox -Fc > netbox-$(date +%F).dump
ls -lh netbox-*.dump
# 復元テスト:別名のDBに戻して件数を数え、終わったら消す
docker compose exec -T postgres createdb -U netbox netbox_restore_test
docker compose exec -T postgres pg_restore -U netbox -d netbox_restore_test --no-owner < netbox-$(date +%F).dump
docker compose exec -T postgres psql -U netbox -d netbox_restore_test -Atc "select count(*) from ipam_ipaddress"
docker compose exec -T postgres dropdb -U netbox netbox_restore_testこの記事のデータ量ではダンプは1.2MBで、復元先のIPアドレスは6件(ゲートウェイ2、srv-01、nas-01、プリンター2)と元のDBに一致しました。ダンプとは別に .env も必ず保管します。API_TOKEN_PEPPER_1 を失うと、DBを戻してもv2トークンのハッシュを照合できず、全トークンの再発行になります。機器の写真などを添付する運用なら、netbox-media ボリュームもバックアップ対象に加えます。
イメージの版を上げるときは、先にダンプを取ってからタグを書き換えます。起動時に migrate --check が未適用のマイグレーションを見つけると自動で適用するため、新しい版で一度起動したDBは新しいスキーマに変わっています。古いイメージに戻しても動く保証はないので、戻すときはダンプからの復元になります。
つまずきポイント
「Waiting on DB...」が続き、30秒で止まる
イメージの起動スクリプトは manage.py showmigrations が成功するまでを「DB待ち」として扱うため、DBと関係のない設定エラーでも同じ表示になります。SECRET_KEY を短くして起動したときのログは次のとおりで、最後の行はDBの話ではなく鍵の作り方のヒントです。
python /opt/netbox/netbox/generate_secret_key.py
[ Use DB_WAIT_DEBUG=1 in netbox.env to print full traceback for errors here ]
⏳ Waiting on DB... (27s / 30s)
❌ Waited 30s or more for the DB to become ready.起動スクリプトはエラーの最終行しか表示しません。SECRET_KEY のエラーは2行に分かれていて、最終行が「鍵の生成コマンド」のほうなので、原因が見えなくなります。環境変数 DB_WAIT_DEBUG=1 を足すとトレースバック全体が出て、ImproperlyConfigured: SECRET_KEY must be at least 50 characters in length. が原因だと分かります。API_TOKEN_PEPPER_1 が短い場合は1行のエラーなので、Invalid pepper 1: Pepper must be at least 50 characters in length. がそのまま待機表示の前に出ます。本当にDBのパスワードが違う場合は、待機の直前に FATAL: password authentication failed for user "netbox" が出ます。公式postgresイメージの POSTGRES_PASSWORD はデータディレクトリが空のときにしか使われないので、初回起動後に .env の DB_PASSWORD だけ変えるとこの状態になります。構築直後なら docker compose down -v で作り直し、データがあるなら ALTER USER netbox PASSWORD '…'; でDB側を合わせます。
起動途中で (unhealthy) になる
初回のマイグレーション中にヘルスチェックの猶予が切れた状態です。docker compose ps で Up 8 minutes (unhealthy) のように見えます。ログに ⚙️ Applying database migrations が出たまま進んでいるなら、待てば ✅ Initialisation is done. のあとにhealthyへ戻ります(1回目の試行でもそうなりました)。unhealthyの間は、service_healthy を条件にしたワーカーが起動されていないことがあるので、healthyに戻ったら docker compose up -d をもう一度実行し、docker compose ps で netbox-worker が Up になったことを確かめます。根本的な対策は、start_period を初回起動の実測より長く(この記事では600秒)しておくことです。
管理者のAPIトークンが作られない、provisionが500を返す
API_TOKEN_PEPPER_1 を入れずに起動すると、起動ログに ⚠️ No API token was created as API_TOKEN_PEPPERS is not set が出て、トークン発行のAPIは次のように500を返します。
{"error": "API_TOKEN_PEPPERS is not defined", "exception": "ValueError", "netbox_version": "4.7.1-Docker-5.1.1", "python_version": "3.14.4"}ペッパーを足してコンテナを作り直せば発行できるようになりますが、管理者のトークンは自動では作られません。管理者の作成処理は「そのユーザーが既にいれば何もしない」作りなので、2回目以降の起動では SUPERUSER_API_TOKEN が読まれないからです。前の節の provision で発行し直してください。
ログイン画面が「Server Error」になる
コンテナは起動しているのに画面が500になる場合は、Redisを疑います。REDIS_HOST を存在しないホスト名にして試すと、エラー画面に redis.exceptions.ConnectionError と Error -3 connecting to redis-cache:6379. Temporary failure in name resolution. が表示されました。パスワードが違うときの例外は redis.exceptions.AuthenticationError: invalid username-password pair or user is disabled. でした(manage.py から接続して確認)。ログイン画面を返す処理の中でもRedisに接続するため、DBが正常でも最初の画面から失敗し、ヘルスチェック(/login/ へのcurl)も通らなくなります。compose.yamlでは REDIS_PASSWORD と REDIS_CACHE_PASSWORD に同じ変数を入れているので、.env を1か所直せば両方がそろいます。
動作確認
4つのサービスの状態、APIの応答、台帳の中身の順に確かめます。NETBOX_TOKEN には前の節で作ったトークンを入れておきます。
docker compose ps --format 'table {{.Service}}\t{{.Status}}'
curl -s -H "Authorization: Bearer $NETBOX_TOKEN" http://127.0.0.1:18000/api/status/ \
| jq '{"netbox-version", "rq-workers-running"}'
curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:18000/api/ipam/prefixes/SERVICE STATUS
netbox Up 12 minutes (healthy)
netbox-worker Up 13 minutes
postgres Up 5 hours (healthy)
redis Up 5 hours (healthy)
{
"netbox-version": "4.7.1",
"rq-workers-running": 1
}
403rq-workers-running が0ならワーカーが動いていません。トークンなしのリクエストが403になることで、未ログインでは台帳を読めない状態も確認できます。
続いて台帳の中身です。1つ目はサーバー用プレフィックスのIPを割り当て先の機器と一緒に一覧にし、2つ目はDNS名が入っているアドレスを /etc/hosts 形式で書き出します。2つ目の形にしておけば、台帳からDNSやDHCPの設定ファイルを生成する処理にそのままつなげられます。
curl -s -H "Authorization: Bearer $NETBOX_TOKEN" \
"http://127.0.0.1:18000/api/ipam/ip-addresses/?parent=10.10.10.0/24" \
| jq -r '.results[] | [.address, .status.value, (.assigned_object.device.name // "-"), (.dns_name | if . == "" then "-" else . end)] | @tsv'
curl -s -H "Authorization: Bearer $NETBOX_TOKEN" \
"http://127.0.0.1:18000/api/ipam/ip-addresses/?parent=10.10.0.0/16&status=active&dns_name__empty=false&limit=0" \
| jq -r '.results[] | "\(.address | split("/")[0])\t\(.dns_name)"'10.10.10.1/24 active sw-01 -
10.10.10.10/24 active srv-01 srv-01.office.internal
10.10.10.11/24 active - nas-01.office.internal
10.10.10.10 srv-01.office.internal
10.10.10.11 nas-01.office.internal画面では「IPAM」→「プレフィックス」→「10.10.10.0/24」→「IP アドレス」タブで、ゲートウェイ、予約レンジ、srv-01、nas-01の下に「243 IP が利用可能」と表示されれば、ここまでの登録がすべて反映されています(使えるアドレス254から、ゲートウェイ1、予約8、サーバーとNASの2を引いた数です)。
よくある質問
NetBoxで何ができますか?
IPアドレス・プレフィックス・VLAN・VRFの管理(IPAM)と、拠点・ラック・機器・インターフェース・ケーブルの管理(DCIM)を1つのデータベースで行えます。すべての操作にREST APIがあり、変更履歴も残ります。監視、ネットワークの自動検出、DHCPやDNSの配布機能は持たないので、それらは別のツールと組み合わせます。
phpIPAMとNetBoxの違いは何ですか?
phpIPAMはIPアドレス管理が中心で、サブネットのスキャンで使用中のIPを拾えます。一方のNetBoxはスキャンを持たない代わりに、機器・インターフェース・配線までを台帳にし、IPをインターフェースに割り当てて関係をたどれます。IPだけを軽く管理したいならphpIPAM、機器構成や自動化の起点にしたいならNetBoxが向きます。
NetBoxでIPアドレス管理はできますか?
できます。プレフィックスの階層、予約やDHCPプールを表すIPアドレス範囲、重複の自動拒否、使用率の表示、APIによる次の空きIPの払い出しまでそろっています。ただしDHCPサーバーではないので、実際の配布は既存のDHCPサーバーが行い、NetBoxは設計と割り当ての記録を持つ形になります。
オープンソース版のNetBoxに費用はかかりますか?
オープンソース版はApache License 2.0で、ライセンス費用はかかりません。必要なのは動かすサーバーの費用と運用の手間で、この記事の構成ではメモリを約0.9GB使いました。開発元が提供する有償のクラウド版などを検討する場合は、価格と含まれる機能を公式サイトで確認してください(執筆時点)。
まとめ
NetBox 4.7.1はcompose.yamlと .env の2ファイルで起動でき、初回は約8分のマイグレーションを start_period: 600s で待てば、あとの再起動は35秒でした。登録は拠点→VLANグループ→VLAN→プレフィックス→機器→IPの順にし、空きIPは available-ips に任せれば同時に払い出しても重なりません。次にやることは、今使っているExcel台帳のうち1つのサブネットだけを seed.sh の形に書き換えて流し込み、「IP アドレス」タブの空き数がExcelの認識と一致するかを確かめることです。