Skip to content

Instantly share code, notes, and snippets.

@suryakencana007
Created April 24, 2025 14:34
Show Gist options
  • Save suryakencana007/79f1ef05be82450a56219f57cd3f917f to your computer and use it in GitHub Desktop.
Save suryakencana007/79f1ef05be82450a56219f57cd3f917f to your computer and use it in GitHub Desktop.
Azure Terraform — Deploy a secure application: container apps, postgreSQL, and Redis

source: https://medium.com/@dinh.nt

Recently I took a task to deploy a full-fledged application securely on a virtual network in Azure cloud. I have to use IAC to reduce maintenance time later on.

This is not a easy task for me, since my knowledge of virtual network was limited. But luckily, I managed to get it done in a week, which is quite fast for what I had estimated.

I will share with you everything I did in this post.

Application Architecture

The application will be securely contained within a virtual network and only allow traffic from internet to the container app. By that way, we minimize attack surface and secure all internal resources, while allow them to communicate over private connections

The application consists of the following components:

  • Azure Container App: Connects to other services and serves as the entry point for internet traffic.
  • Azure Redis Cache: Accessed via a private endpoint.
  • Azure PostgreSQL Flexible Server: Secured within a dedicated subnet.

Each service resides in its own subnet, connected through a private DNS zone for seamless internal communication.

Our redis cache will be connected through a private endpoint, why ? Actually we can setup redis to sit within a virtual network directly, but that requires Redis Premium Tier which has a minimum size of 6GB and cost ~ $400/m, or more — which is very expensive for a small to medium application.

So we should go with Basic or Standard tier, and the only way to isolate it inside virtual network is through a private endpoint.

All services will be connected using a url, managed by the private DNS zone.

Application Architecture in virtual network

Prerequisite

  • Basic Terraform and IAC knowledge
  • Azure subscription, an available resource group and Azure cli
  • Terraform local setup

Setting Up Virtual Network

Everything is bounded by a virtual network so we will start with that

data "azurerm_resource_group" "app_rg" {
  name = var.resource_group_name
}
resource "azurerm_virtual_network" "app_vnet" {
  name                = "app-virtual-network"
  location            = data.azurerm_resource_group.app_rg.location
  resource_group_name = data.azurerm_resource_group.app_rg.name
  address_space       = ["10.0.0.0/16"]
  tags                = local.common_tags
}

Deploying PostgreSQL in a Virtual Network

In order to have PostgreSQL in a virtual network, we need the subnet dedicated for postgreSQL DB resources, a private DNS zone for routing and the DB itself. Notice that this is the DB server, we need to define which database we want to create in the server, too.

Database

resource "azurerm_subnet" "postgresql_subnet" {
  name                 = "postgresql-subnet"
  resource_group_name  = data.azurerm_resource_group.app_rg.name
  virtual_network_name = azurerm_virtual_network.app_vnet.name
  address_prefixes     = ["10.0.2.0/24"]
  service_endpoints    = ["Microsoft.Storage"]
  delegation {
    name = "fs"
    service_delegation {
      name = "Microsoft.DBforPostgreSQL/flexibleServers"
      actions = [
        "Microsoft.Network/virtualNetworks/subnets/join/action",
      ]
    }
  }
}

resource "azurerm_private_dns_zone" "postgres_zone" {
  name                = "private.postgres.database.azure.com"
  resource_group_name = data.azurerm_resource_group.app_rg.name
}
resource "azurerm_private_dns_zone_virtual_network_link" "postgres_vn_link" {
  name                  = "postgresVnetZone.com"
  private_dns_zone_name = azurerm_private_dns_zone.postgres_zone.name
  virtual_network_id    = azurerm_virtual_network.app_vnet.id
  resource_group_name   = data.azurerm_resource_group.app_rg.name
}
resource "azurerm_postgresql_flexible_server" "postgres_server" {
  name                          = "app-db-posgresql"
  resource_group_name           = data.azurerm_resource_group.app_rg.name
  location                      = data.azurerm_resource_group.app_rg.location
  version                       = "16"
  delegated_subnet_id           = azurerm_subnet.postgresql_subnet.id
  private_dns_zone_id           = azurerm_private_dns_zone.postgres_zone.id
  public_network_access_enabled = false
  administrator_login           = var.db_admin_username
  administrator_password        = var.db_admin_password
  zone                          = "1"
  storage_mb   = var.app_config.postgresql.storage_mb
  storage_tier = var.app_config.postgresql.storage_tier
  sku_name     = var.app_config.postgresql.sku_name
  lifecycle {
    prevent_destroy = true
  }
}
// we will create main-database in the server
resource "azurerm_postgresql_flexible_server_database" "main_database" {
  name      = "main-database"
  server_id = azurerm_postgresql_flexible_server.postgres_server.id
  collation = "en_US.utf8"
  charset   = "utf8"
  lifecycle {
    prevent_destroy = true
  }
}

Setting Up Redis with private endpoint

The next thing will be the Azure Redis Cache with the a private endpoint and a DNS zone

Database and Redis

resource "azurerm_redis_cache" "redis" {
  name                          = "app-redis-cache"
  location                      = data.azurerm_resource_group.app_rg.location
  resource_group_name           = data.azurerm_resource_group.app_rg.name
  capacity                      = var.app_config.redis.capacity
  family                        = var.app_config.redis.family
  sku_name                      = var.app_config.redis.sku_name
  public_network_access_enabled = false
  non_ssl_port_enabled          = false
  minimum_tls_version           = "1.2"
  redis_version                 = "6"
}
resource "azurerm_private_endpoint" "redis_private_endpoint" {
  name                = "redis-endpoint"
  location            = data.azurerm_resource_group.app_rg.location
  resource_group_name = data.azurerm_resource_group.app_rg.name
  subnet_id           = azurerm_subnet.redis_cache_subnet.id
  custom_network_interface_name = "private-redis-nic"
  private_dns_zone_group {
    name                 = "private.redis.cache.windows.net"
    private_dns_zone_ids = [azurerm_private_dns_zone.private_redis_zone.id]
  }
  private_service_connection {
    name                           = "redis-private"
    subresource_names              = ["redisCache"]
    private_connection_resource_id = azurerm_redis_cache.redis.id
    is_manual_connection           = false
  }
}
resource "azurerm_private_dns_zone" "private_redis_zone" {
  name                = "redis.cache.windows.net" // must be redis.cache.windows.net
  resource_group_name = data.azurerm_resource_group.app_rg.name
}
resource "azurerm_private_dns_zone_virtual_network_link" "private_redis_vn_link" {
  name                  = "redisVnetZone.com"
  private_dns_zone_name = azurerm_private_dns_zone.private_redis_zone.name
  virtual_network_id    = azurerm_virtual_network.app_vnet.id
  resource_group_name   = data.azurerm_resource_group.app_rg.name
  registration_enabled  = false
}

Deploying Azure Container Apps

The container app will need a registry storage, but you can choose other registry service as well. The container app is only gate accept connection from internet.

The beauty of container app is that it will handle scaling automatically based on the traffic, custom domain, and zero downtime between deployment.

Container App

resource "azurerm_container_app_environment" "container_app_environment" {
  name                     = "app-container-environment"
  location                 = data.azurerm_resource_group.app_rg.location
  resource_group_name      = data.azurerm_resource_group.app_rg.name
  infrastructure_subnet_id = azurerm_subnet.container_apps.id
  workload_profile {
    name                  = "Consumption"
    workload_profile_type = "Consumption"
  }
  log_analytics_workspace_id = azurerm_log_analytics_workspace.log_analytics.id
}

resource "azurerm_subnet" "container_apps" {
  name                 = "containerapps-subnet"
  resource_group_name  = data.azurerm_resource_group.app_rg.name
  virtual_network_name = azurerm_virtual_network.app_vnet.name
  address_prefixes     = ["10.0.0.0/23"]

  delegation {
    name = "Microsoft.App.environments"
    service_delegation {
      name = "Microsoft.App/environments"
      actions = [
        "Microsoft.Network/virtualNetworks/subnets/join/action",
      ]
    }
  }
}

resource "azurerm_container_app" "container_app" {
  name                         = "container-app"
  container_app_environment_id = azurerm_container_app_environment.container_app_environment.id
  resource_group_name          = data.azurerm_resource_group.app_rg.name
  revision_mode                = "Single"
  template {
    container {
      name   = "container-app"
      image  = "...container-app:latest" // your image url
      cpu    = "0.5"
      memory = "1Gi"
    }
  }
}

The last piece: Connecting the Services

locals {
  databaseUrl = "postgres://${azurerm_postgresql_flexible_server.postgres_server.administrator_login}:${azurerm_postgresql_flexible_server.postgres_server.administrator_password}@${azurerm_postgresql_flexible_server.postgres_server.fqdn}:5432/${azurerm_postgresql_flexible_server_database.main_database.name}?sslmode=require"
  redisUrl    = "rediss://:${azurerm_redis_cache.redis.primary_access_key}@${azurerm_redis_cache.redis.hostname}:${azurerm_redis_cache.redis.ssl_port}"
}

resource "azurerm_container_app" "container_app" {
  template {
    container {
      env {
        name  = "DATABASE_URL"
        value = local.databaseUrl
      }
      env {
        name  = "REDIS_URL"
        value = local.redisUrl
      }
    }
  }
}

VNET Container App

And your container app can start access to Redis and the db.

Hope you guys find this usefull. If you have questions or improvements, feel free to share them!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment