May 13, 2026

Baremetal Provisioning with Ironic

A practical operations guide for Bifrost, the lightweight standalone deployment of OpenStack Ironic. Covers installation, node enrollment, custom IPA initramfs, image building with disk-image-create, SW RAID, and end-to-end baremetal provisioning on Ubuntu 22.04.


Bifrost (Lightweight OpenStack Ironic) Operations Guide

OS Environment: Ubuntu 22.04.5 LTS (CLI)
Last Updated: April 2026

Table of Contents

  1. Overview
  2. System Update and Package Installation
  3. Downloading Bifrost
  4. Installing Bifrost
  5. Verifying Installation and Initial Setup
  6. Node Driver Configuration
  7. Nginx httpboot Configuration
  8. Building a Custom IPA initramfs
  9. Building a Deployment Image
  10. Deploying a Node
  11. SW RAID Setup and Teardown
  12. Undeploying a Node
  13. Appendix

1. Overview

Bifrost is a lightweight solution that lets you run Ironic — OpenStack's baremetal provisioning service — as a standalone component, without the rest of the OpenStack stack.
It gives you a complete baremetal deployment pipeline: PXE boot → IPA (Ironic Python Agent) → image deployment, all self-contained.

Key Components

Component Role
Ironic Baremetal provisioning engine
IPA (Ironic Python Agent) Agent running on the node (ramdisk)
Nginx (httpboot) Serves kernel and image files over HTTP
dnsmasq DHCP/TFTP for PXE booting

Node State Flow

PXE boot → enroll → manageable → available → deploying → active
State Description
enroll Initial state after auto-registration via PXE boot
manageable IPMI/Redfish connectivity verified; node is under management
available Ready for deployment (after cleaning completes)
deploying OS image deployment in progress
active Deployment complete; OS is running
cleaning Disk wipe in progress
clean failed Disk wipe failed
maintenance Maintenance mode (automatic power state sync disabled)

2. System Update and Package Installation

sudo apt update -y && sudo apt upgrade -y

sudo apt install -y \
  git \
  python3-pip \
  python3-venv \
  python3-dev \
  libffi-dev \
  libssl-dev \
  build-essential \
  libguestfs-tools \
  qemu-utils \
  diskimage-builder

3. Downloading Bifrost

git clone https://opendev.org/openstack/bifrost /opt/bifrost
cd /opt/bifrost

4. Installing Bifrost

Variable notation: Replace anything in <...> with values matching your environment.

4-1. VM Environment (No IPMI / manual-management)

./bifrost-cli install \
  --network-interface <NETWORK_INTERFACE> \      # e.g. enp1s0
  --dhcp-pool <DHCP_START_IP>-<DHCP_END_IP> \   # e.g. 192.168.122.100-192.168.122.200
  --hardware-types manual-management \
  -e enable_inspector=true \
  -e enable_inspector_discovery=true \
  -e inspector_rule_default_node_driver=manual-management \
  -e ironic_auth_strategy=noauth

4-2. Physical Baremetal Servers (IPMI / Redfish)

./bifrost-cli install \
  --network-interface <NETWORK_INTERFACE> \      # e.g. enp2s0
  --dhcp-pool <DHCP_START_IP>-<DHCP_END_IP> \   # e.g. 192.168.240.100-192.168.240.200
  --hardware-types ipmi,redfish \
  -e enable_inspector=true \
  -e enable_inspector_discovery=true \
  -e inspector_rule_default_node_driver=ipmi \
  -e default_boot_mode=uefi \
  -e ironic_auth_strategy=noauth

Important: Always include -e ironic_auth_strategy=noauth.
If omitted, Bifrost installs with http_basic auth mode, and all baremetal CLI commands will return 401 Unauthorized.

4-3. Creating clouds.yaml Before Installation

Creating this file beforehand prevents failures during the installation verification step.

mkdir -p ~/.config/openstack
cat > ~/.config/openstack/clouds.yaml << 'EOF'
clouds:
  bifrost:
    auth_type: none
    baremetal_endpoint_override: http://<CONTROLLER_IP>:6385
    ironic_inspector_endpoint_override: http://<CONTROLLER_IP>:5050
    region_name: RegionOne
EOF

5. Verifying Installation and Initial Setup

When installation completes successfully, you'll see:

Ironic is installed and running, try it yourself:
  source /opt/stack/bifrost/bin/activate
  export OS_CLOUD=bifrost
  baremetal driver list

5-1. Activating the Environment

source /opt/stack/bifrost/bin/activate
export OS_CLOUD=bifrost

# List available drivers
baremetal driver list

# List nodes (an empty list here is expected and normal)
baremetal node list

5-2. Checking Service Status

systemctl status ironic
systemctl status ironic-inspector
systemctl status dnsmasq
systemctl status nginx

5-3. Verifying noauth and Fixing It Manually

grep auth_strategy /etc/ironic/ironic.conf

If the value is http_basic or keystone, fix it manually:

sed -i 's/auth_strategy = .*/auth_strategy = noauth/' /etc/ironic/ironic.conf
systemctl restart ironic

5-4. Suppressing Warning Messages (Optional)

Running baremetal commands may surface Eventlet deprecation warnings. They don't affect functionality, but if you want to suppress them, add the following to .bashrc:

echo "export PYTHONWARNINGS=ignore" >> ~/.bashrc
source ~/.bashrc

6. Node Driver Configuration

6-1. IPMI Driver

NODE=<NODE_UUID>

baremetal node set $NODE \
  --driver ipmi \
  --driver-info ipmi_address=<IPMI_IP> \        # e.g. 192.168.1.100
  --driver-info ipmi_port=623 \
  --driver-info ipmi_username=<USERNAME> \       # e.g. ADMIN
  --driver-info ipmi_password=<PASSWORD>

6-2. Redfish Driver

Use this for servers that support Redfish — Supermicro, HPE iLO, Dell iDRAC, and so on.

NODE=<NODE_UUID>

baremetal node set $NODE \
  --driver redfish \
  --driver-info redfish_address=https://<BMC_IP> \           # e.g. https://192.168.1.100
  --driver-info redfish_username=<USERNAME> \                 # e.g. ADMIN
  --driver-info redfish_password=<PASSWORD> \
  --driver-info redfish_system_id=/redfish/v1/Systems/1 \
  --driver-info redfish_verify_ca=False \                     # Allow self-signed certificates
  --bios-interface redfish \
  --boot-interface redfish-virtual-media \
  --deploy-interface direct \
  --inspect-interface agent \
  --management-interface redfish \
  --power-interface redfish \
  --raid-interface agent \
  --vendor-interface no-vendor

Note: Use redfish_verify_ca=False, not redfish_verify_cafile=False.
Using the wrong parameter name will cause SSL certificate errors and put the node into a power failure state.

6-3. manual-management Driver (VM / Test Environments)

Use this when power control is not needed.

baremetal node set $NODE \
  --driver manual-management \
  --management-interface noop \
  --power-interface fake \
  --boot-interface ipxe \
  --deploy-interface direct \
  --inspect-interface no-inspect \
  --vendor-interface no-vendor \
  --raid-interface no-raid \
  --bios-interface no-bios

6-4. Clearing Maintenance Caused by SSL Errors

# Clear maintenance mode
baremetal node maintenance unset $NODE

# Check power state
baremetal node show $NODE | grep -E "power_state|fault|maintenance"

7. Nginx httpboot Configuration

7-1. File Paths

Item Path
Ironic httpboot root /var/lib/ironic/httpboot
Nginx config file /etc/nginx/conf.d/bifrost-httpboot.conf

7-2. Basic Nginx Configuration

server {
    listen 8080;
    server_name <SERVER_HOSTNAME>;    # e.g. pxeserver

    root /var/lib/ironic/httpboot;

    location / {
        autoindex on;
    }
}

After making changes, reload Nginx:

nginx -t && systemctl reload nginx

8. Building a Custom IPA initramfs

The default ipa.initramfs has no user accounts, which makes direct SSH access for troubleshooting impossible.
The two methods below let you build a customized IPA image.

Method A: Build from Scratch with a devuser

Build an IPA image that includes a login-capable account and any additional packages you need.

If you don't have an SSH key yet, generate one first:

ssh-keygen -t rsa -b 4096

Set Environment Variables

export ELEMENTS_PATH=/opt/stack/bifrost/share/ironic-python-agent-builder/dib

export DIB_DEV_USER_USERNAME=<USERNAME>           # e.g. ipauser
export DIB_DEV_USER_PASSWORD=<PASSWORD>           # e.g. changeme123
export DIB_DEV_USER_PWDLESS_SUDO=yes
export DIB_DEV_USER_AUTHORIZED_KEYS=/root/.ssh/id_rsa.pub
export DIB_RELEASE=jammy
export DIB_DEV_USER_SHELL=/bin/bash

Build

ironic-python-agent-builder \
  -o /opt/custom-ipa \
  -e devuser \
  ubuntu 2>&1 | tee /opt/ipa-build.log

Copy Build Output

cp /opt/custom-ipa.kernel     /var/lib/ironic/httpboot/ipa.kernel
cp /opt/custom-ipa.initramfs  /var/lib/ironic/httpboot/ipa.initramfs
chmod 644 /var/lib/ironic/httpboot/ipa.*

Method B: Unpack and Modify an Existing initramfs (Repackaging)

Use this when you need changes beyond just adding an account — like modifying services or config files.

Extract

mkdir -p /opt/ipa-custom && cd /opt/ipa-custom
zcat /var/lib/ironic/httpboot/ipa.initramfs | cpio -idmv

Change the root Password

chroot . /bin/bash
passwd root
exit

Enable SSH Access

sed -i 's/^#*PermitRootLogin.*/PermitRootLogin yes/'                  etc/ssh/sshd_config
sed -i 's/^#*PasswordAuthentication.*/PasswordAuthentication yes/'    etc/ssh/sshd_config

# If a cloud-img override file exists
sed -i 's/PasswordAuthentication no/PasswordAuthentication yes/' \
  etc/ssh/sshd_config.d/60-cloudimg-settings.conf

Remove Boot Delay

Prevents the long wait caused by the network-online target during ramdisk boot:

rm -f etc/systemd/system/network-online.target.wants/systemd-networkd-wait-online.service
rm -f etc/systemd/system/network-online.target.wants/ifupdown-wait-online.service

Repackage

cd /opt/ipa-custom
find . | cpio -H newc -o | gzip -9 > /var/lib/ironic/httpboot/ipa.initramfs
chmod 644 /var/lib/ironic/httpboot/ipa.initramfs

9. Building a Deployment Image

9-1. Building a Custom Image with disk-image-create

Cloud images downloaded via wget come with a fixed partition layout that doesn't allow flexible partitioning.
Using disk-image-create gives you full control over partition layout, installed packages, and the bootloader.

Set Environment Variables

# Ubuntu release codename (jammy = 22.04, noble = 24.04)
export DIB_RELEASE=jammy

# Temp directory for the build workspace — make sure there's enough free space
export TMPDIR=/tmp

# Package mirror — using a nearby mirror significantly speeds up the build
export DIB_DISTRIBUTION_MIRROR=http://mirror.kakao.com/ubuntu

# Image size in GiB — set this to the minimum you need for your OS
export DIB_IMAGE_SIZE=10

Partition Layout

Below is a standard 4-partition layout with UEFI boot support.
Adjust size values to suit your environment.

export DIB_BLOCK_DEVICE_CONFIG='
- local_loop:
    name: image0

- partitioning:
    base: image0
    label: gpt
    partitions:
      - name: esp
        type: EF00
        size: 512MiB
        mkfs:
          type: vfat
          mount:
            mount_point: /boot/efi
            fstab:
              options: umask=0077
              fsck-passno: 1

      - name: bios
        type: EF02
        size: 8MiB

      - name: boot
        type: 8300
        size: 1GiB
        mkfs:
          type: ext4
          mount:
            mount_point: /boot
            fstab:
              options: defaults
              fsck-passno: 2

      - name: root
        type: 8300
        size: 100%
        mkfs:
          type: ext4
          mount:
            mount_point: /
            fstab:
              options: defaults
              fsck-passno: 1
'

Partition Notes

  • esp (EF00): UEFI boot partition. Minimum 256MiB, recommended 512MiB.
  • bios (EF02): BIOS compatibility partition. No need to change the size.
  • boot (8300): Mounted at /boot. Increase size if you plan to keep multiple kernels.
  • root (8300): Root partition. Setting size: 100% uses all remaining space.

Build the Image

disk-image-create \
  ubuntu \
  bootloader \
  grub2 \
  block-device-efi \
  cloud-init \
  growroot \
  -p openssh-server,curl,vim,net-tools,htop,mdadm,\
grub-efi-amd64,grub-efi-amd64-signed,shim-signed \
  -t qcow2 \
  -o /var/lib/ironic/httpboot/ubuntu-22.04-baremetal \
  2>&1 | tee /tmp/image-build.log

Elements (build components)

  • ubuntu: Base Ubuntu OS
  • bootloader: Installs GRUB bootloader
  • grub2: GRUB2 configuration
  • block-device-efi: Enables UEFI partition layout
  • cloud-init: Includes cloud-init for user-data handling at deploy time
  • growroot: Automatically expands the root partition on first boot

-p packages: Add any packages you need, comma-separated.
mdadm is required for SW RAID — remove it if you don't need it.

Verify the Build

ls -lh /var/lib/ironic/httpboot/ubuntu-22.04-baremetal.qcow2

9-2. Patching UEFI Boot Files (If Needed)

If the /boot/efi/EFI/ubuntu path is missing after the build, apply this patch:

guestfish -a /var/lib/ironic/httpboot/ubuntu-22.04-baremetal.qcow2 << 'EOF'
run
mount /dev/sda4 /
mount /dev/sda3 /boot
mount /dev/sda1 /boot/efi

mkdir-p /boot/efi/EFI/ubuntu
mkdir-p /boot/efi/EFI/BOOT

copy-out /usr/lib/shim/shimx64.efi /tmp/
copy-in /tmp/shimx64.efi /boot/efi/EFI/BOOT/BOOTX64.EFI
copy-in /tmp/shimx64.efi /boot/efi/EFI/ubuntu/shimx64.efi

copy-out /usr/lib/grub/x86_64-efi-signed/grubx64.efi.signed /tmp/
copy-in /tmp/grubx64.efi.signed /boot/efi/EFI/ubuntu/grubx64.efi
copy-in /tmp/grubx64.efi.signed /boot/efi/EFI/BOOT/grubx64.efi

write /boot/efi/EFI/ubuntu/grub.cfg \
  "search --no-floppy --label --set=root mkfs_boot\nset prefix=(\$root)/grub\nconfigfile \$prefix/grub.cfg\n"
write /boot/efi/EFI/BOOT/grub.cfg \
  "search --no-floppy --label --set=root mkfs_boot\nset prefix=(\$root)/grub\nconfigfile \$prefix/grub.cfg\n"

sh "sed -i 's|search --no-floppy --fs-uuid --set=root.*|search --no-floppy --label --set=root mkfs_boot|g' /boot/grub/grub.cfg"
EOF

Partition number warning: The device numbers (/dev/sda1, /dev/sda3, /dev/sda4) may differ depending on your build environment.
Always check your actual partition layout first:

guestfish -a <image_path> run : list-filesystems

9-3. Further Customization with virt-customize

Apply additional configuration to the image after the build completes:

virt-customize -a /var/lib/ironic/httpboot/ubuntu-22.04-baremetal.qcow2 \
  --root-password password:<ROOT_PASSWORD> \
  --ssh-inject root:file:/root/.ssh/id_rsa.pub \
  --run-command 'echo "PermitRootLogin yes" >> /etc/ssh/sshd_config' \
  --run-command 'echo "PasswordAuthentication yes" >> /etc/ssh/sshd_config' \
  --run-command 'ssh-keygen -A' \
  --run-command 'systemctl disable systemd-networkd-wait-online.service' \
  --run-command 'systemctl mask systemd-networkd-wait-online.service' \
  --run-command 'echo "network: {config: disabled}" > /etc/cloud/cloud.cfg.d/99-disable-network-config.cfg' \
  --run-command 'cloud-init clean --logs --seed'

virt-customize Option Reference

Accounts / Passwords

--root-password password:<PASSWORD>
--password <USERNAME>:password:<PASSWORD>

SSH

--ssh-inject root:file:/root/.ssh/id_rsa.pub
--ssh-inject <USERNAME>:string:"ssh-rsa AAAA..."

File Operations

--upload /local/file:/remote/path
--write '/etc/hosts:127.0.0.1 myhost'
--delete /path/to/file
--mkdir /new/directory

Running Commands

--run-command 'apt-get install -y nginx'
--firstboot /path/to/script.sh         # Run script on first boot
--firstboot-command 'systemctl start app'

Packages

--install nginx,curl,vim
--uninstall apache2
--update

System

--hostname <HOSTNAME>
--timezone Asia/Seoul
--truncate /path/to/file

9-4. Generating a SHA256 Checksum

sha256sum /var/lib/ironic/httpboot/ubuntu-22.04-baremetal.qcow2 \
  > /var/lib/ironic/httpboot/ubuntu-22.04-baremetal.qcow2.sha256

cat /var/lib/ironic/httpboot/ubuntu-22.04-baremetal.qcow2.sha256

9-5. Preparing the cloud-init config-drive

user-data.yaml Example

A standard cloud-init configuration:

#cloud-config

# Hostname
hostname: baremetal-node-01

# User accounts
users:
  - name: admin
    groups: sudo
    shell: /bin/bash
    sudo: ALL=(ALL) NOPASSWD:ALL
    lock_passwd: false
    # Generate with: openssl passwd -6 <password>
    passwd: "$6$rounds=4096$saltsalt$hashedpassword..."
    ssh_authorized_keys:
      - ssh-rsa AAAA...your-public-key...

# Allow SSH password authentication
ssh_pwauth: true

# Package updates and installs
package_update: true
package_upgrade: true
packages:
  - curl
  - vim
  - htop
  - net-tools
  - tmux

# Network configuration (netplan)
write_files:
  - path: /etc/netplan/99-baremetal.yaml
    permissions: '0600'
    content: |
      network:
        version: 2
        ethernets:
          enp1s0:
            dhcp4: true
          enp2s0:
            dhcp4: false
            addresses:
              - 192.168.240.100/24

# Commands to run after boot
runcmd:
  - netplan apply
  - systemctl restart ssh
  - echo "Provisioning complete: $(date)" >> /var/log/provision.log

Generating a password hash:

openssl passwd -6 <your_password>

config-drive via Directory

mkdir -p /opt/configdrive/openstack/latest
cp user-data.yaml /opt/configdrive/openstack/latest/user_data
echo '{"uuid": "<NODE_UUID>"}' > /opt/configdrive/openstack/latest/meta_data.json

config-drive via Inline JSON

Encode user-data as JSON and set it directly on the node — no separate files needed:

python3 << 'EOF'
import json
with open('/var/lib/ironic/httpboot/user-data.yaml') as f:
    userdata = f.read()
with open('/tmp/configdrive.json', 'w') as out:
    json.dump({'user_data': userdata}, out)
EOF

baremetal node set $NODE \
  --instance-info configdrive="$(cat /tmp/configdrive.json)"

10. Deploying a Node

10-1. Confirming Node Registration

When a baremetal server PXE boots, it registers with Ironic automatically:

baremetal node list
# provision_state should show: enroll

10-2. Transitioning to manageable

NODE=<NODE_UUID>

baremetal node manage $NODE
  • manual-management driver: transitions to manageable immediately
  • ipmi / redfish drivers: verifies BMC connectivity first, then transitions to manageable

10-3. Controlling automated clean

Running provide will trigger automated cleaning if automated_clean = True is set in /etc/ironic/ironic.conf.

Skip cleaning and go straight to available (per-node)

baremetal node set $NODE --no-automated-clean
baremetal node provide $NODE

Re-enable cleaning for a node

baremetal node set $NODE --automated-clean

Disable globally (applies to all nodes)

# /etc/ironic/ironic.conf
[conductor]
automated_clean = False
systemctl restart ironic

10-4. Transitioning to available

baremetal node provide $NODE

10-5. Setting Image Info on the Node

CONTROLLER_IP=<CONTROLLER_IP>    # e.g. 192.168.240.1
IMAGE_NAME=ubuntu-22.04-baremetal

baremetal node set $NODE \
  --instance-info image_source=http://${CONTROLLER_IP}:8080/${IMAGE_NAME}.qcow2 \
  --instance-info image_os_hash_algo=sha256 \
  --instance-info image_os_hash_value=$(curl -s http://${CONTROLLER_IP}:8080/${IMAGE_NAME}.qcow2.sha256 | awk '{print $1}')

10-6. Starting Deployment

Method A: config-drive from directory

baremetal node deploy $NODE --config-drive /opt/configdrive/

Method B: config-drive via inline JSON

Encode and set user-data without needing separate files:

python3 << 'EOF'
import json
with open('/var/lib/ironic/httpboot/user-data.yaml') as f:
    userdata = f.read()
with open('/tmp/configdrive.json', 'w') as out:
    json.dump({'user_data': userdata}, out)
EOF

baremetal node set $NODE \
  --instance-info configdrive="$(cat /tmp/configdrive.json)"

baremetal node deploy $NODE

10-7. Monitoring Deployment Status

# Watch status in real time
watch -n2 "baremetal node list 2>/dev/null"

State flow during deployment:

available → deploying → wait call-back → active

Once the node reaches active, deployment is complete. Nodes registered via IPMI or Redfish will automatically reboot from local disk after deployment.


11. SW RAID Setup and Teardown

11-1. Writing the RAID Config File

The example below sets up RAID 1 (mirroring):

cat > /opt/sw-raid-config.json << 'EOF'
{
  "logical_disks": [
    {
      "size_gb": "MAX",
      "raid_level": "1",
      "controller": "software",
      "is_root_volume": true
    }
  ]
}
EOF

raid_level options: 0 (striping), 1 (mirroring), 5 (parity), 10 (mirrored striping)

11-2. Applying the RAID Config

Run this while the node is in manageable state:

baremetal node set $NODE --raid-interface agent
baremetal node set $NODE --target-raid-config /opt/sw-raid-config.json

11-3. Creating the RAID via a Clean Step

baremetal node clean $NODE \
  --clean-steps '[
    {"interface": "deploy", "step": "erase_devices_metadata"},
    {"interface": "raid",   "step": "create_configuration"}
  ]'

After cleaning completes, SSH into the server to verify:

cat /proc/mdstat
mdadm --detail /dev/md0

11-4. Tearing Down RAID

Removes the RAID and returns disks to a clean state. Run from manageable state.

Removing the RAID along with disk metadata prevents conflicts on the next deployment:

baremetal node clean $NODE \
  --clean-steps '[
    {"interface": "raid",   "step": "delete_configuration"},
    {"interface": "deploy", "step": "erase_devices_metadata"}
  ]'

To fully reset the RAID interface on the node:

baremetal node set $NODE --raid-interface no-raid

12. Undeploying a Node

12-1. Undeploy

baremetal node undeploy $NODE

The node transitions through: activecleaningavailable.
During cleaning, the node reboots into the ramdisk and wipes disk metadata.

Note for RAID-configured nodes: Do not undeploy directly. First boot into the ramdisk and run the delete_configuration clean step, then undeploy.
Skipping this step can cause partition conflicts on the next deployment.

12-2. Manual Clean

baremetal node clean $NODE \
  --clean-steps '[{"interface": "deploy", "step": "erase_devices_metadata"}]'

12-3. Deleting a Node

To completely remove a node, run from manageable state:

# Transition to manageable first
baremetal node manage $NODE

# Delete
baremetal node delete $NODE

13. Appendix

13-1. Node State Transition Summary

Current State Command Next State
enroll baremetal node manage manageable
manageable baremetal node provide available (after clean)
manageable baremetal node provide (no-automated-clean) available (immediately)
available baremetal node deploy deployingactive
active baremetal node undeploy cleaningavailable
any baremetal node maintenance set maintenance
maintenance baremetal node maintenance unset previous state
manageable baremetal node delete (deleted)
clean failed baremetal node managebaremetal node provide manageableavailable

13-2. Power Control After Cleaning

By default, Ironic powers off the node after clean or inspect completes. To prevent this, configure it per node:

# Keep power ON after clean completes
baremetal node set $NODE --disable-power-off

# Revert to default behavior
baremetal node set $NODE --enable-power-off

To detect when cleaning is done and automatically trigger a next step, you can use this script:

#!/bin/bash
NODE=$1
TARGET_STATE=${2:-available}

echo "Monitoring state changes for node $NODE..."
while true; do
    STATE=$(baremetal node show $NODE -f value -c provision_state 2>/dev/null)
    POWER=$(baremetal node show $NODE -f value -c power_state 2>/dev/null)
    echo "$(date '+%H:%M:%S') state=$STATE power=$POWER"

    if [ "$STATE" = "$TARGET_STATE" ]; then
        echo "Target state reached: $TARGET_STATE"
        break
    elif [[ "$STATE" == *"failed"* ]]; then
        echo "Error detected: $STATE"
        exit 1
    fi
    sleep 5
done

13-3. Troubleshooting

baremetal command returns 401 Unauthorized

grep auth_strategy /etc/ironic/ironic.conf
# If not noauth, fix it:
sed -i 's/auth_strategy = .*/auth_strategy = noauth/' /etc/ironic/ironic.conf
systemctl restart ironic

Recovering from clean failed

baremetal node maintenance unset $NODE
baremetal node manage $NODE
baremetal node provide $NODE

power failure / Redfish SSL error

# Re-set redfish_verify_ca=False
baremetal node set $NODE --driver-info redfish_verify_ca=False
# Remove the incorrect parameter if it was set
baremetal node unset $NODE --driver-info redfish_verify_cafile
baremetal node maintenance unset $NODE

No IP assigned after PXE boot

# Check dnsmasq logs
journalctl -u dnsmasq -f

# Verify DHCP Discover packets are reaching the interface
tcpdump -i <PXE_INTERFACE> port 67 or port 68 -n

Node stuck in enroll and not registering

# Check Inspector status
systemctl status ironic-inspector
journalctl -u ironic-inspector -f

# Verify Inspector port is listening
ss -tlnp | grep 5050

13-4. Key Paths and Files

Item Path
Ironic config /etc/ironic/ironic.conf
dnsmasq config /etc/dnsmasq.conf, /etc/dnsmasq.d/
httpboot root /var/lib/ironic/httpboot/
Deploy kernel / ramdisk /var/lib/ironic/httpboot/ipa.kernel, ipa.initramfs
Deploy logs /var/log/ironic/deploy/
DHCP host config /etc/dnsmasq.d/bifrost.dhcp-hosts.d/
DHCP options config /etc/dnsmasq.d/bifrost.dhcp-opts.d/
clouds.yaml ~/.config/openstack/clouds.yaml