Skip to content

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-1 and Lisbon-Network-1) that reference those templates and supply site-specific variable values.

Let us walk through each file section by section.


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 log

Summary: 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 brevity

Summary: 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: true

Summary: 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: fullList

Summary: 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.


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 variables

Let 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:

VariableRome-Network-1Lisbon-Network-1
time_zoneEurope/RomeEurope/Lisbon
addressRomeLisbon
vlan10_subnet10.1.10.0/2410.2.10.0/24
vlan20_subnet10.1.20.0/2410.2.20.0/24
vlan999_subnet10.1.255.0/2410.2.255.0/24
static_route1_gw10.1.10.210.2.10.2
static_route2_gw10.1.20.210.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.


Now let us deploy this configuration.

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.

  1. Copy both files from the lab-data folder to the data folder (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/
  2. Verify the data/ folder contains only these two files:

    Terminal window
    ls data/

    You should see two file in data folder:

    08_branch_variables.nac.yaml
    08_templates.nac.yaml

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.

  1. Navigate to the workspaces directory and run Terraform:

    Terminal window
    cd /home/dcloud/network-as-code/meraki-as-code/workspaces/
    terraform init
    terraform apply -auto-approve

    This will generate a merged_configuration.nac.yaml file — the fully rendered data model with all templates expanded and variables substituted.

  2. You can inspect the merged output to see what Terraform will actually deploy:

    Terminal window
    code-server merged_configuration.nac.yaml

    You should see two complete network definitions (Rome-Network-1 and Lisbon-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:

Terminal window
cd /home/dcloud/network-as-code/meraki-as-code/
nac-validate --non-strict -s schema.yaml -r rules/ workspaces/merged_configuration.nac.yaml

If validation passes with no errors, you are ready to plan.

Terminal window
terraform plan

Review 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).

Terminal window
terraform apply

Review the plan summary and enter yes to confirm. Wait for the deployment to complete.

Navigate to your Meraki Dashboard and verify:

  • Both Rome-Network-1 and Lisbon-Network-1 appear 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 (Data and Guest) 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)

Run nac-test to verify that the deployed configuration in the Meraki Dashboard matches the intent defined in your data model:

Terminal window
cd /home/dcloud/network-as-code/meraki-as-code/
nac-test --templates tests/ --data workspaces/merged_configuration.nac.yaml --output test_results

If 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.


Before moving on to the next tasks, remove all deployed configuration to start with a clean organization.

  1. Delete all YAML files from the data/ folder:

    Terminal window
    cd /home/dcloud/network-as-code/meraki-as-code/
    rm data/*.nac.yaml
  2. Run terraform apply to remove all resources from the Meraki Dashboard:

    Terminal window
    terraform apply

    Terraform 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.

  3. 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.


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.