Skip to main content

Understanding Infrahub’s schema system

Infrahub’s schema system provides a flexible, declarative way to define your infrastructure data model. Unlike traditional databases with rigid, single schemas, Infrahub allows different schemas per branch and stores the schema itself as data in the graph database. This design enables schema evolution, versioning, and experimentation without disrupting production environments.

Why a flexible schema matters

Infrastructure data models vary significantly across organizations. A network team managing a data center fabric has different needs than a cloud team orchestrating Kubernetes clusters. Infrahub doesn’t impose a rigid schema—instead, it provides a flexible foundation you can adapt to your specific requirements. This flexibility delivers several benefits:
  • Customization: Model your infrastructure exactly as it exists, without forcing it into predefined structures
  • Evolution: Change your data model as your infrastructure grows and transforms
  • Experimentation: Test schema changes in branches before applying them to production
  • Reusability: Share common patterns through generics while maintaining specific implementations
  • Validation: Enforce data quality through constraints and relationships at the schema level

Core concepts

Schema as data

Infrahub stores the schema itself in the graph database, treating it like any other object. This architectural decision enables powerful capabilities:
  • Each branch can have its own schema version
  • Schema changes follow the same branching and merging workflow as data changes
  • Schema history is preserved with full audit trail
  • Schema migrations run automatically during branch operations
When you load a schema into Infrahub, it becomes part of the database state. This means you can query the schema, version it, and evolve it using the same tools you use for infrastructure data.

The four building blocks

Infrahub schemas consist of four primary types: Nodes represent concrete objects in your infrastructure. Each node has attributes (direct values like text or numbers) and relationships (connections to other nodes). Nodes are the entities you create, query, and manage.
nodes:
  - name: Device
    namespace: Infra
    attributes:
      - name: hostname
        kind: Text
        unique: true
      - name: role
        kind: Dropdown
        choices:
          - name: core
            label: Core Router
          - name: leaf
            label: Leaf Switch
    relationships:
      - name: location
        peer: InfraLocation
        cardinality: one
        kind: Attribute
Attributes define the properties of nodes. Infrahub provides rich attribute types including Text, Number, IPAddress, DateTime, Password, and many others. Each attribute type determines how the value is validated, stored, and displayed in the UI. Relationships connect nodes together, forming the graph structure. Relationships have cardinality (one or many) and kinds that determine their behavior:
  • Attribute: Related entities appear in list and detail views
  • Component: Related entities appear in separate tabs, with cascade deletion
  • Parent: Hierarchical relationships that enable filtering and organization
  • Generic: Flexible relationships without specific functional significance
Generics enable code reuse by sharing attributes and relationships across multiple node types. They work like class inheritance in programming:
generics:
  - name: Device
    namespace: Generic
    attributes:
      - name: name
        kind: Text
        unique: true
      - name: description
        kind: Text
        optional: true

nodes:
  - name: Router
    namespace: Network
    inherit_from:
      - GenericDevice
    attributes:
      - name: routing_protocol
        kind: Dropdown
        choices:
          - name: bgp
          - name: ospf
The Router node inherits name and description from GenericDevice and adds its own routing_protocol attribute.

Schema namespaces and kinds

Every node and generic has a namespace and name. Together, these form the “kind”—a unique identifier for the schema element:
name: Device
namespace: Infra
# Kind becomes: InfraDevice
Reserved namespaces (Core, Infrahub, Profile, Builtin) are used by the system and cannot be used for custom schemas. This separation ensures your schema doesn’t conflict with internal objects.

Architecture and design decisions

Declarative and incremental

The schema format is declarative—you describe what you want, not how to create it. It’s also incremental, meaning you can load schema fragments from multiple sources that compose into a complete schema. To explicitly remove a schema element, use state: absent:
nodes:
  - name: Rack
    namespace: Infra
    state: absent
Without this flag, omitting an element simply ignores it rather than deleting it.

Branch-aware, agnostic, and local

Schema elements can operate in three branching modes: Branch-aware (default): Changes are local to the branch and can be merged. This mode supports the standard branching workflow—changes in feature branches merge into main through proposed changes. Branch-agnostic: Changes are immediately visible in all branches. This mode is useful for system-level configurations and reference data that should remain consistent across all branches:
nodes:
  - name: Country
    namespace: Location
    branch: agnostic
Branch-local: Changes stay in the branch but are never merged. This mode supports temporary data like test fixtures or branch-specific metadata:
nodes:
  - name: TestData
    namespace: Internal
    branch: local
Attributes and relationships can override the parent node’s branch mode. By default:
  • Attributes inherit their node’s branch mode
  • Relationships become branch-agnostic only if both connected nodes are branch-agnostic

Schema validation and migration

When you update a schema, Infrahub automatically validates existing data against the new schema and performs necessary migrations. This process occurs:
  • When loading a schema into a branch
  • During proposed change validation
  • When rebasing a branch
  • When merging branches
Infrahub enforces several validation rules:
  • Required attributes must have values
  • Unique constraints must not have duplicates
  • Relationships must reference valid nodes
  • Attribute types must match the data
The system also runs data migrations to adapt existing data to schema changes. For example, if you change an attribute from optional to required, Infrahub will validate that all existing objects have values for that attribute.

Implementation examples

Human-friendly identifiers (HFID)

Infrahub generates UUIDs for internal object identification, but infrastructure teams need human-readable identifiers. The human_friendly_id property defines which attributes compose a unique, readable identifier:
nodes:
  - name: Interface
    namespace: Network
    human_friendly_id: ["device__name__value", "name__value"]
    attributes:
      - name: name
        kind: Text
    relationships:
      - name: device
        peer: NetworkDevice
        cardinality: one
        optional: false
This creates HFIDs like (router-1, eth0) that uniquely identify interfaces based on device name and interface name. HFIDs enable:
  • Idempotent scripts that reference objects before they exist
  • Data synchronization between systems
  • GraphQL upsert operations using readable identifiers

Uniqueness constraints

Complex uniqueness requirements beyond single attributes use uniqueness_constraints. For example, ensuring each person owns at most one car model:
nodes:
  - name: Car
    namespace: Example
    uniqueness_constraints:
      - ["owner", "model__value"]
    attributes:
      - name: model
        kind: Text
    relationships:
      - name: owner
        peer: ExamplePerson
        cardinality: one
        optional: false
Performance tip: Order constraint elements from most selective (fewest matches) to least selective. If addresses are more unique than namespaces, use:
uniqueness_constraints:
  - ["address__value", "ip_namespace"]  # Better
  # - ["ip_namespace", "address__value"]  # Worse

Hierarchical schemas

Some infrastructure naturally forms hierarchies—locations (region → country → city) or organizational structures. Hierarchical mode enables querying across these relationships:
generics:
  - name: Generic
    namespace: Location
    hierarchical: true
    attributes:
      - name: name
        kind: Text

nodes:
  - name: Region
    namespace: Location
    inherit_from: ["LocationGeneric"]
    parent: ""
    children: "LocationCountry"
  - name: Country
    namespace: Location
    inherit_from: ["LocationGeneric"]
    parent: "LocationRegion"
    children: "LocationCity"
  - name: City
    namespace: Location
    inherit_from: ["LocationGeneric"]
    parent: "LocationCountry"
    children: ""
Hierarchical nodes automatically gain parent, children, ancestors, and descendants relationships. In GraphQL, you can query members of a group including all descendant groups:
query {
  CoreStandardGroup(name__value: "US-East") {
    members(include_descendants: true) {
      # Returns members from US-East and all sub-regions
    }
  }
}
The schema controls how models appear in the frontend sidebar:
nodes:
  - name: Device
    namespace: Network
    icon: "mdi:server"
    include_in_menu: true
    menu_placement: "NetworkInfrastructure"
This creates a nested menu structure with Material Design icons from the iconify library.

Schema evolution patterns

Loading schemas

Schemas can be loaded through multiple methods: Via infrahubctl:
infrahubctl schema check schema.yml  # Validate without loading
infrahubctl schema load schema.yml --branch feature-network
Via Git integration: Declare schemas in .infrahub.yml:
schemas:
  - schemas/network.yml
  - schemas/locations/
Infrahub automatically loads schemas from integrated Git repositories.

Renaming schema elements

Namespace and name form the identifier, so renaming requires providing the internal UUID temporarily:
nodes:
  - name: Switch  # Previously "Device"
    namespace: Network
    attributes:
      - id: 76df607c-883f-4cf6-9087-353dc2d863e5
        name: hostname  # Previously "name"
        kind: Text
Find UUIDs in the schema explorer at /schema in the frontend.

Schema strict mode

By default, Infrahub runs all schema validators on every change. When upgrading versions that introduce new validators, you may need to temporarily disable strict mode:
export INFRAHUB_SCHEMA_STRICT_MODE=false
This disables certain validators:
  • Relationships same parent constraint
  • Min/max value and length comparisons
  • HFID uniqueness requirements
  • Number attribute constraint validation
Re-enable strict mode after resolving schema and data issues.

Design trade-offs

Flexibility vs. performance

Storing schema as data provides flexibility but adds complexity. Schema queries require database access, and schema changes trigger validation and migration. Infrahub mitigates this through aggressive caching—the SchemaManager maintains an in-memory cache of branch schemas with LRU eviction. The performance trade-off is worthwhile for infrastructure management, where schema changes are infrequent but the ability to version and branch schemas is invaluable.

Generic inheritance limitations

Generics propagate properties to nodes only during first import. Subsequent generic updates don’t automatically update implementing nodes. This limitation simplifies the system—nodes have stable properties that don’t change unexpectedly when generics update. If you need to update node properties after changing a generic, explicitly update each node.

Validation timing

The default branch enforces comprehensive validation to ensure production data quality. Feature branches relax some constraints for development speed. This creates a trade-off: faster iteration in branches but validation errors may appear later during merge. Proposed Changes validate thoroughly before merge, catching issues before they reach production.

Build docs developers (and LLMs) love