Skip to content

Instantly share code, notes, and snippets.

@Andygol
Created December 28, 2025 11:02
Show Gist options
  • Select an option

  • Save Andygol/a47dc8b49c0dcab10e35ff2dbc683e7c to your computer and use it in GitHub Desktop.

Select an option

Save Andygol/a47dc8b49c0dcab10e35ff2dbc683e7c to your computer and use it in GitHub Desktop.
Creating Multiple Multipass VMs with Static IP Addresses

Creating Multiple Multipass VMs with Static IP Addresses

See more details at https://blog.andygol.co.ua/en/2025/12/26/static-ip-for-multipass-vm/

Option A: Quick Creation with Different IPs

# VM 1 - 10.10.0.10
cat > vm1.yaml << 'EOF'
#cloud-config
write_files:
  - path: /etc/netplan/60-custom-network.yaml
    content: |
      network:
        version: 2
        ethernets:
          enp0s2:
            addresses:
              - 10.10.0.10/24
            routes:
              - to: 10.10.0.0/24
                scope: link
    permissions: '0600'
runcmd:
  - netplan apply
hostname: vm1
EOF

# VM 2 - 10.10.0.11
cat > vm2.yaml << 'EOF'
#cloud-config
write_files:
  - path: /etc/netplan/60-custom-network.yaml
    content: |
      network:
        version: 2
        ethernets:
          enp0s2:
            addresses:
              - 10.10.0.11/24
            routes:
              - to: 10.10.0.0/24
    permissions: '0600'
runcmd:
  - netplan apply
hostname: vm2
EOF

# VM 3 - 10.10.0.12
cat > vm3.yaml << 'EOF'
#cloud-config
write_files:
  - path: /etc/netplan/60-custom-network.yaml
    content: |
      network:
        version: 2
        ethernets:
          enp0s2:
            addresses:
              - 10.10.0.12/24
            routes:
              - to: 10.10.0.0/24
    permissions: '0600'
runcmd:
  - netplan apply
hostname: vm3
EOF

# Launch all VMs
multipass launch --name vm1 --network name=en0,mode=manual --cloud-init vm1.yaml
multipass launch --name vm2 --network name=en0,mode=manual --cloud-init vm2.yaml
multipass launch --name vm3 --network name=en0,mode=manual --cloud-init vm3.yaml

# Wait for completion
sleep 30

# Add alias to bridge101
sudo ifconfig bridge101 10.10.0.1/24 alias

# Check all VMs
multipass list

Option B: Script for Automatic Creation

#!/bin/bash
# Create file: create-vms.sh

BASE_IP="10.10.0"
START_NUM=10
COUNT=3

for i in $(seq 0 $((COUNT-1))); do
    VM_NAME="vm$((i+1))"
    VM_IP="$BASE_IP.$((START_NUM+i))"

    cat > ${VM_NAME}.yaml << EOF
#cloud-config
write_files:
  - path: /etc/netplan/60-custom-network.yaml
    content: |
      network:
        version: 2
        ethernets:
          enp0s2:
            addresses:
              - ${VM_IP}/24
            routes:
              - to: ${BASE_IP}.0/24
    permissions: '0600'
runcmd:
  - netplan apply
hostname: ${VM_NAME}
EOF

    echo "Creating $VM_NAME with IP $VM_IP"
    multipass launch --name $VM_NAME --cloud-init ${VM_NAME}.yaml
done

echo "Waiting for cloud-init to complete..."
sleep 30

TARGET_BRIDGE=$(ifconfig -v | grep -B 20 "member: vmenet" | grep "bridge" | awk -F: '{print $1}' | tail -n 1)

if [ -z "$TARGET_BRIDGE" ]; then
    echo "Error: Bridge not found. Check if VM is running."
else
    echo "VM found on $TARGET_BRIDGE. Assigning $BASE_IP.1..."
    sudo ifconfig $TARGET_BRIDGE $BASE_IP.1/24 alias
fi

echo "VM List:"
multipass list

Run:

chmod +x create-vms.sh
./create-vms.sh

Checking Connectivity Between VMs

# From vm1 to others
multipass exec -n vm1 -- ping -c 2 10.10.0.11
multipass exec -n vm1 -- ping -c 2 10.10.0.12

# From vm2 to others
multipass exec -n vm2 -- ping -c 2 10.10.0.10
multipass exec -n vm2 -- ping -c 2 10.10.0.12

# From host to all
for ip in 10.10.0.10 10.10.0.11 10.10.0.12; do
    echo "Ping $ip:"
    ping -c 2 $ip
done

Removing Static IP from Individual VM

Option 1: Remove Only Additional IP (Keep VM)

# Remove static IP from VM
multipass exec -n test-vm -- sudo ip addr del 10.10.0.10/24 dev enp0s2

# Check
multipass exec -n test-vm -- ip addr show enp0s2

# VM remains with DHCP IP

Option 2: Remove via Netplan (Permanent)

# Remove configuration file
multipass exec -n test-vm -- sudo rm /etc/netplan/60-custom-network.yaml

# Apply changes
multipass exec -n test-vm -- sudo netplan apply

Option 3: Restart VM (Resets Temporary Changes)

# If IP added via ip addr add (not via netplan)
multipass restart test-vm

# IP will disappear after restart
multipass exec -n test-vm -- ip addr show enp0s2

Complete Infrastructure Teardown

Step 1: Delete All VMs

# List all VMs
multipass list

# Stop all VMs
multipass stop --all

# Delete specific VMs
multipass delete test-vm
multipass delete vm1
multipass delete vm2
multipass delete vm3

# Or delete all VMs
multipass delete --all

# Permanently clean (purge)
multipass purge

# Check that everything is deleted
multipass list

Step 2: Remove IP from Bridge on Host

# Remove alias from bridge100, if you added it earlier
sudo ifconfig bridge100 -alias 10.10.0.1

# Check that it's removed
ifconfig bridge100 | grep "inet "

# Should not have 10.10.0.1

Step 3: Clear ARP Cache (Optional)

# Remove all entries for subnet 10.10.0.0
sudo arp -d -a

# Or specific IPs
sudo arp -d 10.10.0.10
sudo arp -d 10.10.0.11
sudo arp -d 10.10.0.12

# Check
arp -an | grep 10.10.0
netstat -rn | grep 10.10.0

Step 4: Remove Routes (If Added Manually)

# Check routes
netstat -rn | grep 10.10.0

# Remove if present
sudo route delete 10.10.0.0/24

# Check again
netstat -rn | grep 10.10.0

Step 5: Remove Configuration Files

# Remove cloud-init files
rm -f multipass-static-ip.yaml
rm -f vm1.yaml vm2.yaml vm3.yaml
rm -f create-vms.sh

Step 6: Restart Multipass (Optional)

# Restart daemon for clean state
sudo launchctl unload /Library/LaunchDaemons/com.canonical.multipassd.plist
sudo launchctl load /Library/LaunchDaemons/com.canonical.multipassd.plist

Step 7: Final Check

# Check that everything is clean
echo "=== Multipass VM ==="
multipass list

echo "=== Bridge IP ==="
ifconfig bridge100 | grep "inet "

echo "=== Routes ==="
netstat -rn | grep 10.10.0

echo "=== ARP cache ==="
arp -an | grep 10.10.0

echo "=== Launch Daemons ==="
ls -la /Library/LaunchDaemons/ | grep multipass

echo "=== Cloud-init files ==="
ls -la *.yaml 2>/dev/null || echo "No cloud-init files"

Expected Result After Cleanup:

  • Multipass VM: No instances found.
  • Bridge IP: only primary IP (not 10.10.0.1)
  • Routes: no entries with 10.10.0
  • ARP cache: no entries with 10.10.0
  • Files: deleted

Infrastructure completely dismantled!


Quick Commands (Cheat Sheet)

Create Infrastructure:

# Host
sudo ifconfig bridge100 10.10.0.1/24 alias

# VM
multipass launch --name test-vm --cloud-init multipass-static-ip.yaml

# Check
ping 10.10.0.10

Dismantle Infrastructure:

# VM
multipass delete test-vm
multipass purge

# Host
sudo ifconfig bridge100 -alias 10.10.0.1
sudo arp -d -a

# Check
multipass list
ifconfig bridge100 | grep 10.10.0

Temporary IP (without cloud-init):

# Add
multipass exec -n test-vm -- sudo ip addr add 10.10.0.10/24 dev enp0s1

# Remove
multipass exec -n test-vm -- sudo ip addr del 10.10.0.10/24 dev enp0s1

Automation: Creation and Teardown Scripts

Creation Script (setup.sh)

#!/bin/bash
# File: setup.sh

BRIDGE="bridge100"
BRIDGE_IP="10.10.0.1"
SUBNET="10.10.0.0/24"
VM_NAME="test-vm"
VM_IP="10.10.0.10"

echo "=== Setting up bridge ==="
sudo ifconfig $BRIDGE $BRIDGE_IP/24 alias
ifconfig $BRIDGE | grep "inet "

echo "=== Creating cloud-init ==="
cat > ${VM_NAME}.yaml << EOF
#cloud-config
write_files:
  - path: /etc/netplan/60-custom-network.yaml
    content: |
      network:
        version: 2
        ethernets:
          default:
            dhcp4: true
            addresses:
              - ${VM_IP}/24
            routes:
              - to: default
                via: ${BRIDGE_IP}
                metric: 200
    permissions: '0600'
runcmd:
  - netplan apply
hostname: ${VM_NAME}
EOF

echo "=== Creating VM ==="
multipass launch --name $VM_NAME --cloud-init ${VM_NAME}.yaml

echo "=== Waiting for cloud-init ==="
sleep 30
multipass exec -n $VM_NAME -- cloud-init status --wait

echo "=== Check ==="
echo "VM list:"
multipass list

echo "VM IP:"
multipass exec -n $VM_NAME -- ip addr show enp0s1 | grep "inet "

echo "Ping test:"
ping -c 4 $VM_IP

echo "✅ Infrastructure created!"

Teardown Script (teardown.sh)

#!/bin/bash
# File: teardown.sh

BRIDGE="bridge100"
BRIDGE_IP="10.10.0.1"
VM_NAME="test-vm"

echo "=== Deleting VM ==="
multipass stop $VM_NAME 2>/dev/null
multipass delete $VM_NAME 2>/dev/null
multipass purge

echo "=== Removing bridge IP ==="
sudo ifconfig $BRIDGE -alias $BRIDGE_IP

echo "=== Clearing ARP ==="
sudo arp -d -a

echo "=== Removing routes ==="
sudo route delete 10.10.0.0/24 2>/dev/null

echo "=== Removing configs ==="
rm -f ${VM_NAME}.yaml

echo "=== Check ==="
echo "Multipass VM:"
multipass list

echo "Bridge IP:"
ifconfig $BRIDGE | grep "inet " | grep -v "127.0.0.1"

echo "Routes:"
netstat -rn | grep 10.10.0

echo "✅ Infrastructure dismantled!"

Using Scripts

# Make executable
chmod +x setup.sh teardown.sh

# Creation
./setup.sh

# Teardown
./teardown.sh

Troubleshooting Teardown

Problem: VM Not Deleting

# Force delete
multipass stop test-vm --force
multipass delete test-vm --force
multipass purge

# If still problems
sudo pkill -9 multipassd
sudo launchctl unload /Library/LaunchDaemons/com.canonical.multipassd.plist
sudo launchctl load /Library/LaunchDaemons/com.canonical.multipassd.plist

Problem: IP Not Removing from Bridge

# Try again
sudo ifconfig bridge100 -alias 10.10.0.1

# If not helping, check all aliases
ifconfig bridge100

# Remove each alias separately
sudo ifconfig bridge100 -alias [IP]

# In extreme case restart bridge
sudo ifconfig bridge100 down
sudo ifconfig bridge100 up

Problem: ARP Entries Not Deleting

# Force delete entire ARP cache
sudo arp -d -a

# Restart network (carefully!)
sudo ifconfig bridge100 down
sudo ifconfig bridge100 up

Problem: Routes Remain

# Check all routes
netstat -rn

# Remove specific route
sudo route delete [IP or subnet]

# Example
sudo route delete 10.10.0.10
sudo route delete 10.10.0.0/24

Permanent Settings (Automatic Creation After Reboot)

If you want the bridge IP to be created automatically:

# Create Launch Daemon
sudo tee /Library/LaunchDaemons/com.local.multipass-bridge.plist << 'EOF'
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>Label</key>
    <string>com.local.multipass-bridge</string>
    <key>ProgramArguments</key>
    <array>
        <string>/bin/sh</string>
        <string>-c</string>
        <string>sleep 10 &amp;&amp; /sbin/ifconfig bridge100 10.10.0.1/24 alias</string>
    </array>
    <key>RunAtLoad</key>
    <true/>
    <key>KeepAlive</key>
    <false/>
</dict>
</plist>
EOF

# Load
sudo launchctl load /Library/LaunchDaemons/com.local.multipass-bridge.plist

# To remove
sudo launchctl unload /Library/LaunchDaemons/com.local.multipass-bridge.plist
sudo rm /Library/LaunchDaemons/com.local.multipass-bridge.plist
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment