Skip to main content
Profiles apply common attribute values and relationships to multiple objects, reducing duplication and ensuring consistency. Think of them as templates for object configuration.

How Profiles Work

Profiles define attribute values that cascade to objects:
Profile: "Production Device Defaults"
├── status: active
├── monitoring_enabled: true
├── backup_frequency: daily
└── maintenance_window: sunday-2am

Device: router-01
├── name: "router-01" (own value)
├── status: active (from profile)
├── monitoring_enabled: true (from profile)
└── site: atl1 (own value)

Device: router-02
├── name: "router-02" (own value)
├── status: active (from profile)
├── monitoring_enabled: true (from profile)
└── site: dfw1 (own value)
When you update the profile, all associated objects automatically inherit the changes.

Creating Profiles

Profiles are schema-specific. You define them in the schema, then create instances.

Define Profile Schema

In your schema file, define a profile for an object kind:
# Schema definition
profiles:
  - name: DeviceProfile
    namespace: Infra
    label: Device Profile
    description: Common settings for devices
    apply_to:
      - InfraDevice
    attributes:
      - name: status
        kind: Text
        optional: true
      - name: monitoring_enabled
        kind: Boolean
        optional: true
        default_value: true
      - name: backup_frequency
        kind: Text
        optional: true
        choices:
          - daily
          - weekly
          - never
    relationships:
      - name: maintenance_window
        peer: MaintenanceWindow
        optional: true
        cardinality: one
Load the schema:
infrahubctl schema load schema.yaml

Create Profile Instance

  1. Navigate to ProfilesDevice Profiles
  2. Click Add Device Profile
  3. Set profile values:
    • Name: Profile identifier
    • Label: Display name
    • Priority: Lower numbers = higher priority (default: 1000)
    • Set attribute values to apply
    • Set relationship peers to apply
  4. Click Save

Applying Profiles to Objects

Assign profiles to objects to apply their settings.

Assign During Creation

mutation {
  InfraDeviceCreate(
    data: {
      name: { value: "router-01" }
      site: { id: "<site-uuid>" }
      device_type: { id: "<type-uuid>" }
      profiles: [
        { id: "<profile-uuid>" }
      ]
    }
  ) {
    ok
    object {
      id
      status { value }  # Inherited from profile
      monitoring_enabled { value }  # Inherited from profile
    }
  }
}

Assign to Existing Object

mutation {
  InfraDeviceUpdate(
    data: {
      id: "<device-uuid>"
      profiles: [
        { id: "<profile-uuid>" }
      ]
    }
  ) {
    ok
  }
}

Profile Priority

When multiple profiles apply to the same object, priority determines which profile’s values are used.

Priority Rules

  1. Lower number = higher priority: Priority 100 overrides priority 1000
  2. Default priority: 1000 if not specified
  3. Per-attribute resolution: Each attribute uses the highest-priority profile that sets it
  4. Explicit values win: Values set directly on the object override all profiles

Example

# Profile 1 (priority 100)
profile1 = await client.create(
    kind="InfraDeviceProfile",
    profile_name="high-priority",
    profile_priority=100,
    status="active",
    monitoring_enabled=True
)

# Profile 2 (priority 200)
profile2 = await client.create(
    kind="InfraDeviceProfile",
    profile_name="low-priority",
    profile_priority=200,
    status="maintenance",  # This loses to profile1
    backup_frequency="daily"  # This wins (profile1 doesn't set it)
)

# Device with both profiles
device = await client.create(
    kind="InfraDevice",
    name="router-01",
    profiles=[profile1.id, profile2.id]
)

# Result:
# - status: "active" (from profile1, priority 100)
# - monitoring_enabled: True (from profile1, only one sets it)
# - backup_frequency: "daily" (from profile2, only one sets it)

Profile Inheritance Tracking

Infrahub tracks which values come from profiles.

Check Value Source

query {
  InfraDevice(name__value: "router-01") {
    edges {
      node {
        status {
          value
          is_from_profile
          source {
            id
            display_label
          }
        }
        monitoring_enabled {
          value
          is_from_profile
          source {
            id
          }
        }
      }
    }
  }
}
Response:
{
  "status": {
    "value": "active",
    "is_from_profile": true,
    "source": {
      "id": "<profile-uuid>",
      "display_label": "Production Defaults"
    }
  },
  "monitoring_enabled": {
    "value": true,
    "is_from_profile": true,
    "source": {
      "id": "<profile-uuid>"
    }
  }
}

Override Profile Values

Set a value directly on the object to override the profile:
mutation {
  InfraDeviceUpdate(
    data: {
      id: "<device-uuid>"
      status: { value: "maintenance" }  # Overrides profile
    }
  ) {
    ok
  }
}
Now is_from_profile is false and source is the device itself.

Profile with Relationships

Profiles can set relationship values.

Define Relationship in Profile Schema

profiles:
  - name: DeviceProfile
    relationships:
      - name: tags
        peer: BuiltinTag
        optional: true
        cardinality: many
      - name: maintenance_window
        peer: MaintenanceWindow
        optional: true
        cardinality: one

Set Relationships in Profile

mutation {
  InfraDeviceProfileCreate(
    data: {
      profile_name: { value: "production-defaults" }
      tags: [
        { id: "<tag-production-uuid>" },
        { id: "<tag-monitored-uuid>" }
      ]
      maintenance_window: { id: "<window-uuid>" }
    }
  ) {
    ok
  }
}
All devices with this profile inherit the tags and maintenance window.

Updating Profiles

Changes to profiles automatically propagate to associated objects.

Update Profile Values

mutation {
  InfraDeviceProfileUpdate(
    data: {
      id: "<profile-uuid>"
      backup_frequency: { value: "weekly" }  # Change from daily to weekly
    }
  ) {
    ok
  }
}
All devices using this profile now have backup_frequency: "weekly" (unless they override it).

Profile Change Propagation

The NodeProfilesApplier handles propagation:
from infrahub.profiles.node_applier import NodeProfilesApplier

# After updating a profile
applier = NodeProfilesApplier(db=db, branch=branch)

# Apply to a specific node
await applier.apply_profiles(node=device)
await device.save()

# Or apply to all nodes using the profile
devices = await client.all(
    kind="InfraDevice",
    profiles__id="<profile-uuid>"
)

for device in devices:
    await applier.apply_profiles(node=device)
    await device.save()
Infrahub automatically applies profiles during save operations.

Use Cases

Environment Profiles

Define profiles per environment:
# Production profile
prod_profile = await client.create(
    kind="InfraDeviceProfile",
    profile_name="production",
    monitoring_enabled=True,
    backup_frequency="daily",
    sla="99.99"
)

# Development profile
dev_profile = await client.create(
    kind="InfraDeviceProfile",
    profile_name="development",
    monitoring_enabled=False,
    backup_frequency="never",
    sla="none"
)

Location Profiles

Define profiles per datacenter:
# Atlanta DC profile
atl_profile = await client.create(
    kind="InfraDeviceProfile",
    profile_name="atlanta-dc",
    timezone="America/New_York",
    dns_servers=["10.0.1.1", "10.0.1.2"],
    ntp_servers=["10.0.1.10"]
)

Role Profiles

Define profiles by device role:
# Edge router profile
edge_profile = await client.create(
    kind="InfraDeviceProfile",
    profile_name="edge-router",
    bgp_enabled=True,
    firewall_enabled=True,
    port_security=True
)

# Access switch profile
access_profile = await client.create(
    kind="InfraDeviceProfile",
    profile_name="access-switch",
    poe_enabled=True,
    vlan_mode="trunk",
    stp_enabled=True
)

Compliance Profiles

Define profiles for compliance requirements:
# PCI-DSS profile
pci_profile = await client.create(
    kind="InfraDeviceProfile",
    profile_name="pci-dss",
    encryption_required=True,
    audit_logging=True,
    password_complexity="high",
    session_timeout=15
)

Best Practices

  1. Use descriptive names: Make profile purpose clear
  2. Set appropriate priorities: Reserve low numbers (100-500) for critical profiles
  3. Document in label: Use profile_label for human-readable names
  4. Avoid too many profiles: 3-5 profiles per object is usually sufficient
  5. Profile by concern: Separate environment, location, and role profiles
  6. Test before applying: Validate profile values before applying to production objects
  7. Review periodically: Audit profile usage and remove unused profiles

Profiles vs. Templates

FeatureProfilesTemplates
PurposeApply common settingsCreate pre-configured objects
TimingApplied at any timeApplied at creation
UpdatesPropagate to objectsDon’t propagate
OverrideObjects can overrideObjects inherit but don’t track
Use caseShared defaultsObject blueprints
Use profiles for settings that should stay synchronized across objects. Use templates for creating objects with initial configuration that may diverge over time.

Next Steps

Build docs developers (and LLMs) love