Task 9 - Working with Templates
In the previous tasks, you deployed Meraki configuration using the regular Data Model where every setting was explicitly defined — even when the same SSID, switch policy, or firewall rule was identical across networks. If you had 10 branch networks with the same wireless and security configuration, you would need to repeat that entire YAML block 10 times. A single change — say, updating a RADIUS server address — would require editing every copy individually. This approach works for a handful of networks, but it does not scale, and it introduces risk: one missed update creates configuration drift between sites.
Templates solve this problem. Instead of repeating configuration, you define it once as a named template with variable placeholders (${variable_name}), and then each network simply references the templates it needs and supplies its own site-specific values. The shared configuration lives in one place. A change to the template propagates to every network that uses it — consistently and automatically.
In this task, we will work with two files:
08_templates.nac.yaml— Defines reusable named templates for network setup, switch, wireless, and appliance/firewall configuration.08_branch_variables.nac.yaml— Defines two networks (Rome-Network-1andLisbon-Network-1) that reference those templates and supply site-specific variable values.
Let us walk through each file section by section.
Understanding the Templates File
Section titled “Understanding the Templates File”The templates file (08_templates.nac.yaml) resides in the lab-data/ folder in Code Server. It uses the meraki.templates.networks section of the data model to define reusable network-level configuration blocks. Each template has a name, a type (set to model), and a configuration block containing the actual settings with ${variable} placeholders where site-specific values will be substituted.
This file defines four templates:
Template 1: nw_setup_mgmt — Network Setup and Management
Section titled “Template 1: nw_setup_mgmt — Network Setup and Management”This template covers the foundational network settings — the same kind of configuration you deployed manually in Task 5 for Amsterdam-Network-1.
meraki: templates: networks: - name: nw_setup_mgmt type: model configuration: product_types: - appliance - switch - wireless - cellularGateway time_zone: ${time_zone} notes: ${network_notes} tags: - cleu_branch settings: local_status_page_enabled: true remote_status_page: true secure_port: false local_status_page_authentication: username: ${local_page_username} password: !env secret_password named_vlans: true snmp: access: users users: - username: ${snmp_username} passphrase: !env secret_password syslog_servers: - host: ${syslog_server} port: ${syslog_port} roles: - Switch Event log - Air Marshal events - Flows - URLs - Wireless Event log - Appliance Event logSummary: This template sets up the network with appliance, switch, wireless, and cellular gateway product types. It configures the local and remote status pages, SNMP with user-based access, and syslog forwarding. Notice how values like time_zone, network_notes, syslog_server, and snmp_username are expressed as ${variable} placeholders — these will be filled in by each network’s variable definitions. Settings that are consistent across all networks (such as syslog roles, product types, and status page flags) are hardcoded directly in the template.
Template 2: switch — Switch Configuration
Section titled “Template 2: switch — Switch Configuration”This template defines the switch access policies, general settings, MTU, multicast, DSCP-to-CoS mappings, and QoS rules.
- name: switch type: model configuration: switch: access_policies: - name: 'Wired Access Policy' radius_servers: - host: ${radius_server1_host} port: ${radius_server1_port} secret: !env secret_password radius_accounting_servers: - host: ${radius_accounting_server1_host} port: ${radius_accounting_server1_port} secret: !env secret_password radius_accounting: true radius_coa_support: true radius_testing: true radius_group_attribute: '11' host_mode: Single-Host voice_vlan_clients: true access_policy_type: Hybrid authentication radius: re_authentication_interval: 86400 dot1x_control_direction: both url_redirect_walled_garden: false settings: vlan: 999 use_combined_power: false uplink_client_sampling: true mac_blocklist: false mtu: default_mtu_size: ${switch_mtu_size} routing_multicast: default_settings: igmp_snooping: true flood_unknown_multicast_traffic: false dscp_to_cos_mappings: - cos: 0 dscp: 8 title: Scavenger CS1 - cos: 1 dscp: 0 title: Default CS0 - cos: 5 dscp: 46 title: Voice EF # ... additional DSCP-to-CoS mappings omitted for brevity qos_rules: - vlan: 20 protocol: ANY dscp: -1 qos_rule_name: Trust_DSCP_Voice_VLAN - vlan: 10 protocol: UDP source_port_range: 16384-32767 destination_port_range: 16385-32767 dscp: 46 qos_rule_name: Voice_to_EF_Data_VLAN # ... additional QoS rules omitted for brevitySummary: This template configures a wired access policy with RADIUS authentication and accounting, switch settings with VLAN 999 as default, MTU configuration (parameterized via ${switch_mtu_size}), multicast settings with IGMP snooping, a full set of DSCP-to-CoS mappings for QoS classification, and QoS rules for voice and data traffic. The RADIUS server addresses are parameterized so each site can point to its own servers, while the QoS and DSCP mappings are standardized across all branches.
Template 3: wireless — Wireless Settings and SSIDs
Section titled “Template 3: wireless — Wireless Settings and SSIDs”This template defines wireless settings, RF profiles, and two SSIDs: a corporate Data SSID with 802.1X RADIUS and a Guest SSID with a splash page.
- name: wireless type: model configuration: wireless: settings: upgrade_strategy: minimizeUpgradeTime meshing: false rf_profiles: - name: Corp wireless rf profile band_selection_type: ssid min_bitrate_type: band per_ssid_settings: - ssid_name: ${guest_wireless_ssid_name} band_operation_mode: dual band_steering: true bands: - '2.4' - '5' - ssid_name: ${data_wireless_ssid_name} band_operation_mode: dual band_steering: true bands: - '2.4' - '5' - '6' transmission: true ssids: - name: ${data_wireless_ssid_name} auth_mode: 8021x-radius available_on_all_aps: true band_selection: Dual band operation with Band Steering default_vlan_id: 10 dot11r: enabled: true dot11w: enabled: true required: false enabled: true encryption_mode: wpa-eap ip_assignment_mode: Bridge mode lan_isolation: false radius: accounting: true accounting_servers: - host: ${radius_accounting_server1_host} port: 1813 secret: !env secret_password - host: ${radius_accounting_server2_host} port: 1813 secret: !env secret_password servers: - host: ${radius_server1_host} port: 1812 secret: !env secret_password - host: ${radius_server2_host} port: 1812 secret: !env secret_password testing: true unavailability_schedules: enabled: ${data_ssid_schedules_enabled} ranges: - start_day: Sunday start_time: 00:00:00 end_time: 06:00:00 end_day: Sunday - start_day: Saturday start_time: 00:00:00 end_time: 06:00:00 end_day: Saturday ssid_number: '0' use_vlan_tagging: true visible: true wpa_encryption_mode: WPA2 only - name: ${guest_wireless_ssid_name} auth_mode: open available_on_all_aps: true band_selection: Dual band operation default_vlan_id: 50 enabled: true firewall_l3_firewall_rules: allow_lan_access: false ip_assignment_mode: Bridge mode lan_isolation: true mandatory_dhcp: true per_client_bandwidth_limit_down: ${guest_wireless_client_bw_down} per_client_bandwidth_limit_up: ${guest_wireless_client_bw_up} per_ssid_bandwidth_limit_down: ${guest_wireless_ssid_bw_down} per_ssid_bandwidth_limit_up: ${guest_wireless_ssid_bw_up} splash_page: Click-through splash page splash_settings: welcome_message: ${guest_ssid_welcome_message} ssid_number: '1' use_vlan_tagging: true visible: trueSummary: This template creates two SSIDs. The Data SSID uses 802.1X RADIUS authentication with WPA2-EAP, fast roaming (802.11r), management frame protection (802.11w), and weekend unavailability schedules. The Guest SSID uses open authentication with a click-through splash page, LAN isolation, and parameterized bandwidth limits. An RF profile is defined for both SSIDs with band steering. SSID names, RADIUS servers, bandwidth limits, and schedule settings are all parameterized — each site can customize these through variables while the security model and wireless architecture remain consistent.
Template 4: sdwan_fw — Appliance, VLANs, Traffic Shaping, and Firewall
Section titled “Template 4: sdwan_fw — Appliance, VLANs, Traffic Shaping, and Firewall”This is the largest template. It covers appliance settings, VLANs, traffic shaping rules, L3 and L7 firewall rules, firewalled services, and content filtering.
- name: sdwan_fw type: model configuration: appliance: settings: client_tracking_method: 'MAC address' deployment_mode: routed dynamic_dns: prefix: 'my-network' enabled: true traffic_shaping: global_bandwidth_limits: limit_up: 5000 limit_down: 9000 rules: rules: - name: Rule 01 definitions: - type: applicationCategory value: 'meraki:layer7/category/24' - type: application value: meraki:layer7/application/332 per_client_bandwidth_limits: settings: custom bandwidth_limits: limit_up: 1000 limit_down: 1000 dscp_tag_value: 10 priority: high default_rules: false vlans: - vlan_id: 10 name: Data subnet: ${vlan10_subnet} appliance_ip: ${vlan10_appliance_ip} dhcp_handling: 'Run a DHCP server' mandatory_dhcp: false - vlan_id: 20 name: Voice subnet: ${vlan20_subnet} appliance_ip: ${vlan20_appliance_ip} dhcp_handling: 'Run a DHCP server' mandatory_dhcp: false - vlan_id: 30 name: IoT subnet: ${vlan30_subnet} appliance_ip: ${vlan30_appliance_ip} dhcp_handling: 'Run a DHCP server' mandatory_dhcp: false - vlan_id: 50 name: Guest subnet: ${vlan50_subnet} appliance_ip: ${vlan50_appliance_ip} dhcp_handling: 'Run a DHCP server' mandatory_dhcp: false - vlan_id: 999 name: Infra subnet: ${vlan999_subnet} appliance_ip: ${vlan999_appliance_ip} dns_nameservers: opendns dhcp_handling: 'Run a DHCP server' dhcp_lease_time: '1 day' dhcp_boot_options: false mandatory_dhcp: false firewall: l3_firewall_rules: rules: - comment: 'Block Bad Traffic' policy: deny protocol: udp source_port: 1433 source_cidr: Any destination_port: 1433 destination_cidr: Any - comment: 'Block SSH' policy: deny protocol: tcp source_port: 22 source_cidr: Any destination_port: 22 destination_cidr: Any firewalled_services: - service_name: 'ICMP' access: 'blocked' - service_name: 'web' access: 'restricted' allowed_ips: - '2.2.2.2' - '3.3.3.3' l7_firewall_rules: - policy: deny type: applicationCategory value: 'meraki:layer7/category/27' - policy: deny type: host value: 'abc.com' # ... additional L7 rules omitted for brevity content_filtering: allowed_url_patterns: - 'www.cisco.com' - '*.meraki.com' blocked_url_patterns: - www.socialmedia.net - www.example.badsitehere.com blocked_url_categories: - 'meraki:contentFiltering/category/C68' - 'meraki:contentFiltering/category/C69' url_category_list_size: fullListSummary: This template configures the MX appliance in routed mode with dynamic DNS, traffic shaping with global bandwidth limits and application-specific rules, five VLANs (Data, Voice, IoT, Guest, Infra) with DHCP and parameterized subnets and gateway IPs, L3 firewall rules blocking SQL and SSH traffic, firewalled services restricting ICMP and web access, L7 firewall rules blocking by application category, host, port, and IP range, and content filtering with URL allow/block lists and category filtering. The VLAN addressing is parameterized via ${vlan*_subnet} and ${vlan*_appliance_ip} variables so each site gets unique subnets. This template demonstrates how security and SD-WAN policies can be standardized across all branches.
Understanding the Variables File
Section titled “Understanding the Variables File”The variables file (08_branch_variables.nac.yaml) is where you define your actual networks. Each network references the templates it needs and provides the site-specific values for the ${variable} placeholders.
meraki: domains: - name: !env domain administrator: name: admin organizations: - name: !env org_name managed: true networks: - name: Rome-Network-1 templates: [nw_setup_mgmt, switch, wireless, sdwan_fw] variables: syslog_server: 3.4.5.6 syslog_port: 514 snmp_username: snmpuser local_page_username: localuser time_zone: Europe/Rome network_notes: This is a small branch network created via Netascode. vlan10_subnet: 10.1.10.0/24 vlan10_appliance_ip: 10.1.10.1 vlan20_subnet: 10.1.20.0/24 vlan20_appliance_ip: 10.1.20.1 # ... additional variablesLet us look at the key elements:
Template references: The templates field is a list of template names that this network should use. Rome-Network-1 references all four templates: [nw_setup_mgmt, switch, wireless, sdwan_fw]. If a particular site does not need wireless, you would simply remove wireless from this list — no need to touch the template itself.
Variables: The variables block provides the values that replace ${variable} placeholders in the templates. For example, syslog_server: 3.4.5.6 in the variables will replace every ${syslog_server} in the templates with 3.4.5.6.
Two networks, same templates, different values
Section titled “Two networks, same templates, different values”The power of this approach becomes clear when you see both networks side by side. Rome-Network-1 and Lisbon-Network-1 reference the exact same four templates but provide different site-specific values:
| Variable | Rome-Network-1 | Lisbon-Network-1 |
|---|---|---|
time_zone | Europe/Rome | Europe/Lisbon |
address | Rome | Lisbon |
vlan10_subnet | 10.1.10.0/24 | 10.2.10.0/24 |
vlan20_subnet | 10.1.20.0/24 | 10.2.20.0/24 |
vlan999_subnet | 10.1.255.0/24 | 10.2.255.0/24 |
static_route1_gw | 10.1.10.2 | 10.2.10.2 |
static_route2_gw | 10.1.20.2 | 10.2.20.2 |
Everything else — SSID names, RADIUS servers, syslog settings, QoS rules, firewall rules, content filtering — is identical because it comes from the shared templates. If you need to add a third branch in Madrid, you add one more network entry with its own variables and reference the same templates. No duplication. No drift.
Deploy Using Templates
Section titled “Deploy Using Templates”Now let us deploy this configuration.
Step 1: Prepare the data folder
Section titled “Step 1: Prepare the data folder”Since we are deploying with templates from a clean state, make sure the data/ folder does not contain any YAML files from the previous tasks. If you completed Task 8 (Removal of Resources), the folder should already be empty.
Copy both files from the
lab-datafolder to thedatafolder (using commands below) or manually as you did in previous tasks:Terminal window cd /home/dcloud/network-as-code/meraki-as-code/cp lab-data/08_templates.nac.yaml data/cp lab-data/08_branch_variables.nac.yaml data/Verify the
data/folder contains only these two files:Terminal window ls data/You should see two file in
datafolder:08_branch_variables.nac.yaml08_templates.nac.yaml
Step 2: Render the templates
Section titled “Step 2: Render the templates”Terraform already renders templates at runtime when you run terraform plan or terraform apply — so this step is not strictly required for deployment. However, rendering the templates ahead of time produces a merged_configuration.nac.yaml file that we can feed into nac-validate for pre-change validation. This lets you catch data model errors before Terraform makes any API calls to the Meraki Dashboard.
The rendering is done by the Template Rendering Module in the workspaces/ directory.
Navigate to the workspaces directory and run Terraform:
Terminal window cd /home/dcloud/network-as-code/meraki-as-code/workspaces/terraform initterraform apply -auto-approveThis will generate a
merged_configuration.nac.yamlfile — the fully rendered data model with all templates expanded and variables substituted.You can inspect the merged output to see what Terraform will actually deploy:
Terminal window code-server merged_configuration.nac.yamlYou should see two complete network definitions (
Rome-Network-1andLisbon-Network-1) with all template placeholders replaced by the variable values — no${...}remaining.
Step 3: Validate the rendered configuration
Section titled “Step 3: Validate the rendered configuration”Navigate back to the project root and run nac-validate on the rendered configuration:
cd /home/dcloud/network-as-code/meraki-as-code/nac-validate --non-strict -s schema.yaml -r rules/ workspaces/merged_configuration.nac.yamlIf validation passes with no errors, you are ready to plan.
Step 4: Plan your changes
Section titled “Step 4: Plan your changes”terraform planReview the plan output. You should see resources being created for both Rome-Network-1 and Lisbon-Network-1 — networks, settings, SNMP, syslog, switch policies, wireless SSIDs, appliance VLANs, firewall rules, content filtering, and more. All resources should be marked with + (create).
Step 5: Deploy the configuration
Section titled “Step 5: Deploy the configuration”terraform applyReview the plan summary and enter yes to confirm. Wait for the deployment to complete.
Step 6: Verify in Meraki Dashboard
Section titled “Step 6: Verify in Meraki Dashboard”Navigate to your Meraki Dashboard and verify:
- Both
Rome-Network-1andLisbon-Network-1appear under your organization - Each network has appliance, switch, wireless, and cellular gateway product types
- Network settings, SNMP, and syslog are configured identically on both networks
- Wireless SSIDs (
DataandGuest) are present on both networks - Switch access policies and QoS rules are configured
- Appliance VLANs, firewall rules, and content filtering are applied
- The only differences between the two networks are the site-specific values (time zone, subnets, addresses)
Step 7: Post-Deployment Testing
Section titled “Step 7: Post-Deployment Testing”Run nac-test to verify that the deployed configuration in the Meraki Dashboard matches the intent defined in your data model:
cd /home/dcloud/network-as-code/meraki-as-code/nac-test --templates tests/ --data workspaces/merged_configuration.nac.yaml --output test_resultsIf all tests pass, it confirms that the template-rendered configuration was applied correctly to both networks. You can download and open test_results/log.html in your browser to review the detailed results.
Clean-Up
Section titled “Clean-Up”Before moving on to the next tasks, remove all deployed configuration to start with a clean organization.
Delete all YAML files from the
data/folder:Terminal window cd /home/dcloud/network-as-code/meraki-as-code/rm data/*.nac.yamlRun
terraform applyto remove all resources from the Meraki Dashboard:Terminal window terraform applyTerraform will detect that the data model is now empty and plan the destruction of all previously deployed resources. Review the plan, confirm with
yes, and wait for the removal to complete.Verify in the Meraki Dashboard that the organization is now empty — no networks, SSIDs, VLANs, or firewall rules should remain.
Your lab environment is now ready for the next tasks.
Key Takeaway
Section titled “Key Takeaway”With templates, you defined the network configuration once and deployed it to two sites by simply providing different variable values. Adding a third, fourth, or fiftieth site is as simple as adding another network entry with its own variables — the templates handle the rest. When you need to update a policy across all sites, you change the template once and every network that references it receives the update on the next deployment.
This is the real power of Meraki as Code at scale: define once, deploy everywhere, change once, update everywhere.