This guide explains how to deploy an Nginx TCP proxy for MongoDB migrations on Azure by using Bicep templates. The proxy VM must be deployed into the spoke virtual network so that Azure Database Migration Service (DMS) worker nodes can reach a source MongoDB server that is accessible only through a private network (VPN or ExpressRoute).
For more information about private connectivity for MongoDB migrations to Azure DocumentDB, see Private connectivity.
The following diagram shows the hub-spoke network topology used by the TCP proxy. The proxy VM is deployed into a spoke virtual network that is peered with both the hub (which has connectivity to the source MongoDB server) and the DMS private virtual network. This placement allows DMS worker nodes to connect to the proxy, which forwards traffic to the source MongoDB server on-premises or in another cloud.
- Azure CLI - Install from Install the Azure CLI.
- PowerShell 7+ - Install from Install PowerShell.
- Azure subscription - An active Azure subscription.
- SSH client - For connecting to the VM (built-in on Windows 10 and later).
- Private network connectivity - VPN or ExpressRoute to the target Azure virtual network. The VM has no public IP address.
- Spoke virtual network - An existing spoke virtual network peered with both the hub virtual network and the DMS private virtual network. The proxy VM must be deployed into this spoke virtual network.
Edit azure-config.json with your Azure settings:
{
"subscriptionId": "YOUR_SUBSCRIPTION_ID",
"resourceGroup": "YOUR_RESOURCE_GROUP_NAME",
"location": "YOUR_AZURE_REGION",
"vmName": "YOUR_VM_NAME",
"vmSize": "YOUR_VM_SIZE",
"adminUsername": "YOUR_SSH_USERNAME",
"vmImage": {
"publisher": "Canonical",
"offer": "0001-com-ubuntu-server-jammy",
"sku": "22_04-lts-gen2",
"version": "latest"
},
"networking": {
"vnetName": "YOUR_VNET_NAME",
"subnetName": "YOUR_SUBNET_NAME",
"nsgName": "YOUR_NSG_NAME"
},
"proxyPorts": [YOUR_PROXY_PORT1", "YOUR_PROXY_PORT2", "YOUR_PROXY_PORT3]
}Note: The
vmImageandnetworkingsections are required by the deployment script. Do not omit them.
Field descriptions:
| Field | Description |
|---|---|
subscriptionId |
Your Azure subscription ID. Find it in the Azure portal under Subscriptions, or run az account show --query id -o tsv. |
resourceGroup |
Name of the existing resource group that contains your virtual network and subnet. The VM and other resources are deployed here. |
location |
Azure region to deploy into (see the list that follows). |
vmName |
Name for the Azure VM. Must be unique within the resource group. |
vmSize |
Azure VM SKU (see sizes that follow). |
adminUsername |
SSH login username for the VM. |
vmImage |
Ubuntu image to use. The defaults work as-is. Change them only if you need a different OS version. |
networking.vnetName |
Name of the existing spoke virtual network to deploy the proxy VM into. This must be the spoke virtual network that is peered with the hub and the DMS private virtual network. Must be in the same resource group as resourceGroup. Find it in the Azure portal under Virtual networks, or run az network vnet list -g YOUR_RG --query "[].name" -o tsv. |
networking.subnetName |
Name of the existing subnet within the spoke virtual network. Must be in the same resource group as resourceGroup. Find it under the virtual network's Subnets blade, or run az network vnet subnet list --vnet-name YOUR_VNET -g YOUR_RG --query "[].name" -o tsv. |
networking.nsgName |
Name for the network security group to create. Choose any descriptive name (for example, nsg-mongoproxy). |
proxyPorts |
Array of TCP ports to open on the VM. Must match the listen_port values in proxy-config.json. |
Tip: The spoke virtual network and subnet must already exist in the same resource group specified in
resourceGroup. The deployment creates only the NSG, NIC, and VM — it doesn't create networking infrastructure. If your spoke virtual network is in a different resource group, move it or update the Bicep template to use a cross-resource-group reference.
Azure VM sizes:
Standard_B2s- 2 vCPU, 4 GB RAM (minimal, ~$30/month)Standard_D8s_v3- 8 vCPU, 32 GB RAM (recommended, ~$280/month)Standard_D8as_v5- 8 vCPU, 32 GB RAM (recommended, AMD-based, ~$250/month)
Popular Azure regions:
eastus- East USwestus2- West US 2centralus- Central USwesteurope- West Europeeastasia- East Asia
Edit proxy-config.json to define your MongoDB proxy mappings:
[
{
"name": "YOUR_PROXY_NAME",
"listen_port": YOUR_LISTEN_PORT,
"target_host": "YOUR_TARGET_HOST",
"target_port": YOUR_TARGET_PORT,
"description": "YOUR_DESCRIPTION"
}
]Field descriptions:
| Field | Description |
|---|---|
name |
A unique identifier for this proxy entry. Used internally as the Nginx upstream name (for example, mongodb-primary). Must be unique across all entries. |
listen_port |
The TCP port the proxy VM listens on for incoming connections (for example, 8080). Must also appear in proxyPorts in azure-config.json. |
target_host |
IP address or hostname of the target MongoDB server to forward traffic to. |
target_port |
The port on the target MongoDB server (typically 27017). |
description |
Free-text description for documentation purposes. Not used by the proxy. |
Important: Every
listen_portinproxy-config.jsonmust have a corresponding entry in theproxyPortsarray inazure-config.json. TheproxyPortsarray controls which ports are opened in the Azure NSG firewall and exposed by Docker. If alisten_portis missing fromproxyPorts, the proxy entry will be unreachable from outside the VM.
Run the deployment script from the same directory as this guide (all scripts and config files are self-contained here):
.\deploy-azure.ps1The script performs the following steps:
- Validates your Azure sign-in.
- Validates that the resource group exists.
- Deploys the VM with Docker by using Bicep.
- Installs Docker and Docker Compose.
- Copies configuration files.
- Starts the Nginx proxy.
Deployment takes approximately 5 to 10 minutes.
.\deploy-azure.ps1.\deploy-azure.ps1 -DeployOnly.\deploy-azure.ps1 -ConfigureOnlyNote: All commands assume you are running from the same directory as this guide.
- Network Security Group (SSH and proxy ports)
- Network interface (attached to your existing spoke virtual network and subnet)
- Ubuntu 22.04 VM
- Docker and Docker Compose (installed through a VM extension)
Note: The VM is deployed into your existing resource group, spoke virtual network, and subnet with a private IP address only. No public IP address is created. You must have private connectivity (VPN or ExpressRoute) to the spoke virtual network to run the deployment script and manage the VM.
- Port 22 - SSH (accessible through the private network)
- Proxy ports as defined in
proxyPortsinazure-config.json(configurable)
- Ubuntu 22.04 LTS
- Docker Engine
- Docker Compose V2
- Nginx (containerized)
ssh <USERNAME>@<PROXY_PRIVATE_IP>ssh <USERNAME>@<PROXY_PRIVATE_IP>
cd mongoproxy && docker compose logs -f- Edit
proxy-config.jsonlocally - If you added or removed ports: Update
proxyPortsinazure-config.jsonto match, then redeploy the Bicep template (deploy-azure.ps1 -DeployOnly) so the NSG firewall rules are updated. - Regenerate nginx.conf:
.\generate-config.ps1 - Copy to VM:
scp nginx.conf <USERNAME>@<PROXY_PRIVATE_IP>:~/mongoproxy/
- Restart proxy:
ssh <USERNAME>@<PROXY_PRIVATE_IP> 'cd mongoproxy && docker compose restart'
Note: All files (
proxy-config.json,generate-config.ps1,nginx.conf) are in the current directory. Run all commands from here.
ssh <USERNAME>@<PROXY_PRIVATE_IP> 'cd mongoproxy && docker compose down'ssh <USERNAME>@<PROXY_PRIVATE_IP> 'cd mongoproxy && docker compose up -d'# Test connection through the proxy using the VM's private IP
# Use the credentials of the TARGET MongoDB server (not the proxy)
mongosh "mongodb://<MONGO_USER>:<MONGO_PASSWORD>@<PROXY_PRIVATE_IP>:<PROXY_PORT>/admin"ssh <USERNAME>@<PROXY_PRIVATE_IP>
# Check if containers are running
docker compose ps
# Test local proxy
curl -v telnet://localhost:<PROXY_PORT>Standard_D8s_v3 VM (recommended):
- VM: ~$280/month
- Storage (30 GB Premium SSD): ~$5/month
- Total: ~$285/month
Standard_B2s VM (minimal):
- VM: ~$30/month
- Storage (30 GB Premium SSD): ~$5/month
- Total: ~$35/month
To delete all Azure resources:
az group delete --name rg-mongoproxy --yes --no-waitssh <USERNAME>@<PROXY_PRIVATE_IP>
sudo systemctl status docker
sudo systemctl start dockerssh <USERNAME>@<PROXY_PRIVATE_IP>
cd mongoproxy
docker compose logs nginx- Check NSG rules in Azure Portal
- Verify proxy ports in azure-config.json
- Ensure target MongoDB is accessible from Azure
Check extension logs:
az vm extension show \
--resource-group rg-mongoproxy \
--vm-name vm-mongoproxy \
--name installDocker- Restrict SSH access - Update NSG to allow SSH only from your private network range
- Regular updates - Keep VM and Docker updated
Enable VM insights in Azure Portal for:
- CPU/Memory metrics
- Network traffic
- Disk I/O
View in real-time:
ssh <USERNAME>@<PROXY_PRIVATE_IP> 'cd mongoproxy && docker compose logs -f --tail 100'For issues:
- Check Azure deployment logs in Portal
- Review VM extension logs
- Check docker compose logs
- Verify NSG rules and connectivity
