Manage a Cisco ACI application profile — the container that groups an application's EPGs within a tenant (class
fvAp, DNuni/tn-{tenant}/ap-{name}) — as a typed, secure-by-default building block targetingCiscoDevNet/aci ~> 2.20.
This module manages a single ACI application profile and its metadata tail as one coherent, secure-by-default unit:
- 📦 The application profile (
aci_application_profile.this) — the container in the ACI Management Information Tree (MIT) that groups the EPGs representing one application's tiers within a tenant, addressed by the Distinguished Nameuni/tn-{tenant}/ap-{name}. - 🎚️ QoS priority class —
priority, left at the safeunspecifieddefault; a fabric-wide QoS class is opt-in. - 🏷️ The ACI metadata tail —
annotation(preserved asorchestrator:terraformso Terraform-managed objects are identifiable in APIC),name_alias, anddescription, plusowner_key/owner_tagand theannotations/tagskey-value lists. - 🔗 A typed monitoring-policy relation — an optional
relation_to_monitoring_policythat binds the application profile to a monitoring policy by name. - 🔑 Scope via the tenant DN — a required
tenant_dnwires this module under its parent tenant; authentication and the APIC URL remain the caller's provider concern and are never module variables.
💡 Why it matters: the application profile is the organizational unit that every application EPG nests under — it is how a tenant's application tiers are grouped, delegated, and reasoned about independently of the tenant's other applications. A consistent, identifiable, least-privilege application profile is the anchor every downstream EPG module wires into through its
application_profile_dninput.
If these Terraform modules have been helpful to you or your organization, I'd appreciate your support in any of the following ways:
- ⭐ Star this repository to help others discover this Terraform module.
- 🤝 Connect with me on LinkedIn: linkedin.com/in/microsoftexpert
- ☕ Buy me a coffee: buymeacoffee.com/microsoftexpert
Whether it's a star, a professional connection, or a coffee, every gesture helps keep these modules actively maintained and continually improving. Thank you for being part of the community!
graph LR
apic["Cisco APIC fabric (provider auth, out of band)"]:::ext
tn["terraform-aci-tenant"]:::sib
ap["terraform-aci-application-profile (this module)"]:::this
fv["aci_application_profile - class fvAp - DN uni/tn-{tenant}/ap-{name}"]:::keystone
epg["terraform-aci-application-epg"]:::sib
mon["terraform-aci-monitoring-policy"]:::sib
apic -->|"provider configured by caller"| ap
tn -->|"tenant_dn"| ap
ap -->|"manages"| fv
ap -->|"application_profile_dn"| epg
mon -->|"relation_to_monitoring_policy (by name)"| ap
classDef this fill:#00BCEB,color:#fff,stroke:#00BCEB;
classDef keystone fill:#0D274D,color:#fff,stroke:#0D274D;
classDef sib fill:#f5f5f5,color:#333,stroke:#cccccc;
classDef ext fill:#eeeeff,color:#333,stroke:#9999ff;
The application profile sits directly under its parent tenant. It takes tenant_dn as its only parent variable and emits an id (its DN, uni/tn-{tenant}/ap-{name}) that the application-EPG module consumes as application_profile_dn.
graph TD
tdn["tenant_dn (required, parent DN)"]:::in
n["application_profile.name (required, immutable)"]:::in
pr["application_profile.priority (QoS class)"]:::in
meta["annotation / name_alias / description / owner_key / owner_tag"]:::in
at["annotations / tags (key/value maps)"]:::in
rel["relation_to_monitoring_policy (typed)"]:::in
this["aci_application_profile.this (keystone, fvAp)"]:::this
oid["output: id (DN uni/tn-{tenant}/ap-{name})"]:::out
onm["output: name"]:::out
tdn --> this
n --> this
pr --> this
meta --> this
at --> this
rel --> this
this --> oid
this --> onm
classDef this fill:#00BCEB,color:#fff,stroke:#00BCEB;
classDef in fill:#f5f5f5,color:#333,stroke:#cccccc;
classDef out fill:#eeeeff,color:#333,stroke:#9999ff;
Resource inventory
| Resource | Name | Cardinality | Role |
|---|---|---|---|
aci_application_profile |
this |
1 (keystone) | The application profile / EPG container (fvAp). |
| Requirement | Value |
|---|---|
| Terraform | >= 1.3.0 (uses optional() object defaults) |
| Provider | CiscoDevNet/aci ~> 2.20 |
| Provider block | None in this module — the caller configures and authenticates the provider (username/password, X.509 signature, or login domain) out of band. |
| Scope | tenant_dn — the parent tenant's DN (uni/tn-{name}). |
Schema notes that bite (verified against the live provider schema):
- 🔒
application_profile.nameis immutable. Changing it forces replacement — a brand-new application profile, and every EPG created under its DN goes with it. Treat renames as migrations. ⚠️ The parent is wired throughparent_dn, nottenant_dn. The live schema still exposes atenant_dnresource attribute alongsideparent_dn; this module always setsparent_dn(the current attribute) and never the legacy one.- ℹ️
aci_application_profileis a migrated (plugin-framework) resource in this provider line.annotationsandtagsare typed{ key, value }lists andrelation_to_monitoring_policyis a typed nested attribute — all assigned with=, not as HCLblock {}stanzas. ⚠️ annotation(singular) andannotations(plural) are different fields.annotationis the single orchestrator marker (defaults toorchestrator:terraform);annotationsis the separate user-defined tagAnnotation list. This module manages both.- ℹ️
priorityhas a documented enum plus an undocumented sibling. The validpriorityvalues arelevel1-level6andunspecified; the schema also exposes an undocumentedprioattribute, which this module does not manage. - ℹ️ Classic relations are retained but unused here. The provider still exposes a flat
relation_fv_rs_ap_mon_polattribute from before migration; this module uses the migrated typedrelation_to_monitoring_policyand leaves the classic attribute to the provider. - ℹ️
idis the DN (uni/tn-{tenant}/ap-{name}), computed by APIC at create. It is the value downstream EPG modules consume asapplication_profile_dn. ⚠️ validate_relation_dn(provider defaulttrue) means arelation_to_monitoring_policypointing at a monitoring policy that does not yet exist will fail at apply — create the policy first, or wire it by reference.
Scope the caller's APIC login to the least privilege this module needs:
- Create / delete an application profile: the
tenant-adminrole (or a custom role with application-profile write privilege) scoped to the tenant's security domain. This is a tenant-scoped action, not a fabric-wide one. - Day-2 management: the same
tenant-adminwrite scope to the tenant's security domain covers ongoing changes. - Referenced monitoring policy: read on the monitoring policy targeted by
relation_to_monitoring_policy.
The module never sees a credential — authentication is a provider/caller concern supplied out of band (e.g. ACI_USERNAME / ACI_PASSWORD, or ACI_PRIVATE_KEY / ACI_CERT_NAME for signature-based auth).
- A reachable Cisco APIC (
ACI_URL) whose version is compatible with the~> 2.20provider, with the provider configured and authenticated by the caller. - The parent tenant (
tenant_dn) must already exist — this module does not create tenants. - In production, the provider should be configured with
insecure = falseand proper CA trust — the provider's own default (insecure = true, skip TLS verification) is not a safe steady state. - If you set
relation_to_monitoring_policy, the referenced monitoring policy must exist (or be created in the same configuration) so the provider's DN validation passes.
terraform-aci-application-profile/
├── providers.tf # terraform{} + required_providers (aci ~> 2.20); no provider block
├── variables.tf # tenant_dn + the application_profile object — deeply typed, secure defaults, heredoc schema, validations
├── main.tf # aci_application_profile.this (keystone) + metadata tail + typed monitoring relation
├── outputs.tf # id (the DN) first, then name
├── README.md # this document
├── SCOPE.md # cross-module contract (scope, consumes/emits, roles, prerequisites)
├── LICENSE # MIT
└── .gitignore # canonical library ignore set
# The caller configures the provider (authentication is out of band).
provider "aci" {
# username / password, or private_key + cert_name for signature auth;
# url = "https://apic.example.com"; set insecure = false in production.
}
module "tenant" {
source = "git::https://github.com/microsoftexpert/terraform-aci-tenant.git?ref=v1.0.0"
tenant = { name = "core-prod" }
}
module "application_profile" {
source = "git::https://github.com/microsoftexpert/terraform-aci-application-profile.git?ref=v1.0.0"
tenant_dn = module.tenant.id
application_profile = {
name = "payments"
}
}
output "application_profile_dn" {
value = module.application_profile.id # pass this as application_profile_dn to the EPG module
}Consumes
| Input | Type | Typical source |
|---|---|---|
tenant_dn |
string (DN) |
terraform-aci-tenant output id |
application_profile |
object({...}) |
caller (name + QoS priority + metadata tail + optional monitoring relation) |
Emits
| Output | Description | Consumed by |
|---|---|---|
id |
Application profile DN (uni/tn-{tenant}/ap-{name}) — primary reference |
terraform-aci-application-epg (as application_profile_dn) |
name |
Application profile name | composition / audit |
1 · Minimal — an application profile with secure defaults
module "application_profile" {
source = "git::https://github.com/microsoftexpert/terraform-aci-application-profile.git?ref=v1.0.0"
tenant_dn = module.tenant.id
application_profile = {
name = "payments"
}
}💡 The minimal call creates only the application profile.
annotationis preserved asorchestrator:terraform,prioritystaysunspecified, and no relations are forced.
2 · Description and GUI alias
module "application_profile" {
source = "git::https://github.com/microsoftexpert/terraform-aci-application-profile.git?ref=v1.0.0"
tenant_dn = module.tenant.id
application_profile = {
name = "payments"
name_alias = "Payments Platform"
description = "EPG container for the payments application tiers"
}
}ℹ️
name_aliasis a display alias shown in the APIC GUI;nameremains the immutable identity encoded in the DN.
3 · A dedicated QoS priority class
module "application_profile" {
source = "git::https://github.com/microsoftexpert/terraform-aci-application-profile.git?ref=v1.0.0"
tenant_dn = module.tenant.id
application_profile = {
name = "payments"
priority = "level1"
}
}
⚠️ prioritywidens QoS treatment fabric-wide for traffic in this application profile's EPGs. Leave it at the defaultunspecifiedunless a QoS design calls for a dedicated class.
4 · A custom annotation marker
module "application_profile" {
source = "git::https://github.com/microsoftexpert/terraform-aci-application-profile.git?ref=v1.0.0"
tenant_dn = module.tenant.id
application_profile = {
name = "payments"
annotation = "orchestrator:terraform:platform-team"
}
}🔒 Keep the
orchestrator:terraformprefix so Terraform-managed objects stay identifiable in APIC. This suite defaultsannotationtoorchestrator:terraform; override it only to extend, not to erase, that marker.
5 · User metadata via the annotations (tagAnnotation) list
module "application_profile" {
source = "git::https://github.com/microsoftexpert/terraform-aci-application-profile.git?ref=v1.0.0"
tenant_dn = module.tenant.id
application_profile = {
name = "payments"
annotations = {
"cost-center" = "CC-4021"
"environment" = "production"
"owner" = "app-platform"
}
}
}ℹ️
annotationsis given as an ergonomic{ key = value }map and rendered as the ACItagAnnotation{key, value}list. Keys are unique by construction.
6 · Classification tags (tagTag) list
module "application_profile" {
source = "git::https://github.com/microsoftexpert/terraform-aci-application-profile.git?ref=v1.0.0"
tenant_dn = module.tenant.id
application_profile = {
name = "payments"
tags = {
"tier" = "gold"
"pci" = "in-scope"
}
}
}ℹ️
tagsmaps to the ACItagTaglist — use it for classification/labeling distinct from the free-formannotations.
7 · Ownership and correlation keys
module "application_profile" {
source = "git::https://github.com/microsoftexpert/terraform-aci-application-profile.git?ref=v1.0.0"
tenant_dn = module.tenant.id
application_profile = {
name = "payments"
owner_key = "cmdb-9931"
owner_tag = "provisioned-by-app-automation"
}
}ℹ️
owner_keyandowner_taglet external systems correlate this object with their own records — neither carries security posture.
8 · A typed monitoring-policy relation
module "application_profile" {
source = "git::https://github.com/microsoftexpert/terraform-aci-application-profile.git?ref=v1.0.0"
tenant_dn = module.tenant.id
application_profile = {
name = "payments"
relation_to_monitoring_policy = {
monitoring_policy_name = "prod-monitoring"
}
}
}
⚠️ The referenced monitoring policy must exist (or be created in the same apply). With the provider'svalidate_relation_dndefault oftrue, a dangling relation fails at apply — fix the missing policy rather than disabling validation.
9 · A fully-annotated application profile (all metadata)
module "application_profile" {
source = "git::https://github.com/microsoftexpert/terraform-aci-application-profile.git?ref=v1.0.0"
tenant_dn = module.tenant.id
application_profile = {
name = "payments"
name_alias = "Payments Platform"
description = "EPG container for the payments application tiers"
priority = "level2"
owner_key = "cmdb-9931"
owner_tag = "app-automation"
annotations = { "cost-center" = "CC-4021", "environment" = "production" }
tags = { "tier" = "gold" }
}
}10 · Least-privilege operating model (documentation variant)
# Configure the provider with a login scoped to tenant management only —
# not a fabric-wide admin — for day-2 changes to an existing application profile.
provider "aci" {
# username = "svc-tenant-admin" # a tenant-admin role, write-scoped to
# # this tenant's security domain
# private_key = var.apic_private_key # signature auth avoids login-rate limits
# cert_name = "terraform-cert"
# url = "https://apic.example.com"
# insecure = false
}
module "application_profile" {
source = "git::https://github.com/microsoftexpert/terraform-aci-application-profile.git?ref=v1.0.0"
tenant_dn = module.tenant.id
application_profile = { name = "payments" }
}🔒 Delegate application-profile management to a
tenant-adminrole scoped to the tenant's security domain. Prefer signature-based (X.509) auth for automation to avoid APIC login-rate thresholds.
11 · Many application profiles from one definition (caller-side for_each)
locals {
application_profiles = {
"payments" = { name = "payments", description = "Payments application tiers" }
"orders" = { name = "orders", description = "Order-management application tiers" }
"reporting" = { name = "reporting", description = "Reporting application tiers" }
}
}
module "application_profiles" {
source = "git::https://github.com/microsoftexpert/terraform-aci-application-profile.git?ref=v1.0.0"
for_each = local.application_profiles
tenant_dn = module.tenant.id
application_profile = each.value
}
output "application_profile_dns" {
value = { for k, m in module.application_profiles : k => m.id }
}💡 Instantiate the module with
for_eachto manage a fleet of application profiles under one tenant from a single, auditable map.
12 · Reading outputs for downstream wiring
module "application_profile" {
source = "git::https://github.com/microsoftexpert/terraform-aci-application-profile.git?ref=v1.0.0"
tenant_dn = module.tenant.id
application_profile = { name = "payments" }
}
output "application_profile_dn" { value = module.application_profile.id } # uni/tn-core-prod/ap-payments
output "application_profile_name" { value = module.application_profile.name }13 · Wiring the tenant DN into an application profile
module "tenant" {
source = "git::https://github.com/microsoftexpert/terraform-aci-tenant.git?ref=v1.0.0"
tenant = { name = "core-prod" }
}
module "application_profile" {
source = "git::https://github.com/microsoftexpert/terraform-aci-application-profile.git?ref=v1.0.0"
tenant_dn = module.tenant.id # <-- the tenant's DN becomes the application profile's parent
application_profile = { name = "payments" }
}14 · Wiring the application profile DN toward an EPG
module "application_profile" {
source = "git::https://github.com/microsoftexpert/terraform-aci-application-profile.git?ref=v1.0.0"
tenant_dn = module.tenant.id
application_profile = { name = "payments" }
}
module "epg" {
source = "git::https://github.com/microsoftexpert/terraform-aci-application-epg.git?ref=v1.0.0"
application_profile_dn = module.application_profile.id # <-- the application profile's DN becomes the EPG's parent
application_epg = { name = "payments-web" }
}15 · 🏗️ End-to-end composition — tenant → VRF → bridge domain → application profile → EPG → contract
provider "aci" {
# configured + authenticated by the caller; insecure = false in production
}
# 1) The keystone tenant — the root everything nests under.
module "tenant" {
source = "git::https://github.com/microsoftexpert/terraform-aci-tenant.git?ref=v1.0.0"
tenant = { name = "core-prod", description = "Core production tenant" }
}
# 2) A VRF in the tenant.
module "vrf" {
source = "git::https://github.com/microsoftexpert/terraform-aci-vrf.git?ref=v1.0.0"
tenant_dn = module.tenant.id
vrf = { name = "core-vrf" }
}
# 3) A bridge domain bound to the VRF.
module "bd" {
source = "git::https://github.com/microsoftexpert/terraform-aci-bridge-domain.git?ref=v1.0.0"
tenant_dn = module.tenant.id
bridge_domain = { name = "app-bd", relation_to_vrf = { vrf_name = "core-vrf" } }
subnets = { "10.0.1.1/24" = { ip = "10.0.1.1/24" } }
}
# 4) This module — the application profile that groups the payments EPGs.
module "application_profile" {
source = "git::https://github.com/microsoftexpert/terraform-aci-application-profile.git?ref=v1.0.0"
tenant_dn = module.tenant.id
application_profile = { name = "payments" }
}
# 5) An application EPG under the application profile.
module "epg" {
source = "git::https://github.com/microsoftexpert/terraform-aci-application-epg.git?ref=v1.0.0"
application_profile_dn = module.application_profile.id
application_epg = { name = "payments-web", relation_to_bridge_domain = { bridge_domain_name = "app-bd" } }
}
# 6) A contract in the same tenant, consumed by the EPG.
module "contract" {
source = "git::https://github.com/microsoftexpert/terraform-aci-contract.git?ref=v1.0.0"
tenant_dn = module.tenant.id
contract = { name = "web-to-app" }
}
output "application_profile_dn" { value = module.application_profile.id }🏗️ One tenant, VRF, and bridge domain in; an application profile and its EPG out.
module.application_profile.idis the single reference every EPG under this application nests into viaapplication_profile_dn.
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
tenant_dn |
string (DN) |
✅ | — | DN of the parent tenant, e.g. module.tenant.id (format uni/tn-{name}). |
application_profile |
object({...}) |
✅ | — | The application profile: name (required, immutable) plus QoS priority, the metadata tail, and an optional typed monitoring relation. |
Full input schema (from variables.tf)
variable "tenant_dn" {
type = string
# validation: must match ^uni/tn- (a tenant DN)
}
variable "application_profile" {
type = object({
name = string # REQUIRED, immutable (force-new), 1-64 chars
priority = optional(string, "unspecified") # level1-level6 | unspecified
annotation = optional(string, "orchestrator:terraform") # ACI annotation marker (kept identifiable)
name_alias = optional(string, null) # GUI display alias
description = optional(string, null) # free-form description
owner_key = optional(string, null) # client correlation key
owner_tag = optional(string, null) # client correlation tag
annotations = optional(map(string), {}) # tagAnnotation {key,value} pairs as a {key = value} map
tags = optional(map(string), {}) # tagTag {key,value} pairs as a {key = value} map
relation_to_monitoring_policy = optional(object({ # typed relation fvRsApMonPol, by name
monitoring_policy_name = string
}), null)
})
# validation: name is 1-64 chars and matches ^[a-zA-Z0-9_.:-]+$ (ACI naming rules)
# validation: priority is one of level1-level6, unspecified
}| Output | Description | Notes |
|---|---|---|
id |
Application profile Distinguished Name (uni/tn-{tenant}/ap-{name}) |
Primary cross-module reference — pass as application_profile_dn. |
name |
Application profile name | For composition / audit. |
- One keystone, no children.
aci_application_profile.thisis the single resource. The application profile is an organizational container; the EPGs that live under it are owned byterraform-aci-application-epgand reference this application profile by DN — keeping each module small and composable along the MIT. - Migrated (plugin-framework) shape.
annotations,tags, andrelation_to_monitoring_policyare typed attributes, not HCL blocks. The module acceptsannotations/tagsas{ key = value }maps and converts each to the provider's[{ key, value }]list form — ergonomic for callers, faithful to the schema for the provider. - Unmanaged when empty. When
annotations/tagsare empty the module passesnullrather than an empty list, so it never fights provider-computed state or produces a spurious diff. - Current attribute, not the deprecated one. The parent is wired through
parent_dn; the live schema retains a legacytenant_dnresource attribute that this module deliberately does not set. - Immutable identity.
application_profile.nameis force-new: a rename destroys and recreates the application profile (and, server-side, every EPG under its DN). The two namevalidationblocks reject names that violate the ACI length/character rules at plan time, not apply time. - Secure by omission. The minimal call preserves the
orchestrator:terraformannotation, leavespriorityatunspecified, and sets no relations — nothing permissive is created by default.
| Concern | Secure default | How to opt out (deliberately) |
|---|---|---|
application_profile.annotation |
orchestrator:terraform — Terraform-managed objects stay identifiable in APIC |
Extend the marker (e.g. add a team suffix); do not blank it. |
application_profile.priority |
unspecified — no fabric-wide QoS class forced |
Set level1-level6 when a QoS design calls for it. |
annotations / tags |
{} — no user metadata managed, no spurious diffs |
Populate the maps explicitly. |
relation_to_monitoring_policy |
null — no relation forced |
Set it to bind a monitoring policy by name. |
| Transport (provider) | This suite instructs callers to set insecure = false with CA trust |
The provider default is insecure = true; do not keep it as a steady state. |
| Secrets | None accepted or emitted | n/a — the application profile carries no secret material; credentials are provider config. |
# From the module directory (offline, no credentials, no backend):
terraform init -backend=false
terraform validate
terraform fmt -check- Pin the module by immutable tag:
?ref=v1.0.0— never a branch. - This module is plan-only from the library's perspective. A human runs
terraform plan/applyagainst a sub-production APIC from their own pipeline, with a login scoped to the permissions above. No cloud apply happens here.
The offline proof gate for this module:
- ✅
terraform validate— parses the module, resolves theapplication_profileobject type, runs thenameandpriorityvalidations, and confirms every argument exists in the provider schema. - ✅
terraform fmt -check— canonical formatting. - ⛔ Not exercised offline (only a real
plan/applyagainst an APIC covers these): DN validation ofrelation_to_monitoring_policy(server-sidevalidate_relation_dn), APIC-side name-collision checks against the parent tenant, and the computed DN returned asid.
$ terraform output
id = "uni/tn-core-prod/ap-payments"
name = "payments"
| Symptom | Cause | Fix |
|---|---|---|
tenant_dn must be a tenant DN of the form uni/tn-{name} |
tenant_dn was not the tenant's DN |
Pass the tenant module's id output, e.g. module.tenant.id. |
application_profile.name must be 1-64 characters |
Name is empty or too long | Use a 1-64 character name. |
application_profile.name may contain only letters, digits, and the characters _ . : - |
Name has spaces or unsupported characters | Remove spaces/special characters (ACI naming rules). |
application_profile.priority must be one of: level1, level2, level3, level4, level5, level6, unspecified |
An invalid priority value was supplied |
Use one of the documented enum values. |
Changing name wants to destroy/recreate the application profile |
application_profile.name is immutable (force-new) |
Treat a rename as a migration; expect the application profile and its EPGs to be replaced. |
| Apply fails validating the monitoring-policy relation | relation_to_monitoring_policy targets a policy that does not exist |
Create the monitoring policy first (or in the same apply); do not disable validate_relation_dn. |
Post ... 401 / authentication error |
Provider not configured or wrong credentials | Configure the aci provider with valid credentials and url; prefer signature auth for automation. |
- Cisco ACI provider —
aci_application_profile - Cisco ACI provider — provider configuration & authentication
- Cisco APIC object model — class
fvAp(the application profile / EPG container). - Sibling modules:
terraform-aci-tenant,terraform-aci-application-epg,terraform-aci-vrf,terraform-aci-bridge-domain,terraform-aci-contract,terraform-aci-monitoring-policy. - This module's
SCOPE.md— the cross-module contract.
💙 "Infrastructure as Code should be standardized, consistent, and secure."