A powerful and flexible Ansible dynamic inventory script for LXD that supports multiple endpoints, advanced filtering, and hostname customization.
This script was created after running into too many limitations of community.general.lxd inventory. Using many projects on multiple clusters, I ended up with over 20 inventory files. This has now been reduced to 1.
Should also work fine with Incus!
Feel free to request additional features.
- Multi-Endpoint Support: Connect to multiple LXD servers in a single inventory run
- Tag-based Filtering: Filter instances using
user.*
configuration keys - Flexible Hostname Formatting: Customize how hostnames appear in your inventory (
{name}.{project}.{endpoint}.example.com
) - Custom Group Formatting: Customize Ansible group names with templates
- User-Defined Groups: Create custom groups using
user.ansible_groups
configuration - Project and Profile Filtering: Filter by LXD projects and profiles
- Network Interface Control: Configure IP address selection with interface filtering and IPv4/6 preference
- Status and Type Filtering: Include/exclude based on instance status and type
- Regex-based Name Filtering: Exclude instances based on names with support for regex
- SSL/TLS Support: Full certificate management for secure connections
- Unix Socket Support: Local LXD daemon connections
- Debug Mode: Detailed logging for troubleshooting
pip install PyYAML requests urllib3
For Unix socket connections (local LXD):
pip install requests-unixsocket
git clone https://github.com/vosdev/ansible-lxd-inventory
chmod +x ansible-lxd-inventory/lxd_inventory.py
Create lxd_inventory.yml
:
global_defaults:
verify_ssl: false
filters:
status: [running, stopped]
type: [container, virtual-machine]
projects: [all]
lxd_endpoints:
local:
endpoint: "unix:///var/lib/lxd/unix.socket"
remote:
endpoint: "https://lxd.example.com:8443"
cert_path: "/path/to/client.crt"
key_path: "/path/to/client.key"
# List all instances
./lxd_inventory.py --list
# Get specific instance details
./lxd_inventory.py --instance mycontainer
# Debug mode
./lxd_inventory.py --list --debug
# Run playbook using dynamic inventory
ansible-playbook -i lxd_inventory.py playbook.yml
# List hosts in inventory
ansible-inventory -i lxd_inventory.py --list
To allow for multiple instances of the script in restricted environments (e.g. SemaphoreUI, AWX or Tower), the script searches for configuration files in this order based on it's file name. e.g., lxd_inventory_dev.py
-> lxd_inventory_dev.yml
in the following locations:
/etc/{base_name}.yml
/etc/{base_name}.yaml
~/.config/{base_name}.yml
~/.config/{base_name}.yaml
./{base_name}.yml
./{base_name}.yaml
If those are not found, it will fallback to the default config filename in the following locations:
--config /path/to/config.yml
(CLI argument)./lxd_inventory.yml
./lxd_inventory.yaml
~/.config/lxd_inventory.yml
~/.config/lxd_inventory.yaml
/etc/lxd_inventory.yml
/etc/lxd_inventory.yaml
global_defaults:
# SSL/TLS settings
verify_ssl: false
cert_path: "/path/to/client.crt"
key_path: "/path/to/client.key"
ca_cert_path: "/path/to/ca.crt"
# Hostname formatting template
hostname_format: "{name}"
# Group formatting templates
group_formats:
project: "lxd_project_{project}" # Default format
profile: "lxd_profile_{profile}"
endpoint: "lxd_endpoint_{endpoint}"
status: "lxd_{status}"
type: "lxd_{type}"
# User-defined groups
user_groups:
enabled: true # Enable user groups (default)
key: "user.ansible_groups" # Configuration key to check
# Default filters
filters:
status: [running, stopped, frozen, error]
type: [container, virtual-machine]
projects: [all]
exclude_projects: []
profiles: []
tags: {}
ignore_interfaces: [lo, docker0, lxdbr0]
prefer_ipv6: false
exclude_names: []
lxd_endpoints:
endpoint_name:
endpoint: "https://lxd.example.com:8443"
# Override any global_defaults here
filters:
# Override specific filters for this endpoint
# CLI
./lxd_inventory.py --list --status running
./lxd_inventory.py --list --status running,stopped
# Config
filters:
status: [running, stopped]
# CLI
./lxd_inventory.py --list --type vm
./lxd_inventory.py --list --type vm,lxc
# Config
filters:
type: [container, virtual-machine]
Filter instances based on their project.
# CLI - specific projects
./lxd_inventory.py --list --project production,development
# CLI - all projects
./lxd_inventory.py --list --all-projects
filters:
# only list instances from production and development project
projects: [production, development]
# list instances from all projects (default value)
projects: [all]
# list instances from all projects with some exceptions
projects: [all]
exclude_projects:
- backup
- 'regex:^temp.*'
- 'regex:^test[1-9]'
Filter instances based on user.*
configuration keys. Tags can be set directly on instances or inherited from profiles.
# Only include instances with user.ansible=true
./lxd_inventory.py --list --tag "user.ansible=true"
# Multiple requirements
./lxd_inventory.py --list --tag "user.ansible=true,user.env=production"
# Exclude instances (negation)
./lxd_inventory.py --list --tag "user.ansible!=false"
# Check for key existence
./lxd_inventory.py --list --tag "user.managed"
filters:
tags:
# Include only instances with user.ansible=true
user.ansible: "true"
# Exclude instances with user.ansible=false
"user.ansible!=": "false"
# Multiple requirements
user.environment: "production"
"user.backup!=": "true"
# Direct instance configuration
lxc config set myinstance user.ansible true
lxc config set myinstance user.environment production
# Profile-based tags (applies to all instances using the profile)
lxc profile create ansible-managed
lxc profile set ansible-managed user.ansible true
lxc profile add myinstance ansible-managed
# CLI
./lxd_inventory.py --list --profile web,database
# Config
filters:
profiles: [web, database]
filters:
exclude_names:
- 'vm1' # excludes vm1 from any project
- 'project/vm1' # excludes vm1 only from specified project
- 'regex:^vm.*' # excludes instances matching regex pattern
- 'regex:project/^vm[1-3]' # excludes instances matching regex in specific project
Customize how instance names appear in your Ansible inventory using template variables:
{name}
- Instance name{project}
- LXD project name{endpoint}
- Endpoint name from config{type}
- Instance type (container/virtual-machine){status}
- Instance status (running/stopped/etc)
# Global default
global_defaults:
hostname_format: "{name}" # Output: "myinstance"
# Include project
global_defaults:
hostname_format: "{name}.{project}" # Output: "myinstance.web"
# FQDN-style (My personal preference)
global_defaults:
hostname_format: "{name}.{project}.{endpoint}.example.com"
# Output: "myinstance.web.prod.example.com"
# Per-endpoint override
lxd_endpoints:
production:
hostname_format: "{name}.prod.example.com"
development:
hostname_format: "{name}.dev.example.com"
Customize how Ansible groups are named using template variables:
project
- Groups by LXD projectprofile
- Groups by LXD profileendpoint
- Groups by LXD endpointstatus
- Groups by instance statustype
- Groups by instance type
global_defaults:
group_formats:
project: "lxd_project_{project}" # Default: lxd_project_web
profile: "lxd_profile_{profile}" # Default: lxd_profile_nginx
endpoint: "lxd_endpoint_{endpoint}" # Default: lxd_endpoint_production
status: "lxd_{status}" # Default: lxd_running
type: "lxd_{type}" # Default: lxd_containers
# Simple naming without lxd_ prefix
global_defaults:
group_formats:
project: "{project}" # Output: "web"
profile: "{profile}" # Output: "nginx"
endpoint: "{endpoint}" # Output: "production"
status: "{status}" # Output: "running"
type: "{type}" # Output: "containers"
# Hierarchical naming
global_defaults:
group_formats:
project: "{endpoint}_{project}" # Output: "production_web"
profile: "{project}_{profile}" # Output: "web_nginx"
status: "{endpoint}_{status}" # Output: "production_running"
# Per-endpoint customization
lxd_endpoints:
production:
group_formats:
project: "prod_{project}" # Output: "prod_web"
profile: "prod_{profile}" # Output: "prod_nginx"
development:
group_formats:
project: "dev_{project}" # Output: "dev_web"
profile: "dev_{profile}" # Output: "dev_nginx"
Create custom groups by setting the user.ansible_groups
(or a key to your personal preference) configuration key on instances or profiles:
# Direct instance configuration
lxc config set myinstance user.ansible_groups "webservers,production,frontend"
# Profile-based groups (applies to all instances using the profile)
lxc profile set nginx-profile user.ansible_groups "webservers,nginx"
lxc profile add myinstance nginx-profile
global_defaults:
user_groups:
enabled: true # Enable user-defined groups (default: true)
key: "user.ansible_groups" # Configuration key to check (default)
# Per-endpoint override
lxd_endpoints:
production:
user_groups:
enabled: true
key: "user.prod_groups" # Use different key for this endpoint
# Web server instances
lxc config set web1 user.ansible_groups "webservers,nginx,production"
lxc config set web2 user.ansible_groups "webservers,nginx,production"
# Database instances
lxc config set db1 user.ansible_groups "databases,mysql,production"
lxc config set db2 user.ansible_groups "databases,mysql,production"
# Load balancer
lxc config set lb1 user.ansible_groups "loadbalancers,haproxy,production,frontend"
This creates Ansible groups: webservers
, nginx
, production
, databases
, mysql
, loadbalancers
, haproxy
, frontend
filters:
# Interfaces to ignore when finding IP addresses
ignore_interfaces: [lo, docker0, cilium_host, lxdbr0]
# Prefer IPv6 over IPv4
prefer_ipv6: true
# CLI
./lxd_inventory.py --list --ignore-interface "lo,docker0" --prefer-ipv6
# Single endpoint
./lxd_inventory.py --list --endpoint production
# Multiple endpoints
./lxd_inventory.py --list --endpoint production,development
# All endpoints (default)
./lxd_inventory.py --list
lxd_endpoints:
production:
endpoint: "https://prod-lxd:8443"
verify_ssl: true
filters:
projects: [production]
tags:
user.ansible: "true"
user.environment: "production"
development:
endpoint: "https://dev-lxd:8443"
verify_ssl: false
filters:
projects: [development, testing]
tags:
"user.ansible!=": "false"
The script automatically creates these groups:
Legacy groups (always available for backward compatibility):
all
- All instanceslxd_containers
- Container instanceslxd_vms
- Virtual machine instanceslxd_running
- Running instanceslxd_stopped
- Stopped instanceslxd_frozen
- Frozen instanceslxd_error
- Error state instances
Configurable groups (customizable via group_formats):
- Project groups - Instances from specific projects
- Profile groups - Instances with specific profiles
- Endpoint groups - Instances from specific endpoints
- Status groups - Instances by status (customizable names)
- Type groups - Instances by type (customizable names)
User-defined groups:
- Custom groups from
user.ansible_groups
configuration
Each instance includes these variables:
lxd_name: "original-instance-name"
lxd_hostname: "formatted-hostname"
lxd_type: "container"
lxd_status: "running"
lxd_architecture: "x86_64"
lxd_profiles: ["default", "web"]
lxd_project: "production"
lxd_endpoint: "production"
lxd_endpoint_url: "https://prod-lxd:8443"
lxd_ip: ["10.0.1.100","2001:db8::100"]
lxd_config: {...}
lxd_expanded_config: {...}
ansible_host: "10.0.1.100"
global_defaults:
verify_ssl: true
cert_path: "/etc/ssl/lxd-client.crt"
key_path: "/etc/ssl/lxd-client.key"
hostname_format: "{name}.{project}.{endpoint}.example.com"
filters:
# Only include ansible-managed instances
tags:
user.ansible: "true"
"user.maintenance!=": "true"
lxd_endpoints:
production:
endpoint: "https://prod-lxd.example.com:8443"
filters:
projects: [production]
tags:
user.environment: "production"
staging:
endpoint: "https://staging-lxd.example.com:8443"
filters:
projects: [staging]
tags:
user.environment: "staging"
With instances configured like:
# Web servers
lxc config set web1 user.ansible "true"
lxc config set web1 user.environment "production"
# Database
lxc config set db1 user.ansible "true"
lxc config set db1 user.environment "production"
global_defaults:
verify_ssl: true
cert_path: "/etc/ssl/lxd-client.crt"
key_path: "/etc/ssl/lxd-client.key"
hostname_format: "{name}.{project}.{endpoint}.example.com"
# Custom group naming
group_formats:
project: "{endpoint}_{project}" # production_web instead of lxd_project_web
profile: "{project}_{profile}" # web_nginx instead of lxd_profile_nginx
endpoint: "{endpoint}" # production instead of lxd_endpoint_production
status: "{status}" # running instead of lxd_running
type: "{type}" # containers instead of lxd_containers
# Enable user-defined groups
user_groups:
enabled: true
key: "user.ansible_groups"
With instances configured like:
# Web servers
lxc config set web1 user.ansible_groups "webservers,nginx,frontend"
# Database
lxc config set db1 user.ansible_groups "databases,mysql,backend"
lxd_endpoints:
local:
endpoint: "unix:///var/lib/lxd/unix.socket"
hostname_format: "{name}.local"
filters:
projects: [all]
status: [running]
tags:
"user.ansible!=": "false"
lxd_endpoints:
multi_env:
endpoint: "https://multi-lxd.example.com:8443"
filters:
projects: [all]
tags:
user.managed: "true" # Must be managed
"user.backup!=": "true" # Exclude backup instances
"user.template!=": "true" # Exclude templates
"user.ansible!=": "false" # Exclude explicitly disabled
lxd_endpoints:
prod:
endpoint: "https://lxd-prod.example.com:8443"
hostname_format: "{name}.{project}.{endpoint}.example.com" # nginx2.webshop.prod.example.com
filters:
projects: [all]
exclude_names:
- webshop/nginx1 # Exclude nginx1 in the webshop project
- sophos # Exclude all instances named sophos
- 'regex:^talos.*' # Exclude all instances starting with talos
- 'regex:security/^opnsense[1-2]' # Exclude opnsense1 and opnsense2 in the security project
exclude_projects:
- backup # Exclude backup project
- 'regex:^temp.*' # Exclude all projects starting with temp
./lxd_inventory.py --list --debug
This shows:
- Configuration file loading
- Endpoint connections
- Instance filtering decisions
- IP address selection
- Tag matching results
- Group creation and naming
No instances found:
- Check endpoint connectivity with
--debug
- Verify project names and permissions
- Check tag filters - they may be too restrictive
SSL Certificate errors:
- Use
verify_ssl: false
for testing - Check certificate paths and permissions
- Ensure certificates are valid and not expired
Permission denied:
- Verify LXD client certificates have proper permissions
- Check that user is in
lxd
group for Unix socket access
IP addresses not detected:
- Check
ignore_interfaces
configuration - Verify instances have network interfaces configured
- Use
--debug
to see network interface processing
Group names not as expected:
- Check
group_formats
configuration - Use
--debug
to see group name generation - Verify template variables are correct
User groups not appearing:
- Verify
user_groups.enabled
istrue
- Check the
user_groups.key
configuration - Use
--debug
to see user group parsing - Ensure instances have the correct configuration key set
# Test specific endpoint
./lxd_inventory.py --list --endpoint production --debug
# Test tag filtering
./lxd_inventory.py --list --tag "user.ansible=true" --debug
# Validate instance details
./lxd_inventory.py --instance mycontainer
# Test group formatting
./lxd_inventory.py --list --debug | grep -E "(Debug:|\"hosts\")"
Bug reports and feature requests are welcome. When reporting issues, please include:
- Configuration file (redacted)
- Command used
- Debug output (
--debug
) - LXD version and OS details
This script is provided as-is for managing LXD infrastructure with Ansible.