閉域環境のNetBox構築:Rocky Linux 8.10とPodmanコンテナバンドル
EdwardMoon
閉域環境のNetBox構築では、オンラインサーバーでパッケージとOCIイメージを集め、完全性を検証してオフラインサーバーへ搬入します。イメージだけをコピーすると、Podman依存RPM、tag、DB初期化、セキュリティ、自動起動で失敗しやすくなります。
対象はRocky Linux 8.10 x86_64のrootful Podmanです。2026年7月20日に確認した例はNetBox v4.6.5-5.0.2、PostgreSQL 18-alpine、Valkey 9.1-alpineです。本番搬入前に公式リリースとregistry digestを必ず再確認してください。

前提と検証基準
- 作成元と対象のRockyマイナー版、CPU、有効リポジトリをそろえる。
latestではなく正確な版のtagを使い、digestを記録する。- RPMは
--resolve --alldepsでインストール済み依存も取得する。 - 同一条件の試験サーバーでオフライン導入を再現する。
- rootfulとrootlessを混用しない。閉域側はすべて
sudo podmanで統一する。
cat /etc/rocky-release
uname -m
sudo dnf repolist --enabled
sudo podman --version
| 要素 | 固定値 | 検証点 |
|---|---|---|
| NetBox Docker | v4.6.5-5.0.2 | NetBoxとnetbox-dockerの互換tag |
| PostgreSQL | 18-alpine | 既存volumeとのメジャー互換性 |
| Valkey | 9.1-alpine | tasksとcacheを分離 |
| ホスト | Rocky Linux 8.10 | 同じCPU、リポジトリ、セキュリティ方針 |
アーキテクチャ
Web、worker、PostgreSQL、tasks用Valkey、cache用Valkeyを1つのPodに入れます。同じPodはネットワークnamespaceを共有するため、127.0.0.1でDBとValkeyへ接続できます。外部にはNetBoxの8080だけをホストの8000へ公開します。
netbox-pod
├── netbox 127.0.0.1:8080 ← host:8000
├── netbox-worker RQ worker
├── netbox-postgres 127.0.0.1:5432
├── netbox-valkey 127.0.0.1:6379 (tasks)
└── netbox-valkey-cache 127.0.0.1:6380 (cache)
ネットワークの概念に不慣れなら、コンテナの概念演習を参照してください。
オンラインでバンドルを作成する
1:バージョンとディレクトリを固定する
搬入後に同じtagを再現できるよう変数へ固定します。ファイル名に版とCPUを含めると、複数バンドルの取り違えを減らせます。
NETBOX_TAG="v4.6.5-5.0.2"
POSTGRES_TAG="18-alpine"
VALKEY_TAG="9.1-alpine"
BUNDLE_ID="netbox-airgap-${NETBOX_TAG}-rocky8-$(uname -m)"
BUNDLE_ROOT="/opt/${BUNDLE_ID}"
sudo mkdir -p "${BUNDLE_ROOT}"/{rpms,images,env,scripts,manifests}
sudo chown -R "$(id -u):$(id -g)" "${BUNDLE_ROOT}"
2:Podman RPMと依存関係を集める
dnf download --resolveだけでは作成元に導入済みのパッケージを省く場合があります。閉域用では--alldepsも使います。有効リポジトリが異なると署名・依存問題が起こるため、一覧もmanifestへ保存します。
sudo dnf install -y dnf-plugins-core
sudo dnf download --resolve --alldeps --destdir="${BUNDLE_ROOT}/rpms" podman tar gzip openssl
sudo dnf repolist --enabled > "${BUNDLE_ROOT}/manifests/dnf-repolist.txt"
rpm -Kv "${BUNDLE_ROOT}"/rpms/*.rpm > "${BUNDLE_ROOT}/manifests/rpm-signatures.txt"
3:正確なtagをPull・Saveする
NetBox DockerのtagはNetBox版と支援ファイル版を組み合わせています。正確なtagを使い、Pull直後のdigestとimage IDを記録します。podman saveは層とtagを保持し、FSだけを平坦化するpodman exportとは異なります。
podman pull "docker.io/netboxcommunity/netbox:${NETBOX_TAG}"
podman pull "docker.io/postgres:${POSTGRES_TAG}"
podman pull "docker.io/valkey/valkey:${VALKEY_TAG}"
{
podman image inspect --format 'netbox {{.Digest}} {{.Id}}' "docker.io/netboxcommunity/netbox:${NETBOX_TAG}"
podman image inspect --format 'postgres {{.Digest}} {{.Id}}' "docker.io/postgres:${POSTGRES_TAG}"
podman image inspect --format 'valkey {{.Digest}} {{.Id}}' "docker.io/valkey/valkey:${VALKEY_TAG}"
} > "${BUNDLE_ROOT}/manifests/image-digests.txt"
podman save --format oci-archive -o "${BUNDLE_ROOT}/images/netbox.tar" "docker.io/netboxcommunity/netbox:${NETBOX_TAG}"
podman save --format oci-archive -o "${BUNDLE_ROOT}/images/postgres.tar" "docker.io/postgres:${POSTGRES_TAG}"
podman save --format oci-archive -o "${BUNDLE_ROOT}/images/valkey.tar" "docker.io/valkey/valkey:${VALKEY_TAG}"
4:既定パスワードを使わず環境ファイルを作る
例示パスワードを公開・再利用しないでください。次は作成時に乱数を生成し、権限を600に制限します。ALLOWED_HOSTSの名前とアドレスを実環境へ変更してください。
umask 077
DB_PASSWORD="$(openssl rand -hex 24)"
VALKEY_PASSWORD="$(openssl rand -hex 24)"
VALKEY_CACHE_PASSWORD="$(openssl rand -hex 24)"
ADMIN_PASSWORD="$(openssl rand -hex 18)"
API_TOKEN_PEPPER_1="$(openssl rand -hex 32)"
SECRET_KEY="$(podman run --rm "docker.io/netboxcommunity/netbox:${NETBOX_TAG}" python /opt/netbox/netbox/generate_secret_key.py)"
cat > "${BUNDLE_ROOT}/env/postgres.env" << EOF
POSTGRES_DB=netbox
POSTGRES_USER=netbox
POSTGRES_PASSWORD=${DB_PASSWORD}
EOF
cat > "${BUNDLE_ROOT}/env/valkey.env" << EOF
VALKEY_PASSWORD=${VALKEY_PASSWORD}
EOF
cat > "${BUNDLE_ROOT}/env/valkey-cache.env" << EOF
VALKEY_PASSWORD=${VALKEY_CACHE_PASSWORD}
EOF
cat > "${BUNDLE_ROOT}/env/netbox.env" << EOF
DB_HOST=127.0.0.1
DB_PORT=5432
DB_NAME=netbox
DB_USER=netbox
DB_PASSWORD=${DB_PASSWORD}
REDIS_HOST=127.0.0.1
REDIS_PORT=6379
REDIS_DATABASE=0
REDIS_PASSWORD=${VALKEY_PASSWORD}
REDIS_SSL=false
REDIS_CACHE_HOST=127.0.0.1
REDIS_CACHE_PORT=6380
REDIS_CACHE_DATABASE=1
REDIS_CACHE_PASSWORD=${VALKEY_CACHE_PASSWORD}
REDIS_CACHE_SSL=false
SECRET_KEY=${SECRET_KEY}
API_TOKEN_PEPPER_1=${API_TOKEN_PEPPER_1}
ALLOWED_HOSTS=netbox.internal 10.20.30.40 localhost
TIME_ZONE=Asia/Seoul
LOGIN_REQUIRED=true
ISOLATED_DEPLOYMENT=true
CENSUS_REPORTING_ENABLED=false
COPILOT_ENABLED=false
SKIP_SUPERUSER=false
SUPERUSER_NAME=admin
SUPERUSER_EMAIL=netbox-admin@example.internal
SUPERUSER_PASSWORD=${ADMIN_PASSWORD}
EOF
chmod 600 "${BUNDLE_ROOT}"/env/*
printf 'admin password: %s
' "${ADMIN_PASSWORD}" > "${BUNDLE_ROOT}/env/initial-admin-password.txt"
chmod 600 "${BUNDLE_ROOT}/env/initial-admin-password.txt"
初回ログイン後は管理者パスワードを変更し、netbox.envのSUPERUSER_PASSWORDを除いてSKIP_SUPERUSER=trueへ変更すると安全です。公式変数名はSUPERUSER_USERNAMEではなくSUPERUSER_NAMEです。
5:起動スクリプト
PostgreSQLと両Valkeyの準備完了後にWebとworkerを起動します。公式構成に合わせてnetbox:rootで実行し、workerでは管理者生成を省きます。
cat > "${BUNDLE_ROOT}/scripts/run-netbox-pod.sh" << 'SCRIPT'
#!/usr/bin/env bash
set -euo pipefail
NETBOX_TAG="v4.6.5-5.0.2"
POSTGRES_TAG="18-alpine"
VALKEY_TAG="9.1-alpine"
BUNDLE_ROOT="/opt/netbox-airgap-${NETBOX_TAG}-rocky8-$(uname -m)"
NETBOX_ENV="${BUNDLE_ROOT}/env/netbox.env"
PG_ENV="${BUNDLE_ROOT}/env/postgres.env"
VALKEY_ENV="${BUNDLE_ROOT}/env/valkey.env"
VALKEY_CACHE_ENV="${BUNDLE_ROOT}/env/valkey-cache.env"
for file in "${NETBOX_ENV}" "${PG_ENV}" "${VALKEY_ENV}" "${VALKEY_CACHE_ENV}"; do
[[ -r "${file}" ]] || { echo "[ERROR] missing ${file}" >&2; exit 1; }
done
if ! podman pod exists netbox-pod; then
podman pod create --name netbox-pod -p 8000:8080
fi
for volume in netbox-postgres netbox-valkey-data netbox-valkey-cache-data netbox-media-files netbox-reports-files netbox-scripts-files; do
podman volume inspect "${volume}" >/dev/null 2>&1 || podman volume create "${volume}"
done
if ! podman container exists netbox-postgres; then
podman run -d --name netbox-postgres --pod netbox-pod --env-file "${PG_ENV}" -v netbox-postgres:/var/lib/postgresql "docker.io/postgres:${POSTGRES_TAG}"
fi
if ! podman container exists netbox-valkey; then
podman run -d --name netbox-valkey --pod netbox-pod --env-file "${VALKEY_ENV}" -v netbox-valkey-data:/data "docker.io/valkey/valkey:${VALKEY_TAG}" sh -c 'valkey-server --appendonly yes --port 6379 --requirepass "$VALKEY_PASSWORD"'
fi
if ! podman container exists netbox-valkey-cache; then
podman run -d --name netbox-valkey-cache --pod netbox-pod --env-file "${VALKEY_CACHE_ENV}" -v netbox-valkey-cache-data:/data "docker.io/valkey/valkey:${VALKEY_TAG}" sh -c 'valkey-server --port 6380 --requirepass "$VALKEY_PASSWORD"'
fi
dependencies_ready=0
for attempt in $(seq 1 90); do
if podman exec netbox-postgres sh -c 'pg_isready -q -t 2 -d "$POSTGRES_DB" -U "$POSTGRES_USER"' && podman exec netbox-valkey sh -c 'valkey-cli --pass "$VALKEY_PASSWORD" -p 6379 ping 2>/dev/null | grep -qx PONG' && podman exec netbox-valkey-cache sh -c 'valkey-cli --pass "$VALKEY_PASSWORD" -p 6380 ping 2>/dev/null | grep -qx PONG'; then
dependencies_ready=1
break
fi
sleep 2
done
[[ "${dependencies_ready}" -eq 1 ]] || {
echo "[ERROR] PostgreSQL or Valkey did not become ready" >&2
exit 1
}
if ! podman container exists netbox; then
podman run -d --name netbox --pod netbox-pod --user netbox:root --env-file "${NETBOX_ENV}" -v netbox-media-files:/opt/netbox/netbox/media -v netbox-reports-files:/opt/netbox/netbox/reports -v netbox-scripts-files:/opt/netbox/netbox/scripts "docker.io/netboxcommunity/netbox:${NETBOX_TAG}"
fi
netbox_ready=0
for attempt in $(seq 1 120); do
if podman exec netbox curl -fsS http://127.0.0.1:8080/login/ >/dev/null 2>&1; then
netbox_ready=1
break
fi
sleep 2
done
[[ "${netbox_ready}" -eq 1 ]] || {
echo "[ERROR] NetBox did not become ready" >&2
podman logs --tail 100 netbox >&2
exit 1
}
if ! podman container exists netbox-worker; then
podman run -d --name netbox-worker --pod netbox-pod --user netbox:root --env-file "${NETBOX_ENV}" -e SKIP_SUPERUSER=true -v netbox-media-files:/opt/netbox/netbox/media -v netbox-reports-files:/opt/netbox/netbox/reports -v netbox-scripts-files:/opt/netbox/netbox/scripts "docker.io/netboxcommunity/netbox:${NETBOX_TAG}" /opt/netbox/venv/bin/python /opt/netbox/netbox/manage.py rqworker
fi
podman pod ps
podman ps --pod
echo "NetBox: http://<server-ip>:8000"
SCRIPT
chmod 700 "${BUNDLE_ROOT}/scripts/run-netbox-pod.sh"
6:データを保持する停止・初期化スクリプト
名前にpostgresやredisを含む全コンテナとvolumeを消すと、他サービスのデータも失う場合があります。次は正確なNetBoxリソースだけを対象にし、既定ではデータvolumeを保持します。
cat > "${BUNDLE_ROOT}/scripts/reset-netbox-pod.sh" << 'SCRIPT'
#!/usr/bin/env bash
set -euo pipefail
mode="${1:-keep-data}"
podman pod rm -f netbox-pod 2>/dev/null || true
podman rm -f netbox netbox-worker netbox-postgres netbox-valkey netbox-valkey-cache 2>/dev/null || true
if [[ "${mode}" == "--delete-data" ]]; then
read -r -p '데이터를 완전히 삭제하려면 DELETE-NETBOX-DATA 입력: ' answer
[[ "${answer}" == "DELETE-NETBOX-DATA" ]] || {
echo "취소했습니다."
exit 1
}
for volume in netbox-postgres netbox-valkey-data netbox-valkey-cache-data netbox-media-files netbox-reports-files netbox-scripts-files; do
podman volume rm -f "${volume}" 2>/dev/null || true
done
else
echo "컨테이너만 제거했습니다. 데이터 볼륨은 보존됩니다."
fi
SCRIPT
chmod 700 "${BUNDLE_ROOT}/scripts/reset-netbox-pod.sh"
7:SHA-256 manifestと圧縮ファイル
chmod 600 "${BUNDLE_ROOT}"/env/*
(
cd "${BUNDLE_ROOT}"
find rpms images env scripts manifests -type f -print0 | sort -z | xargs -0 sha256sum > SHA256SUMS
)
cd /opt
sudo tar --xattrs --selinux -czf "${BUNDLE_ID}.tar.gz" "${BUNDLE_ID}"
sudo sha256sum "${BUNDLE_ID}.tar.gz" > "${BUNDLE_ID}.tar.gz.sha256"
オフラインでインストールする
1:外側と内部ファイルの完全性を検証する
展開前に外側のSHA-256、展開後に内部manifestを検証します。1つでも失敗したら導入せず、媒体と原本を確認してください。
cd /opt
sudo sha256sum -c netbox-airgap-v4.6.5-5.0.2-rocky8-x86_64.tar.gz.sha256
sudo tar --xattrs --selinux -xzf netbox-airgap-v4.6.5-5.0.2-rocky8-x86_64.tar.gz
cd /opt/netbox-airgap-v4.6.5-5.0.2-rocky8-x86_64
sudo sha256sum -c SHA256SUMS
sudo chown -R root:root .
sudo chmod 600 env/*
2:外部リポジトリなしでPodmanを導入する
cd /opt/netbox-airgap-v4.6.5-5.0.2-rocky8-x86_64
sudo dnf install -y --disablerepo='*' ./rpms/*.rpm
sudo podman --version
依存エラー時は閉域側で任意のRPMを追加せず、作成元のOS、CPU、有効リポジトリが対象と同じか確認して作り直します。
3:OCIをロードしてtagを確認する
cd /opt/netbox-airgap-v4.6.5-5.0.2-rocky8-x86_64
sudo podman load -i images/netbox.tar
sudo podman load -i images/postgres.tar
sudo podman load -i images/valkey.tar
sudo podman images --digests | grep -E 'netboxcommunity/netbox|postgres|valkey/valkey'
cat manifests/image-digests.txt
manifestと一致しなければ起動しません。rootlessでロードしたイメージはsudo podmanに見えないため、ロードと実行ユーザーを合わせます。
4:ファイアウォールを制限して初回起動する
例は管理網10.20.0.0/16からTCP 8000だけを許可します。実管理網へ変更してください。先にファイアウォールを設定してから起動すると、ネットワーク再適用の問題を減らせます。
sudo firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="10.20.0.0/16" port protocol="tcp" port="8000" accept'
sudo firewall-cmd --reload
cd /opt/netbox-airgap-v4.6.5-5.0.2-rocky8-x86_64
sudo ./scripts/run-netbox-pod.sh
curl -fsS http://127.0.0.1:8000/login/ -o /dev/null
sudo podman pod ps
sudo podman ps --pod
ブラウザーからhttp://<閉域サーバーIP>:8000へ接続します。長期運用では8000を広く公開せず、内部リバースプロキシとTLSを使う方が安全です。
再起動後の自動開始
systemdへ移すまでは起動スクリプトは新規リソース作成用です。同名の停止コンテナがあれば、先にsudo podman pod start netbox-podで開始します。systemd移行後はsystemctlへ統一し、初期化前にsudo systemctl stop pod-netbox-pod.serviceで管理サービスを止めます。
Rocky Linux 8.10のPodmanでは生成したsystemd unitを利用できます。--newの生成はベストエフォートなので、必ず内容を確認して導入します。Podman 5系ではこのコマンドは非推奨のため、新規基盤ではQuadletを優先して検討します。
unit_dir="$(mktemp -d)"
cd "${unit_dir}"
sudo podman generate systemd --new --files --name netbox-pod
sudo install -m 0644 ./*.service /etc/systemd/system/
sudo restorecon -Rv /etc/systemd/system
sudo podman pod rm -f netbox-pod
sudo systemctl daemon-reload
sudo systemctl enable --now pod-netbox-pod.service
sudo systemctl status pod-netbox-pod.service --no-pager
セキュリティ確認
ALLOWED_HOSTS=*を使わず、実FQDN・IPとlocalhostを指定する。- 既定パスワードを避け乱数を生成し、環境・バックアップファイルを
600にする。 - 初回後に管理者パスワードを変更し、環境ファイルの初期値を除く。
- 8000は管理網だけに許可し、可能なら内部TLSプロキシを使う。
- tagだけでなくdigestと全バンドルのSHA-256を承認記録へ残す。
- 初期化は正確なNetBoxリソースだけを消し、全体の
pruneを自動実行しない。
状態確認と障害対応
基本状態とログ
sudo podman pod ps
sudo podman ps -a --pod
sudo podman logs --tail 100 netbox
sudo podman logs --tail 100 netbox-worker
sudo podman logs --tail 100 netbox-postgres
sudo podman logs --tail 100 netbox-valkey
sudo podman logs --tail 100 netbox-valkey-cache
sudo podman exec netbox /opt/netbox/venv/bin/python /opt/netbox/netbox/manage.py version
ログでルートが満杯なら無闇にコンテナを削除せず、No space left on deviceの診断でブロック、inode、開いたままの削除ファイルを切り分けます。
よくあるエラー
| 症状 | 最初の確認 | 対処 |
|---|---|---|
| DB待機後にNetBox終了 | PostgreSQLログとDB_* | 同じPodの127.0.0.1:5432か確認 |
| 400 Bad Request | ALLOWED_HOSTS | 実FQDN・IPを空白区切りで追加 |
| 初期adminログイン失敗 | SUPERUSER_NAME | 変数名と初回起動ログを確認 |
| sudoでイメージが見えない | rootlessとrootfulの混用 | sudo podman loadで再ロード |
| firewall reload後に接続不能 | Podmanネットワークルール | sudo podman network reload --all |
| ローカルRPMの依存エラー | OS、CPU、リポジトリ | 同条件で--alldepsを使い再作成 |
sudo podman exec netbox-postgres sh -c 'pg_isready -d "$POSTGRES_DB" -U "$POSTGRES_USER"'
sudo podman exec netbox-valkey sh -c 'valkey-cli --pass "$VALKEY_PASSWORD" -p 6379 ping'
sudo podman network reload --all
マイグレーションはNetBox起動中に行われます。手動のmanage.py migrateは、DBバックアップとイメージ互換性を確認した後の最終手段にしてください。
バックアップと復旧
更新・再配布前にPostgreSQL dumpとmediaを一緒に保存します。Redis・Valkeyはキューとキャッシュなので主要原本はDBとmediaですが、復旧目標に応じてscriptsとreportsも保管します。
# アプリケーションの書き込みを止めた変更時間帯に、DBとmediaを1セットで保存します。
sudo bash -euo pipefail <<'BASH'
backup_dir="/var/backups/netbox/$(date +%F-%H%M%S)"
install -d -m 0700 "$backup_dir"
umask 077
podman exec netbox-postgres pg_dump -U netbox -d netbox -Fc > "$backup_dir/netbox.dump"
test -s "$backup_dir/netbox.dump"
podman exec -i netbox-postgres pg_restore --list < "$backup_dir/netbox.dump" > "$backup_dir/dump-contents.txt"
podman volume export --output "$backup_dir/netbox-media-files.tar" netbox-media-files
(cd "$backup_dir" && sha256sum netbox.dump netbox-media-files.tar dump-contents.txt > SHA256SUMS && sha256sum -c SHA256SUMS)
BASH
作成だけでは不十分です。別の試験サーバーでDB復元と添付アクセスを定期検証してください。
よくある質問
Podを使う理由は何ですか
namespaceを共有してDBとValkeyへ127.0.0.1で接続でき、外部公開ポートもまとめて管理できるためです。リソース分離や独立した拡張を重視するなら、別Podやオーケストレーターを検討します。
RPM作成サーバーの条件は何ですか
同じRocky Linux 8.10、CPU、有効リポジトリが最も安全です。作成元の導入済み依存も含むよう--alldepsを使います。
latestを使ってもよいですか
推奨しません。同じ名前が別イメージへ移り、承認済みバンドルの再現が難しくなります。正確なtagとdigestを記録し、更新は新バンドルとして分けて検証します。
まとめ
安全な閉域NetBox構築の中心はコピーではなく、版の固定、同じRPM条件、digestとSHA-256、secret管理、正確な削除範囲、復元試験です。この順序で、インターネットのないRocky Linuxでも再現可能なPodman運用を行えます。