Skip to content
Learni
View all tutorials
Cloud & DevOps

How to Deploy Azure SQL Database in 2026

Lire en français

Introduction

Azure SQL Database is a fully managed PaaS service from Microsoft Azure, providing a highly scalable, secure, and serverless relational SQL Server database. Unlike an on-premises SQL Server instance, it automatically handles backups, patching, and high availability, so you can focus on your code.

Why use it in 2026? With the rise of AI and data-intensive apps, Azure SQL excels in auto-scaling (up to 100 TB), Hyperscale integration for OLTP/OLAP, and native TDE encryption. This intermediate tutorial guides you step by step: CLI deployment, firewall setup, table creation with indexes, optimized queries, and monitoring. By the end, you'll have a functional DB ready to connect to any app (Node.js, .NET, etc.).

Real value: Save 50-70% on costs vs. SQL on VMs thanks to serverless compute. Tested in the France Central region.

Prerequisites

  • Active Azure account with a paid subscription (free credits insufficient for SQL).
  • Azure CLI 2.65+ installed (az --version to check).
  • Basics of T-SQL and Azure administration (intermediate level).
  • SQL tool like Azure Data Studio or sqlcmd to test queries.
  • Environment variable AZURE_SUBSCRIPTION_ID set (az account show --query id -o tsv).

Create the resource group

01-create-resource-group.sh
#!/bin/bash

RESOURCE_GROUP="rg-sql-tutorial-2026"
LOCATION="francecentral"

az group create \
  --name $RESOURCE_GROUP \
  --location $LOCATION \
  --tags project=tutorial environment=dev

# Verify the creation
echo "Resource group créé : $(az group show --name $RESOURCE_GROUP --query 'name' -o tsv)"

# Variables for later steps
export RESOURCE_GROUP=$RESOURCE_GROUP
export LOCATION=$LOCATION

This script creates a dedicated resource group, isolated for your tests. Use francecentral for EU data sovereignty. Tags make cost tracking easier via Azure Cost Management. Pitfall: Don't forget to export the vars for reuse.

Deploy the logical SQL server

A logical SQL server is the container for your databases. It handles authentication and firewall rules. Choose a strong admin password and enable Microsoft Entra ID auth for MFA.

Create the SQL server

02-create-sql-server.sh
#!/bin/bash

SQL_SERVER_NAME="sqlserver-tutorial-$(date +%s)"
ADMIN_LOGIN="sqladmin2026"
ADMIN_PASSWORD="MonP@ssw0rdTr3sS3cur1s2026!"

az sql server create \
  --name $SQL_SERVER_NAME \
  --resource-group $RESOURCE_GROUP \
  --location $LOCATION \
  --admin-user $ADMIN_LOGIN \
  --admin-password $ADMIN_PASSWORD \
  --enable-public-network true \
  --minimal-tls-version 1.2 \
  --version 16.0

# Enable Microsoft Entra admin (optional, requires Entra ID)
# az sql server ad-admin create --server-name $SQL_SERVER_NAME --resource-group $RESOURCE_GROUP --display-name "azuread-admin" --object-id "your-object-id"

export SQL_SERVER_NAME

# Full server name for connection
echo "Serveur SQL : ${SQL_SERVER_NAME}.database.windows.net"

Creates a SQL Server v16 (2022) with minimum TLS 1.2 for security. The name must be globally unique (timestamp added). Major pitfall: Password must be 8+ chars with uppercase/lowercase/digit/symbol. Export the var for later steps.

Create the serverless database

03-create-database.sh
#!/bin/bash

DB_NAME="sqldb-tutorial"

az sql db create \
  --resource-group $RESOURCE_GROUP \
  --server $SQL_SERVER_NAME \
  --name $DB_NAME \
  --service-objective S0 \
  --compute-model Serverless \
  --min-capacity 0.5 \
  --max-size 32GB \
  --sample-name AdventureWorksLT \
  --edition GeneralPurpose

export DB_NAME

# Verify
az sql db show --resource-group $RESOURCE_GROUP --server $SQL_SERVER_NAME --name $DB_NAME --query '{name:name, status:status}' -o table

Deploys a GeneralPurpose serverless DB (auto-pauses at 0.5 vCore). AdventureWorksLT populates it with sample data for immediate testing. Scaling: min 0.5/max 4 vCore auto. Pitfall: Serverless pauses after 1 hour of inactivity, stopping billing.

Configure firewall and security

Access is blocked by default. Open it for your IP or use Managed Identity for apps. Enable Auditing for compliance.

Add firewall rule

04-firewall-rule.sh
#!/bin/bash

# Rule for your current IP (found via curl ifconfig.me)
CLIENT_IP=$(curl -s ifconfig.me)

az sql server firewall-rule create \
  --resource-group $RESOURCE_GROUP \
  --server $SQL_SERVER_NAME \
  --name AllowClientIP \
  --start-ip-address $CLIENT_IP \
  --end-ip-address $CLIENT_IP

# Rule for Azure services (e.g., App Service)
az sql server firewall-rule create \
  --resource-group $RESOURCE_GROUP \
  --server $SQL_SERVER_NAME \
  --name AllowAzureServices \
  --start-ip-address 0.0.0.0 \
  --end-ip-address 0.0.0.0

# Enable auditing
az sql server update \
  --resource-group $RESOURCE_GROUP \
  --name $SQL_SERVER_NAME \
  --set securityPolicy.state=Enabled securityPolicy.auditingPolicy.auditActionsAndGroups='SCHEMA_OBJECT_CHANGE_GROUP'

Opens access for client IP and all Azure services. Security: Always use precise rules; delete after testing (az sql server firewall-rule delete). Auditing logs object changes to Log Analytics.

Create table, index, and data

05-create-table.sql
-- Connect via sqlcmd or Azure Data Studio:
-- sqlcmd -S ${SQL_SERVER_NAME}.database.windows.net -d ${DB_NAME} -U ${ADMIN_LOGIN} -P ${ADMIN_PASSWORD} -G

USE [sqldb-tutorial];

-- Create Users table with clustered index
CREATE TABLE Users (
    Id UNIQUEIDENTIFIER PRIMARY KEY DEFAULT NEWID(),
    Email NVARCHAR(255) NOT NULL UNIQUE,
    Name NVARCHAR(100) NOT NULL,
    CreatedAt DATETIME2 DEFAULT SYSDATETIMEOFFSET(),
    IsActive BIT DEFAULT 1
);

-- Non-clustered index for Email queries
CREATE NONCLUSTERED INDEX IX_Users_Email ON Users(Email) INCLUDE (Name);

-- Insert test data
INSERT INTO Users (Email, Name) VALUES
    ('user1@example.com', 'Alice Johnson'),
    ('user2@example.com', 'Bob Smith'),
    ('user3@example.com', 'Charlie Wilson');

-- Optimized query
SELECT Id, Email, Name FROM Users WHERE Email = 'user1@example.com' AND IsActive = 1;

-- Check count
SELECT COUNT(*) AS TotalUsers FROM Users;

Complete script: table with GUID PK (better than INT for distributed systems), index for query performance. Analogy: Clustered index = physical page order (like a sorted book). Test with sqlcmd to validate. Pitfall: Don't forget USE DB.

Scaling and monitoring

06-scale-monitor.sh
#!/bin/bash

# Scale to Serverless 2 vCore max
az sql db update \
  --resource-group $RESOURCE_GROUP \
  --server $SQL_SERVER_NAME \
  --name $DB_NAME \
  --service-objective GP_Serveless \
  --max-size 100GB

# Enable auto-scaling (serverless handles it automatically)

# Monitoring: Query performance via DMV (run in SQL)
# SELECT * FROM sys.dm_db_resource_stats WHERE end_time > DATEADD(minute, -15, SYSDATETIME());

# CPU >80% alerts
az monitor metrics alert create \
  --name HighCPUAlert \
  --resource-group $RESOURCE_GROUP \
  --scopes "/subscriptions/$AZURE_SUBSCRIPTION_ID/resourceGroups/$RESOURCE_GROUP/providers/Microsoft.Sql/servers/$SQL_SERVER_NAME/databases/$DB_NAME" \
  --condition "avg Percentage_CPU > 80" \
  --description "High CPU alert on tutorial DB"

# Cleanup (run at the end)
# az group delete --name $RESOURCE_GROUP --yes --no-wait

Dynamic scaling for traffic spikes. DMV sys.dm_db_resource_stats = free performance dashboard. Alerts via email/Action Group. Pitfall: Subscription ID required for cross-RG monitoring.

Best practices

  • Serverless first: Ideal for variable workloads; auto-pause saves 60% on costs.
  • Use Microsoft Entra ID for passwordless auth (Managed Identities).
  • Indexes + Query Store: Enable (ALTER DATABASE SET QUERY_STORE = ON) for auto-tuning.
  • Private Endpoint: For production, avoid public internet via VNet.
  • Geo-replication: az sql db create --create-mode Replica for disaster recovery.

Common errors to avoid

  • Firewall blocked: Always add client IP or AllowAzureServices before connecting.
  • Non-unique server name: Add timestamp or UUID.
  • Hidden costs: Serverless pause is fine, but backups (7-day retention) are billed; monitor via Cost Analysis.
  • Outdated TLS: Force 1.2+; otherwise, modern clients will refuse connections.

Next steps