{"id":98,"date":"2026-01-27T12:00:00","date_gmt":"2026-01-27T12:00:00","guid":{"rendered":"https:\/\/rezachegini.com\/?p=98"},"modified":"2026-10-30T07:01:33","modified_gmt":"2026-10-30T07:01:33","slug":"deploying-azure-sql-database-with-arm-templates","status":"publish","type":"post","link":"https:\/\/rezachegini.com\/?p=98","title":{"rendered":"Deploying Azure SQL Database with ARM Templates"},"content":{"rendered":"<h3 class=\"wp-block-heading\">My Second ARM Template Challenge<\/h3><p class=\"wp-block-paragraph\">Hello again! In my last article, I showed you how to deploy a Web App with ARM templates. Today I want to share something more challenging &#8211; deploying Azure SQL Database with secure parameters.<\/p><p class=\"wp-block-paragraph\">This was harder than the Web App because I needed to handle passwords securely. Let me show you what I learned!<\/p><h3 class=\"wp-block-heading\">Why Azure SQL Database?<\/h3><p class=\"wp-block-paragraph\">Most web applications need a database. In my home lab, I&#8217;m building a simple product catalog app. It needs to store data somewhere. Azure SQL Database is a managed database service &#8211; Microsoft handles backups, updates, and security patches for me.<\/p><h3 class=\"wp-block-heading\">My Azure SQL ARM Template<\/h3><p class=\"wp-block-paragraph\">Here is my actual code from GitHub:<\/p><pre class=\"wp-block-code\"><code>{\n  \"$schema\": \"https:\/\/schema.management.azure.com\/schemas\/2019-04-01\/deploymentTemplate.json#\",\n  \"contentVersion\": \"1.0.0.0\",\n  \"parameters\": {\n    \"SQLLogin\": {\n            \"type\":\"string\",\n            \"metadata\":{\n                \"description\":\"The administrator user name\"\n            }\n        },\n        \"SQLPassword\": {\n            \"type\":\"secureString\",\n            \"metadata\":{\n                \"description\":\"The administrator password\"\n            }\n        }\n  },\n  \"functions\": [],\n  \"variables\": {},\n  \"resources\": [\n    {\n            \"type\": \"Microsoft.Sql\/servers\",\n            \"apiVersion\": \"2022-02-01-preview\",\n            \"name\": \"rezasqlserverr9473\",\n            \"location\": \"[resourceGroup().location]\",\n            \"properties\":{\n                \"administratorLogin\": \"[parameters('SQLLogin')]\",\n                \"administratorLoginPassword\": \"[parameters('SQLPassword')]\"\n            }\n        },\n        {\n               \"type\": \"Microsoft.Sql\/servers\/databases\",\n               \"apiVersion\": \"2022-02-01-preview\",\n               \"name\": \"[format('{0}\/{1}','rezasqlserverr9473','appdb')]\",\n               \"location\": \"[resourceGroup().location]\",\n               \"sku\":{\n                \"name\":\"Basic\",\n                \"tier\":\"Basic\"\n               },\n               \"properties\":{},\n                \"dependsOn\":[\n                    \"[resourceId('Microsoft.Sql\/servers','rezasqlserverr9473')]\"\n                ]\n        }\n  ],\n  \"outputs\": {}\n}<\/code><\/pre><h3 class=\"wp-block-heading\">Understanding Parameters &#8211; The New Part!<\/h3><p class=\"wp-block-paragraph\">This template is different from my first one. Look at the parameters section:<\/p><pre class=\"wp-block-code\"><code>\"parameters\": {\n    \"SQLLogin\": {\n        \"type\":\"string\",\n        \"metadata\":{\n            \"description\":\"The administrator user name\"\n        }\n    },\n    \"SQLPassword\": {\n        \"type\":\"secureString\",\n        \"metadata\":{\n            \"description\":\"The administrator password\"\n        }\n    }\n}<\/code><\/pre><h3 class=\"wp-block-heading\">What Are Parameters?<\/h3><p class=\"wp-block-paragraph\">Parameters make templates flexible. Instead of hard-coding username and password in the template, I pass them during deployment.<\/p><p class=\"wp-block-paragraph\">Important difference:<\/p><ul class=\"wp-block-list\"><li>&#8220;type&#8221;:&#8221;string&#8221; &#8211; normal parameter, visible in logs<\/li><li>&#8220;type&#8221;:&#8221;secureString&#8221; &#8211; password parameter, hidden in logs!<\/li><\/ul><p class=\"wp-block-paragraph\">When I first learned this, I used &#8220;type&#8221;:&#8221;string&#8221; for passwords. That was a security mistake! Always use secureString for passwords and secrets.<\/p><h3 class=\"wp-block-heading\">Metadata Section<\/h3><pre class=\"wp-block-code\"><code>\"metadata\":{\n    \"description\":\"The administrator user name\"\n}<\/code><\/pre><p class=\"wp-block-paragraph\">This is documentation inside the template. When someone deploys this template, they see this description. It helps others understand what this parameter is for.<\/p><h3 class=\"wp-block-heading\">Creating the SQL Server<\/h3><pre class=\"wp-block-code\"><code>{\n    \"type\": \"Microsoft.Sql\/servers\",\n    \"apiVersion\": \"2022-02-01-preview\",\n    \"name\": \"rezasqlserverr9473\",\n    \"location\": \"[resourceGroup().location]\",\n    \"properties\":{\n        \"administratorLogin\": \"[parameters('SQLLogin')]\",\n        \"administratorLoginPassword\": \"[parameters('SQLPassword')]\"\n    }\n}<\/code><\/pre><p class=\"wp-block-paragraph\">Technical details:<\/p><ul class=\"wp-block-list\"><li>type: Microsoft.Sql\/servers &#8211; this creates the SQL Server (not the database yet!)<\/li><li>name: rezasqlserverr9473 &#8211; must be globally unique across all Azure<\/li><li>administratorLogin: [parameters(&#8216;SQLLogin&#8217;)] &#8211; using parameter here!<\/li><li>administratorLoginPassword: [parameters(&#8216;SQLPassword&#8217;)] &#8211; secure parameter<\/li><\/ul><h3 class=\"wp-block-heading\">Why the Name Has Random Numbers?<\/h3><p class=\"wp-block-paragraph\">Azure SQL Server names must be unique across ALL of Azure, not just your subscription. When I tried rezasqlserver, Azure said &#8220;name already taken&#8221;. So I added 9473 at the end.<\/p><p class=\"wp-block-paragraph\">In my next articles, I will show you how to use uniqueString() function to generate unique names automatically.<\/p><h3 class=\"wp-block-heading\">Creating the Database<\/h3><pre class=\"wp-block-code\"><code>{\n    \"type\": \"Microsoft.Sql\/servers\/databases\",\n    \"apiVersion\": \"2022-02-01-preview\",\n    \"name\": \"[format('{0}\/{1}','rezasqlserverr9473','appdb')]\",\n    \"location\": \"[resourceGroup().location]\",\n    \"sku\":{\n        \"name\":\"Basic\",\n        \"tier\":\"Basic\"\n    },\n    \"properties\":{},\n    \"dependsOn\":[\n        \"[resourceId('Microsoft.Sql\/servers','rezasqlserverr9473')]\"\n    ]\n}<\/code><\/pre><h3 class=\"wp-block-heading\">The Nested Resource Name<\/h3><p class=\"wp-block-paragraph\">Look at this line carefully:<\/p><pre class=\"wp-block-code\"><code>\"name\": \"[format('{0}\/{1}','rezasqlserverr9473','appdb')]\"<\/code><\/pre><p class=\"wp-block-paragraph\">This uses the format() function. It creates the name: rezasqlserverr9473\/appdb<\/p><p class=\"wp-block-paragraph\">Why this format? The database is a child resource of the SQL Server. In ARM templates, child resources use the format: parentName\/childName<\/p><ul class=\"wp-block-list\"><li>{0} = first parameter = &#8216;rezasqlserverr9473&#8217; (server name)<\/li><li>{1} = second parameter = &#8216;appdb&#8217; (database name)<\/li><\/ul><p class=\"wp-block-paragraph\">When I first wrote this, I tried:<\/p><pre class=\"wp-block-code\"><code>\"name\": \"rezasqlserverr9473\/appdb\"  \/\/ This works but format() is better<\/code><\/pre><p class=\"wp-block-paragraph\">Both work, but format() is cleaner when you use variables or parameters.<\/p><h3 class=\"wp-block-heading\">SKU &#8211; Database Pricing Tier<\/h3><pre class=\"wp-block-code\"><code>\"sku\":{\n    \"name\":\"Basic\",\n    \"tier\":\"Basic\"\n}<\/code><\/pre><p class=\"wp-block-paragraph\">This is important for cost! Azure SQL has different tiers:<\/p><ul class=\"wp-block-list\"><li>Basic: $5\/month &#8211; good for learning and small apps<\/li><li>Standard: $15-300\/month &#8211; for production apps<\/li><li>Premium: $465+\/month &#8211; for high performance<\/li><\/ul><p class=\"wp-block-paragraph\">I use Basic tier because I&#8217;m learning and testing. It&#8217;s cheap but has limits:<\/p><ul class=\"wp-block-list\"><li>Max size: 2 GB<\/li><li>Limited performance<\/li><li>Good enough for my home lab!<\/li><\/ul><h3 class=\"wp-block-heading\">The Dependency Chain<\/h3><pre class=\"wp-block-code\"><code>\"dependsOn\":[\n    \"[resourceId('Microsoft.Sql\/servers','rezasqlserverr9473')]\"\n]<\/code><\/pre><p class=\"wp-block-paragraph\">This is critical! The database cannot exist without the server. The dependsOn tells Azure:<\/p><ol class=\"wp-block-list\"><li>Create SQL Server first<\/li><li>Wait until it&#8217;s ready<\/li><li>Then create the database<\/li><\/ol><p class=\"wp-block-paragraph\">When I forgot this line, deployment failed with error: &#8220;Server does not exist&#8221;<\/p><h3 class=\"wp-block-heading\">How I Deployed This Template<\/h3><p class=\"wp-block-paragraph\">I use Azure CLI with inline parameters:<\/p><pre class=\"wp-block-code\"><code># Create resource group\naz group create --name sql-test-grp --location \"North Europe\"\n\n# Deploy with parameters\naz deployment group create \\\n  --resource-group sql-test-grp \\\n  --template-file 02-AzureSQL.json \\\n  --parameters SQLLogin=sqlusr SQLPassword='Azure@123'<\/code><\/pre><p class=\"wp-block-paragraph\">Security note: This is only for my home lab testing! In real production, never put passwords in command line. Use parameter files or Azure Key Vault instead.<\/p><h3 class=\"wp-block-heading\">Using Parameter Files (Better Way)<\/h3><p class=\"wp-block-paragraph\">For production, I create a separate parameter file:<\/p><p class=\"wp-block-paragraph\">parameters.json:<\/p><pre class=\"wp-block-code\"><code>{\n  \"$schema\": \"https:\/\/schema.management.azure.com\/schemas\/2019-04-01\/deploymentParameters.json#\",\n  \"contentVersion\": \"1.0.0.0\",\n  \"parameters\": {\n    \"SQLLogin\": {\n      \"value\": \"sqlusr\"\n    },\n    \"SQLPassword\": {\n      \"value\": \"Azure@123\"\n    }\n  }\n}<\/code><\/pre><p class=\"wp-block-paragraph\">Then deploy:<\/p><pre class=\"wp-block-code\"><code>az deployment group create \\\n  --resource-group sql-test-grp \\\n  --template-file 02-AzureSQL.json \\\n  --parameters @parameters.json<\/code><\/pre><p class=\"wp-block-paragraph\">The @ symbol tells Azure CLI to read from file.<\/p><h3 class=\"wp-block-heading\">Connecting to My SQL Database<\/h3><p class=\"wp-block-paragraph\">After deployment, I can connect using:<\/p><ul class=\"wp-block-list\"><li>Server name: rezasqlserverr9473.database.windows.net<\/li><li>Database name: appdb<\/li><li>Username: sqlusr<\/li><li>Password: Azure@123<\/li><\/ul><p class=\"wp-block-paragraph\">I use Azure Data Studio or SQL Server Management Studio to connect and create tables.<\/p><h3 class=\"wp-block-heading\">What I Learned<\/h3><ol class=\"wp-block-list\"><li>Parameters make templates reusable &#8211; Same template, different values<\/li><li>secureString is mandatory for passwords &#8211; Security first!<\/li><li>Child resources use slash notation &#8211; parentName\/childName<\/li><li>format() function is useful &#8211; Better than hardcoding strings<\/li><li>dependsOn prevents errors &#8211; Always set dependencies correctly<\/li><li>Basic SKU is cheap &#8211; Perfect for learning ($5\/month)<\/li><li>Parameter files are more secure &#8211; Don&#8217;t put passwords in command line<\/li><\/ol><h3 class=\"wp-block-heading\">My Mistakes and Fixes<\/h3><p class=\"wp-block-paragraph\">Mistake 1: Used &#8220;type&#8221;:&#8221;string&#8221; for password<\/p><pre class=\"wp-block-code\"><code>\"SQLPassword\": {\n    \"type\":\"string\"  \/\/ \u274c Wrong! Password visible in logs\n}<\/code><\/pre><p class=\"wp-block-paragraph\">Fix: Changed to &#8220;type&#8221;:&#8221;secureString&#8221;<\/p><p class=\"wp-block-paragraph\">Mistake 2: Forgot dependsOn<\/p><pre class=\"wp-block-code\"><code>\/\/ No dependsOn array<\/code><\/pre><p class=\"wp-block-paragraph\">Error: &#8220;The specified server does not exist&#8221; Fix: Added dependsOn to database resource<\/p><p class=\"wp-block-paragraph\">Mistake 3: Name not unique globally<\/p><pre class=\"wp-block-code\"><code>\"name\": \"sqlserver\"  \/\/ \u274c Already taken by someone else<\/code><\/pre><p class=\"wp-block-paragraph\">Fix: Added random numbers: rezasqlserverr9473<\/p><p class=\"wp-block-paragraph\">Mistake 4: Wrong database name format<\/p><pre class=\"wp-block-code\"><code>\"name\": \"appdb\"  \/\/ \u274c Wrong for child resource<\/code><\/pre><p class=\"wp-block-paragraph\">Fix: Used format function: [format(&#8216;{0}\/{1}&#8217;,&#8217;rezasqlserverr9473&#8242;,&#8217;appdb&#8217;)]<\/p><h3 class=\"wp-block-heading\">Testing My Database<\/h3><p class=\"wp-block-paragraph\">After deployment, I connected and created a simple table:<\/p><pre class=\"wp-block-code\"><code>CREATE TABLE Products (\n    ProductID int,\n    ProductName varchar(1000),\n    Quantity int\n);\n\nINSERT INTO Products VALUES (1, 'Mobile', 100);\nINSERT INTO Products VALUES (2, 'Laptop', 200);<\/code><\/pre><p class=\"wp-block-paragraph\">It worked! Now my Web App can connect to this database.<\/p>","protected":false},"excerpt":{"rendered":"<p>My Second ARM Template Challenge Hello again! In my last article, I showed you how to deploy a Web App with ARM&hellip;<\/p>\n","protected":false},"author":2,"featured_media":100,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_rezaops_part":"2","_rezaops_linkedin_url":"https:\/\/www.linkedin.com\/pulse\/deploying-azure-sql-database-arm-templates-reza-chegini-ocb0c\/","footnotes":""},"categories":[50,10],"tags":[53,52,56,8,55],"series":[51],"class_list":["post-98","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-azure","category-devops","tag-arm-templates","tag-azure","tag-azure-sql","tag-devops","tag-iac","series-azure-arm-templates"],"jetpack_featured_media_url":"https:\/\/rezachegini.com\/wp-content\/uploads\/2026\/10\/deploying-azure-sql-database-arm-cover.png","_links":{"self":[{"href":"https:\/\/rezachegini.com\/index.php?rest_route=\/wp\/v2\/posts\/98","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/rezachegini.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/rezachegini.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/rezachegini.com\/index.php?rest_route=\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/rezachegini.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=98"}],"version-history":[{"count":1,"href":"https:\/\/rezachegini.com\/index.php?rest_route=\/wp\/v2\/posts\/98\/revisions"}],"predecessor-version":[{"id":99,"href":"https:\/\/rezachegini.com\/index.php?rest_route=\/wp\/v2\/posts\/98\/revisions\/99"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/rezachegini.com\/index.php?rest_route=\/wp\/v2\/media\/100"}],"wp:attachment":[{"href":"https:\/\/rezachegini.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=98"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/rezachegini.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=98"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/rezachegini.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=98"},{"taxonomy":"series","embeddable":true,"href":"https:\/\/rezachegini.com\/index.php?rest_route=%2Fwp%2Fv2%2Fseries&post=98"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}