In modern cloud-native engineering, building an application locally is only the first step. To run reliably, an application must be packaged, versioned, stored securely, and prepared for scalable execution in the cloud.
This article is the continuation of my previous article, "From GitHub to Container: Running a Node.js App (Wonderkid App) Locally and Packaging It with Docker." Here we take the container image we built and move it to the cloud: tagging it, storing it in a private Azure Container Registry (ACR), and running it on Azure Container Apps (ACA). The first two sections revise the key concepts from the previous article before we move on to the Azure workflow.
Prerequisites: Docker Desktop (running), the Azure CLI, an active Azure subscription, and the Docker image from Part 1.
1. Docker Image Creation and Tagging
Before an application can move to the cloud, it must be packaged as a container image using a Dockerfile.
Building an Image
A Docker image is built in layers, one for each instruction in the Dockerfile (such as FROM, WORKDIR, COPY, RUN, EXPOSE, and CMD). To build an image locally, run:
docker build -t <image-name>:<version> .
-
-tflag: assigns a name and version tag to the image. -
.(dot): sets the build context to the current directory, so Docker uses the files there.
Apple Silicon (M1/M2/M3) users: Azure Container Apps runs Linux AMD64 containers. If you build on an ARM-based Mac, add the platform flag so the image runs in Azure:
docker build --platform linux/amd64 -t <image-name>:<version> .
Image Tagging Strategies
Tags let you track and manage each iteration of your application.
-
Use explicit versions. Tags such as
1.0.0or2.0.0are better than relying onlatest. Becauselatestis just a default tag that gets reassigned, it hides which revision is actually running and makes rollbacks unpredictable. -
Tags can point to a registry. The
docker tagcommand doesn't rename an image. It adds an additional reference to the same image. When that reference includes a registry address (for examplemyregistry.azurecr.io/wonderkids:1.0.0), Docker knows where to push the image.
2. Public vs. Private Container Registries
Once built, images need a central place to be stored, a container registry, so they can be pulled by any environment that needs to run them.
Public Registries (e.g., Docker Hub)
- Overview: Docker Hub is the largest public image library, and the default registry for Docker.
- Best for: open-source projects, public distribution, and standard base images (Node.js, Alpine, Ubuntu).
- Limitation: Anything pushed to a public repository is visible to everyone, including your application code, configuration baked into the image, and any secrets accidentally included. Docker Hub does support private repositories, but they come with plan limits and sit outside your Azure security boundary.
Private Registries (e.g., Azure Container Registry)
- Overview: Azure Container Registry (ACR) is a managed, private registry hosted in Azure.
- Best for: enterprise workloads. It offers role-based access control (RBAC), network isolation options, and integration with Microsoft Entra ID and other Azure services.
3. Deployment Walkthrough: Pushing an Image to Azure Container Registry
The following steps push your local Docker image to ACR using the Azure CLI.
Step 1: Sign in to Azure
az login
This opens a browser window (or uses a device code) to authenticate your Azure CLI session. If you have more than one subscription, select the one you want:
az account set --subscription "<subscription-name-or-id>"
Step 2: Create a resource group
A resource group is a logical container for related Azure resources.
az group create --name <resource-group-name> --location <location>
-
Example locations:
eastus,northeurope,australiaeast. - Best practice: keeping everything for one project in a single resource group makes cleanup and cost tracking much simpler. Deleting the group removes everything inside it.
Step 3: Create the private registry
az acr create --resource-group <resource-group-name> --name <acr-name> --sku Basic --admin-enabled false
- Registry names must be globally unique, 5 to 50 characters, lowercase letters and numbers only (no hyphens).
-
--sku Basic: the lowest-cost tier, suitable for learning and small projects. -
--admin-enabled false: disables the shared admin account (this is already the default). Access is controlled through individual identities and Azure RBAC rather than a shared username and password.
Step 4: Authenticate Docker to the registry
az acr login --name <acr-name>
This uses your Azure CLI session to give your local Docker engine a short-lived token for the registry. Docker Desktop must be running. Your account needs permission to push images, such as the AcrPush role, or Owner or Contributor on the registry. If you created the registry yourself, you already have access.
Step 5: Tag the image for the registry
docker tag <local-image-name>:<version> <acr-name>.azurecr.io/<repository-name>:<version>
Step 6: Push the image
docker push <acr-name>.azurecr.io/<repository-name>:<version>
Docker uploads the image layer by layer. Layers that already exist in the registry are skipped, which makes later pushes faster.
Step 7: Verify the upload
az acr repository list --name <acr-name> --output table
az acr repository show-tags --name <acr-name> --repository <repository-name> --output table
The first command confirms the repository exists, and the second confirms your version tag is there.
4. Scalable Execution: Azure Container Apps (ACA)
With the image stored in ACR, you can run it on Azure Container Apps, a managed serverless platform for containers.
- Serverless execution: ACA is built on top of Kubernetes, but Azure manages the cluster for you.
- Less complexity: there are no control planes, nodes, or ingress controllers to manage, and no Kubernetes YAML required to get started.
-
Key benefits:
- Built-in autoscaling: scales on HTTP traffic, events, or CPU and memory load.
- Microservice support: internal service discovery, traffic splitting for canary releases, and managed HTTPS ingress.
- Cost efficiency: apps can scale to zero replicas when idle, so you pay only while they run. This is configurable, since setting a minimum replica count keeps the app always warm.
Deploying the image to Azure Container Apps
1. Install the Container Apps extension and register the providers (one-time setup):
az extension add --name containerapp --upgrade
az provider register --namespace Microsoft.App
az provider register --namespace Microsoft.OperationalInsights
2. Create a Container Apps environment, the secure boundary that hosts your apps:
az containerapp env create --name <environment-name> --resource-group <resource-group-name> --location <location>
3. Create the container app from your ACR image:
az containerapp create \
--name <app-name> \
--resource-group <resource-group-name> \
--environment <environment-name> \
--image <acr-name>.azurecr.io/<repository-name>:<version> \
--target-port 3000 \
--ingress external \
--registry-server <acr-name>.azurecr.io \
--registry-identity system
-
--target-port: must match the port your app listens on inside the container (theEXPOSEd port, e.g., 3000). -
--ingress external: makes the app reachable from the internet over HTTPS. -
--registry-identity system: gives the app a managed identity to pull from ACR, so no passwords are needed. The CLI assigns the AcrPull role for you, provided your account is allowed to create role assignments.
4. Get the app URL:
az containerapp show --name <app-name> --resource-group <resource-group-name> \
--query properties.configuration.ingress.fqdn --output tsv
Open the returned address in your browser (prefixed with https://) to see the app running in Azure.
5. Clean up when you're done to avoid unnecessary charges:
az group delete --name <resource-group-name> --yes --no-wait
Summary
Containerizing an application bridges local development and cloud infrastructure. By building versioned images, storing them in a private registry like Azure Container Registry, and running them on a serverless platform like Azure Container Apps, teams can deliver applications that are reliable, secure, and scalable.





Top comments (0)