This is a deliberately public, temporary demonstration—not a production deployment. Azure Container Instances (ACI) runs containers without requiring an administrator to manage a virtual machine or a Kubernetes cluster. Terraform can declare the resource group, container group, public endpoint, image and compute settings. The result is straightforward to repeat for a lab, but a public IP makes the exposed service reachable from the internet. Use only a harmless sample image, no real accounts or sensitive data, and a subscription in which you are authorized to create and remove resources.
Microsoft’s Azure Container Instances Terraform quickstart and the current HashiCorp AzureRM provider reference describe the azurerm_container_group resource with ip_address_type = "Public". The capital P matters: do not reuse an old guide’s lowercase value without checking the current schema. For how the managed container model differs from orchestrated deployments, the Azure Container Instances overview supplies useful background. This guide instead focuses on the actual infrastructure code, validation, networking evidence and controlled cleanup.
Choose a suitable public-IP workload
ACI is useful for short-lived services, demonstrations and containerized jobs when a full orchestration platform would add unnecessary operations. A container group hosts one or more containers with shared scheduling and networking. A public IP is assigned to the group, not to a particular container process. The container’s listening ports and the group’s exposed ports must agree. If a container is recreated, its assigned IP can change, so do not hardcode an address from a previous lab into permanent external configuration.
The public-IP choice is intentional here because the article demonstrates a simple web endpoint. It is not a way to bypass private networking, authentication or corporate security policy. The sample exposes HTTP on TCP port 80 and should serve only harmless, public demonstration content. Do not place credentials, customer documents, management interfaces or internal databases behind it. For business applications, consider private ACI networking or an appropriately authenticated TLS-enabled entry architecture. That requires additional review beyond this temporary exercise.
Before starting, check that your authorized Azure subscription supports the desired region and container capacity. The group may consume billable compute while it runs, and related services can generate charges. Use a dedicated training resource group, identify the owner and plan the teardown before creating anything. Avoid shared production groups even if your account can access them; isolating the lab keeps the Terraform plan and cleanup understandable.
Authenticate and prepare Terraform
Install supported versions of Terraform and the Azure CLI. Sign in interactively with az login, inspect available subscriptions using az account list, then select the correct one with az account set --subscription <subscription-id>. Terraform AzureRM version 4.0 and later require the subscription ID to be supplied explicitly through provider configuration or the ARM_SUBSCRIPTION_ID environment variable. Signing in with the Azure CLI alone does not waive that provider requirement.
Create a new empty directory and a main.tf file there. The example below reads the subscription ID from a Terraform input variable. In Bash you can set export TF_VAR_subscription_id="$(az account show --query id -o tsv)" to supply it without inserting your account details into the code. Adapt environment-variable syntax for PowerShell or another shell as necessary. Do not publish credentials, state files or saved plans. Automated production workflows should use approved managed or federated workload identities instead of a personal interactive login.
Terraform configuration describes the desired infrastructure while a plan previews changes before they are applied. Review Terraform configuration, state and planning if this separation is unfamiliar. A local state file can contain sensitive infrastructure information, and it must remain protected. In a managed environment, remote state, controlled access, policy checks and change approvals are important; they are intentionally outside the simple local lab.
Create the resource group and public container
Save the following HCL as main.tf. It uses an AzureRM 5.x provider constraint, an explicit subscription variable, a Linux container group, a public IP, the Microsoft hello-world image, and matching container and group TCP port 80 settings. The lab names are illustrative and may need to change to avoid collisions. The configuration is a source-reviewed example; it has not been executed against your particular Azure subscription.
terraform {
required_version = ">= 1.5.0"
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = "~> 5.0"
}
}
}
variable "subscription_id" {
type = string
}
variable "location" {
type = string
default = "eastus"
}
provider "azurerm" {
features {}
subscription_id = var.subscription_id
}
resource "azurerm_resource_group" "lab" {
name = "rg-aci-public-demo"
location = var.location
}
resource "azurerm_container_group" "lab" {
name = "aci-public-demo"
location = azurerm_resource_group.lab.location
resource_group_name = azurerm_resource_group.lab.name
os_type = "Linux"
ip_address_type = "Public"
restart_policy = "Always"
container {
name = "hello"
image = "mcr.microsoft.com/azuredocs/aci-helloworld:latest"
cpu = 0.5
memory = 1.5
ports {
port = 80
protocol = "TCP"
}
}
exposed_port {
port = 80
protocol = "TCP"
}
tags = {
purpose = "temporary-training-only"
}
}
output "container_public_ip" {
value = azurerm_container_group.lab.ip_address
}
The azurerm_resource_group is a dedicated boundary for cleanup. azurerm_container_group declares the instance, while its nested container block supplies the sample image, CPU, memory and listening port. The explicit exposed_port matches the declared container port; HashiCorp requires group-level exposed ports to correspond to ports of containers in that group. The output reads the assigned public IP after Azure creates it. It does not guarantee that the application is healthy or that the address will survive deletion and recreation.
Using the image tag :latest is convenient for a disposable tutorial but does not guarantee reproducibility for production. A controlled deployment should use a reviewed immutable digest, a trusted registry and appropriate image scanning. Private registries can require managed identity or dedicated credentials, which would complicate this example. Never embed registry passwords or application secrets directly into source code or commit them to a public repository.
Plan and apply a reviewed deployment
In the project directory, run terraform fmt -check, terraform init and terraform validate. Initialization installs the required provider; validation checks Terraform configuration structure, but it does not prove that Azure has accepted the request or that the running application will work. If formatting checks fail, run terraform fmt and review the changes. Use provider documentation to resolve errors rather than guessing the names of removed or deprecated arguments.
Run terraform plan -out=lab.tfplan and inspect the complete plan. It should create only the two intended resources in the expected subscription and region. A saved plan lets terraform apply lab.tfplan execute precisely the configuration reviewed. Do not proceed if the plan includes unrelated existing infrastructure, unexpected network exposure or privileged resources. Obtaining access to create resources is not the same as approval to deploy a public web service; follow your organization’s review process.
After applying, verify the container-group state in the Azure portal or by approved CLI commands. An image-pull failure, missing quota, invalid region or application startup error can prevent a working endpoint even if part of the resource group was created. Review the specific events and logs. Terraform reports infrastructure state, not a guarantee that the process inside the container is listening on the expected port.
Test the public endpoint without exposing data
Retrieve the Azure-assigned address with terraform output -raw container_public_ip. For the harmless sample, open http://<public-ip>/ in a browser in the controlled exercise. This is unencrypted HTTP: do not send a password, token or any confidential input. If the sample does not respond, distinguish between container health, the declared listening port, the exposed group port and actual application behavior. Repeatedly changing network settings without examining evidence can create broader exposure without fixing the cause.
Record three separate observations: Terraform manages the intended container group; Azure shows it running with a public IP; and the hello-world page responds. A successful ping alone would not prove the application is healthy, and a failed ping would not necessarily prove that HTTP traffic is blocked. Application logs, resource status and a direct test of the intended service are more useful than assuming that every failed connectivity test is an Azure routing problem.
A custom image might listen on a different port or only on loopback. Its requirements may differ from the Microsoft sample’s CPU and memory. If you adapt this tutorial, confirm the image’s expected listening address, health endpoint, permissions and runtime dependencies. Never expose a database or administration port merely to make a troubleshooting step easier. If a real application requires external access, production decisions should address TLS, identity, authorization, monitoring and resource governance before it is published.
This is useful Azure experience, but it should not be promoted as a mandatory AZ-900 exam skill. Microsoft AZ-900 cloud concepts provide service-model, security, governance and cost context. The exact hands-on Terraform workflow is an optional infrastructure practice task, not proof about what individual confidential exam items require. It is better to explain what a service does and when it is appropriate than memorize one lab command as an exam shortcut.
Destroy the lab and verify cleanup
Cleanup is part of the exercise, not an optional afterthought. Run terraform plan -destroy -out=destroy.tfplan, review every proposed deletion, and confirm the plan contains only the dedicated resource group and container group from this project. When authorized, execute terraform apply destroy.tfplan. Do not destroy a shared group or infrastructure created by someone else. If you adapted the lab with extra services, check whether those are Terraform-managed and included in the reviewed destruction plan.
After the apply, inspect Azure to confirm the container group and resource group no longer exist. Review any remaining logs, storage, registry or diagnostic services added separately; they may continue to incur charges even after the sample group is gone. Deleting main.tf or losing the state file does not delete resources in Azure. Preserve state securely until the infrastructure’s destruction is confirmed, then handle the state and plan files according to your organization’s retention rules.
What a successful Terraform ACI lab demonstrates
A completed exercise should show the desired configuration, provider and subscription choices, a reviewed plan, an Azure-created public address, a harmless HTTP result, and a verified teardown. The broader lessons are knowing when a public endpoint is appropriate, matching exposure to an actual application port, handling state securely and avoiding surprise costs. Terraform makes resource creation repeatable, but it does not remove the operator’s responsibility for security, permissions or operational verification.
For a future business workload, start by evaluating whether ACI, a private container service or another Azure platform fits the availability, networking and security requirements. Replace the disposable image with an approved immutable image, design authentication and TLS, decide how logs and state will be managed, and automate a controlled release process. The public-IP lab provides a safe way to learn Terraform’s relationship to Azure resources when used with permission; it should not be copied into production unchanged.