Image created by the author using free icons.

Hands-On Azure Container Apps 101 — Deploying a scalable Go-Backend

Unlocking the Secrets of Secure Web App Architecture on Azure Cloud

David Minkovski
14 min readAug 8, 2023

--

Motivation

I am sure you have seen the world go crazy about Kubernetes and while this technology is amazing and has many benefits, the complexities of managing these clusters can be quite, how shall I put this…challenging.

Well lucky for us Microsoft has come up with some real magic —
Embrace the simplicity and power of Azure Container Apps and enjoy hassle-free web app deployment.
YES. Imagine effortlessly packaging your web applications into containers using a github action, pushing them to the Azure Container Registry and deploying them with just a few clicks. It’s like waving a magic wand and watching your apps come to life!

For Non-Techies in Short: Microsoft has come up with a magical service that helps developers to forget about infrastructure and rather focus on development itself. Say “Bye” to HR looking for certified Kubernetes Pros.

Wait but my app needs to SCALE!

Yes, the fun doesn’t stop. Azure Container Apps are built for scalability. As your app’s popularity skyrockets, you can seamlessly scale up or down to meet the demand, keeping your users happy and your costs in check. It’s that easy.

For Non-Techies in Short: Using this service you can have more machines serving your soon billion users with as much as a click. All Hassle-Free!

What about integration with other stuff?

Azure Container Apps play extremely well with other Azure services. They’re like the popular kid in school who gets along with everyone, I mean everyone! Connect them to Azure Functions or Logic Apps and relax as you create powerful, event-driven architectures.

For Non-Techies in Short: Your other azure services will play along nicely, so take “Have you heard about container apps?” to your Cloud Architect.

Come on — this can not be safe?!

So what about security? Fear not! With Azure’s robust security framework, including Azure Active Directory integration and role-based access control, your apps are protected from any troublemakers. You have your own team of superheroes guarding your applications day and night.
I am not going to mention monitoring, you know about Azure Monitor and it’s ability to detect and zap any issues before they become real problems already.

For Non-Techies in Short: Security is included. Come on- it’s Microsoft!

What are we building in this little series?

Introducing the Ultimate Todo App API: a web application that effortlessly handles your todos. With a frontend React App seamlessly connecting to the backend API, your todos can securely be saved in a database. Get ready for a whole new level of productivity, accompanied by unbeatable security and scalability. Experience the most secure and available Todo App out there!
Jokes aside…let’s get to it!

Preview UI of what we are building and Architecture

You might be wondering why my architecture is not displaying only Container Apps. Well that is because I really am pragmatic and I know you will want to see a viable and deployable solution, right? So I am taking into account security stuff. Like using Private Link instead of routing over the internet AND using Front Door that comes with SSL resolving, web application firewalls and other fancy stuff. Don’t thank me — use it!

Warning: This is a 100% Hands-On Tutorial — follow at your own risk!

Step By Step: Let’s Go with the Go-Backend!

Step 1. Prerequisites

You will be able to find all the code & resources using Git right here:
dminkovski/aca101todo: ACA 101 Todo App (github.com)
Please clone the public repository to your machine. We will need it later.

# Clone the folder to your current location
git clone https://github.com/dminkovski/aca101todo.git

In case you want to run things locally you will need the following installed:

Please install Docker to build the application images.

Before we Start: You need to have an active Azure Subscription.
Find out more about azure: Cloud Computing Services | Microsoft Azure

Have Azure CLI installed please— because Hacker feeling we want.
To interact with azure and the resource manager we need the: Azure CLI.
Already there? Let us login first and see that we get the details back on our machine in PowerShell to make sure we have our CLI working as expected:

# Login to Azure
az login

The response should look something like this, after you have successfully logged in via the magically appearing browser window:

Azure Login Success — With a lot of Black.

Step 2. Create the Resource Group — Let there be light.

Azure resource groups are a way to organize your azure resources.
This is pretty useful if you have multiple projects running and need to organize your resources accordingly. Not to mention rules and tagging.
But I also use it mostly to test out things - because it is super easy to delete everything within a single RG.

# Set Variables: Location & Ressource Group Name
$LOCATION = "westeurope"
$RESOURCE_GROUP = "ACA101-RG"

# Create a Resource Group within our Subscription
az group create -l $LOCATION -n $RESOURCE_GROUP

# See all Resource Groups to verify
az group list
Resource Group successfully created

Once this was successful you should be able to see the Resource Group in the Azure Portal as well. But we prefer the CLI — because we can.

Step 3. Virtual Network for our Container App — Sweet Home.

# Give the Virtual Network a name
$VN_BACKEND = "Backend-Virtual-Network"

# Create a Virtual Network for our Backend
az network vnet create --name $VN_BACKEND --resource-group $RESOURCE_GROUP --location $LOCATION --address-prefixes "10.3.0.0/16" --tags ENV=PROD APP=ACA101

Step 4. Our Container Registry — Images need a Home as well.

The Azure Container Registry is the place where we will store our container images. Our services can then pull the image we want and deploy it. So Let’s create one!

# Name for our registry
$REGISTRY_NAME = "registryaca101acr"

# Create the Container Registry
$ACR = az acr create --resource-group $RESOURCE_GROUP --name $REGISTRY_NAME --sku Basic

# This is the registry server address
$ACR_SERVER = "$REGISTRY_NAME.azurecr.io"

For the next step we need to have our docker daemon running (Docker Desktop is running), so we can log into our container registry.
Please install Docker if you have not done so already.

# Log into our Container Registry and see that "Login succeeded"
az acr login --name $REGISTRY_NAME

Step 5. Building Frontend & Backend Container Images — Too easy.

Navigate to the frontend folder of the project you cloned from git and build the docker image there.

PS: You will need this in case you want to deploy the frontend to an app service or static web app or similar. Otherwise skip right to the backend.

cd aca101todo\frontend\
docker build -t todo/frontend:latest .
Screenshot of Docker Build process

Then do the same for the backend api image, which we will deploy soon:

cd aca101todo\api\
docker build -t todo/api:latest .

We should have our container images in our local Docker repository.
It’s time to tag and push them to our created Azure Container Registry.

# Tag Docker images
docker tag todo/frontend:latest $ACR_SERVER/todo-frontend:latest
docker tag todo/api:latest $ACR_SERVER/todo-api:latest

# Push Docker images to Registry
docker push $ACR_SERVER/todo-frontend:latest
docker push $ACR_SERVER/todo-api:latest

# Verify and list your images in the Registry
az acr repository list --name $REGISTRY_NAME --output table

Result
-------------
todo-api
todo-frontend
Status Update of our Architecture so far

Step 6. Subnet Time —A private room our Container App

Let us create a subnet for our Azure Container Apps Environment:

# A fancy subnet name
$SUBNET_NAME = "Subnet-Backend"

# Create a Subnet within our Virtual Network
az network vnet subnet create --name $SUBNET_NAME --resource-group $RESOURCE_GROUP --vnet-name $VN_BACKEND --address-prefixes "10.3.2.0/23" --disable-private-link-service-network-policies true --disable-private-link-service-network-policies true

Enable the admin user for our registry and get the credentials of our Azure Container Registry so we can push our images to it.

# Create an Admin User for our registry
az acr update -n $REGISTRY_NAME --admin-enabled true

# Retrieve the ACR Credentials for our Admin User
$ACR_USERNAME = az acr credential show --name $REGISTRY_NAME --query username
$ACR_PASSWORD = az acr credential show --name $REGISTRY_NAME --query passwords[0].value

Step 7. Deploying our Container App — This is it!

Let’s finally create the Container Apps Environment in our Subnet and then the app in our container apps environment.

# Give the environment a name
$ENV_NAME = "ACAEnv"

# Get the ID from the subnet
$SUBNET_ID = az network vnet subnet show -g $RESOURCE_GROUP -n $SUBNET_NAME --vnet-name $VN_BACKEND --query id

# Create a Container Apps Environment
az containerapp env create --name $ENV_NAME --resource-group $RESOURCE_GROUP --infrastructure-subnet-resource-id $SUBNET_ID --internal-only true --location $LOCATION --tags ENV=PROD APP=ACA101

# Check the provisioningState and wait (> 5 minutes) until its not "Waiting" anymore and shows "Succeeded"
az containerapp env show --resource-group $RESOURCE_GROUP --name $ENV_NAME --query properties.provisioningState

# <!-- Environment provisioned and state equals "Succeeded" --!>

# Give the container app a name
$ACA_NAME = "aca-backend-api"

# Create the Container App within the Environment
$BACKEND_FQDN = az containerapp create -n $ACA_NAME -g $RESOURCE_GROUP --image "$ACR_SERVER/todo-api:latest" --environment $ENV_NAME --ingress internal --target-port 8080 --registry-server $ACR_SERVER --registry-username $ACR_USERNAME --registry-password $ACR_PASSWORD --min-replicas 1 --query properties.configuration.ingress.fqdn

# Remove "internal" from the FQDN Link
$BACKEND_FQDN = $BACKEND_FQDN.replace('.internal','')

Go to your Azure Container App in the Portal and click on Ingress, make sure “Limited to Vnet” is selected:

Ingress ACA Settings with “Limited to VNet”

Yeah! We got our Container App deployed and up and running. You can check out the logs in the portal, but yes, since it’s protected we can’t see anything. Yet. How easy was this? Right? I told you it’s amazing. The rest is security and other services now.

What we have so far.

Step 8. Azure Front Door — A door that is lockable.

Time to make our secure Backend accessible using Front Door PREMIUM.
In order to link to our Container Apps we will need a private Link to use the Microsoft Backbone from our Front Door. This makes sure the connection does not go over the public internet — fancy security stuff.

# Get the Ressource Group name of our managed aks cluster
$AKS_RG = az group list --tag 'APP=ACA101' --query "[1].name" --out tsv

# Take a look at the internal Load Balancer we have - he is not in our Resource Group but in the one created by ACA
az network lb list --resource-group $AKS_RG --query "[?tags.APP=='ACA101' && name=='kubernetes-internal'].{Name: name, FrontendIP: frontendIPConfigurations[0].id, PrivateIP: frontendIPConfigurations[0].privateIPAddress}"

# Getting the IP Config of our internal kubernetes load balancer
$LB_IP_CONF = az network lb list --resource-group $AKS_RG --query "[?tags.APP=='ACA101' && name=='kubernetes-internal'].frontendIPConfigurations[0].id" --out tsv

# Name of our Private Link
$PLS_NAME = "PLS101"

# Creating the Private Link Service
az network private-link-service create --resource-group $RESOURCE_GROUP --name $PLS_NAME --vnet-name $VN_BACKEND --subnet $SUBNET_NAME --location $LOCATION --lb-frontend-ip-configs $LB_IP_CONF --fqdns $BACKEND_FQDN --tags APP=ACA101 ENV=PROD

# Retrieving the ID of the PLS
$PLS_ID = az network private-link-service show --resource-group $RESOURCE_GROUP --name $PLS_NAME --query id --out tsv

Now let us create our Premium Front Door instance and link it to our previously created Private Link Service.

$PROFILE = "FrontdoorACA101"
$ENDPOINT_NAME = "apiEndpoint"
$HOSTNAME = $BACKEND_FQDN.replace('https://','')

# Create a Frontdoor instance
az afd profile create -g $RESOURCE_GROUP --profile-name $PROFILE --sku Premium_AzureFrontDoor --tags ENV=PROD APP=ACA101

# Create the Origin Group
az afd origin-group create -g $RESOURCE_GROUP --origin-group-name "api" --profile-name $PROFILE --probe-request-type GET --probe-protocol Http --probe-interval-in-seconds 120 --probe-path / --sample-size 4 --successful-samples-required 3 --additional-latency-in-milliseconds 50

# Add the origin to our origin group
az afd origin create -g $RESOURCE_GROUP --host-name $HOSTNAME --profile-name $PROFILE --origin-group-name api --origin-name api --origin-host-header $HOSTNAME --priority 1 --weight 1000 --enabled-state Enabled --http-port 80 --https-port 443 --enable-private-link true --private-link-resource $PLS_ID --private-link-location $LOCATION --private-link-request-message 'Please approve this request'

# Create an Endpoint in Frontdoor
az afd endpoint create --endpoint-name $ENDPOINT_NAME --profile-name $PROFILE --resource-group $RESOURCE_GROUP --enabled-state Enabled --tags APP=ACA101 ENV=PROD

# Link to Default Domain of Front Door
az afd route create --endpoint-name $ENDPOINT_NAME --forwarding-protocol MatchRequest --https-redirect Enabled --origin-group api --profile-name $PROFILE --resource-group $RESOURCE_GROUP --route-name apiRoute --supported-protocols Https --link-to-default-domain Enabled --enabled-state Enabled

If you have a custom domain you want to point to this api continue:

# The domain of your api endpoint
$API_HOST_NAME = "api.yourdomain.com"

# Create a custom domain
az afd custom-domain create --certificate-type ManagedCertificate --custom-domain-name apiDomain --host-name $API_HOST_NAME --minimum-tls-version TLS12 --profile-name $PROFILE --resource-group $RESOURCE_GROUP

# Get the Domain ID
$CUSTOM_DOMAIN_ID=az afd custom-domain list -g $RESOURCE_GROUP --profile-name $PROFILE --query [0].id --out tsv

# Create a Route for your custom domain
az afd route create --endpoint-name $ENDPOINT_NAME --forwarding-protocol MatchRequest --https-redirect Enabled --origin-group api --profile-name $PROFILE --resource-group $RESOURCE_GROUP --route-name apiRoute --supported-protocols Https --custom-domains $CUSTOM_DOMAIN_ID --enabled-state Enabled

Step 9. DNS Settings & Domain Validation —prooving It’s ours.

Validate the custom domain if you haven’t got one — GO TO STEP 10.

# Retrieve validation token for custom domain
az afd custom-domain list -g $RESOURCE_GROUP - profile-name $PROFILE - query [0].validationProperties.validationToken

Take a note of the validation token and go to your DNS provider and add the following TXT Record: _dnsauth.api.yourdomain.com with the value of the token you just saw.
If you’re on Godaddy (#no-ad) it could look like:

DNS Record on GoDaddy for Custom Domain Verification

Step 10. Validate the connection using Private Link — Double Check

Now go to the Portal and “validate” the Connection to your Private Link from Frontdoor. You can also use the CLI to do this:

# Get Private Endpoint Connection ID
$PE_CONN_ID = az network private-endpoint-connection list -g $RESOURCE_GROUP -n $PLS_NAME --type Microsoft.Network/privateLinkServices --query [0].id --out tsv

# Approve connection to Private Link from Frontdoor
az network private-endpoint-connection approve --id $PE_CONN_ID --description "Approved"
Do the Private Link Approval in Azure Portal if you see errors

And now we wait for at least 5 minutes for everything to get up & running and let the magic happen…good time to grab a coffee.

# Retrieve the URL for your Front Door
$FRONTDOOR_URL = az afd endpoint list -g $RESOURCE_GROUP --profile-name $PROFILE --query "[].hostName" --out tsv

# Add HTTPS Protocol
$FRONTDOOR_URL = "https://"+$FRONTDOOR_URL

# Make Get Request to the Frontdoor Instance
curl $FRONTDOOR_URL

Or you can open Front Door Resource in the Portal and copy the Endpoint Hostname from there. If you call your Front Door endpoint from the terminal you should see this:

JSON Response to Frontdoor Request

Our Backend is ALIVE! Yeah — this is amazing isn’t it? Okay yes, you don’t see much else. Especially because the API is not fully accessible yet since we have no database and we have no UI up & running, YET!
But this is a huge step for this Web App. Congrats on making it this far.

Step 11. Adding a database— Yes we can store the TODOS.

When it comes to storing data, I believe one needs to really think about the structure, relationships and future goals — that is why when it comes to a simple proof of concept or our amazing Todo App I decided to keep it simple. We will go with MongoDB — good enough for simple JSON Object storage. Although Microsoft has a great solution to offer, namely CosmosDB, I want to use an external service to show you how its done, if the DB is not on Azure. Even though Azure DB services are AMAZING.
It’s simple, why make it easy when it can be complicated?!

Atlas MongoDB is good enough as a free service to try things out. When it would come to production — I’d stick to CosmosDB though. Like seriously. PLEASE. Don’t come crying for help to me if you decide otherwise.

Without further ado create an account and setup your database.
This is what our architecture will look like now:

Backend Architecture with MongoDB Database

Go to MongoDB register for free and create a free cluster.
Then once you are at the overview click on your Database and Connect.
Choose MongoDB Compass (which you can also install to check out your DB) and note the connection string. We will need this for our Backend.

MongoDB Atlas Connection String

Then navigate to Network Access inside the MongoDB Portal and add “0.0.0.0” to the list. This will make the Database accessible by anyone.
Don’t worry we will change this later.

Let us add an Environment Variable containing our DB Connection String to our Container App.

# Store Connection String in Variable
$DB_CONNECTION = MONGODB_CONNECTION_STRING_YOU_COPIED

# Update Env Variables in our container app
az containerapp update -g $RESOURCE_GROUP -n $ACA_NAME --set-env-vars DATABASE_URI=$DB_CONNECTION

That is it. You can navigate to the portal to make sure your container app has the new variables and runs.

Step 12. Creating a NAT Gateway — making the DB access secure

We will need a NAT Gateway for security purposes because we want to Whitelist the IP of our Container App — so the database can be accessed only by our backend. Why? Because a container app does not come with a static IP…that is why we need the NAT in our network.

# NAT Gateway name
$NAT_NAME = "NATGW"

# Create the nat gateway
az network nat gateway create --name $NAT_NAME --resource-group $RESOURCE_GROUP --location $LOCATION

# Add/Update the Back Subnet with our Gateway
az network vnet subnet update --resource-group $RESOURCE_GROUP --vnet-name $VN_BACKEND --name $SUBNET_NAME --nat-gateway $NAT_NAME

# Create the Public IP for our Gateway
$NAT_ID = az network public-ip create --name "NATGWPIP" --resource-group $RESOURCE_GROUP --location $LOCATION --allocation-method Static --sku Standard --query "publicIp.id"

# Update the gateway with the Public IP
az network nat gateway update --resource-group $RESOURCE_GROUP --name $NAT_NAME --public-ip-addresses $NAT_ID

# Retrieve the public IP
$NAT_PIP = az network public-ip show --name "NATGWPIP" -g $RESOURCE_GROUP --query "ipAddress"

Now you can add the $NAT_PIP value to the Network Settings of MongoDB and remove the “0.0.0.0” rule. Congrats! Your Database access is only accessible from this IP — it’s safe now! We are DONE.

Trouble Shooting

  • Our Services aren’t available right now.
    Front Door does need some time to startup — so please wait for 15 minutes.
Services not available — Front Door Starting Up
  • Check the Log stream in your container app inside the portal.
    It needs to be running and show “Listening and serving HTTP on :8080”. If not create a new revision with the right image and scale it to at least 1.
# Container Apps Log
az containerapp logs show -n $ACA_NAME -g $RESOURCE_GROUP --follow --tail 30
  • Check Front Door Routes and Origins. Make sure the origin links the default Hostname of Front Door to the Container Apps Hostname without “.internal.”.

Summary

In this article I wanted to guide you with hands-on steps through creating the backend part of your amazing Todo App using Azure Container Apps and Azure Front Door. Also I hope that I could give you an overview of what container apps are and that they truly are a great alternative to Azure Kubernetes Services or other kubernetes infrastructure setups.

Curious about more?

My newsletter is a burst of tech inspiration, problem-solving hacks, and entrepreneurial spirit.
Subscribe for your weekly dose of innovation and mind-freeing insights:
https://davidthetechie.substack.com/

--

--