Part 3 โ Terraform and Secure Cloud Infrastructure
In Part 2, I showed how my GitHub Actions pipeline automated builds, scans, and deployments for my portfolio app.
Now itโs time to talk about infrastructure โ the piece that made everything real in the cloud.
๐ ๏ธ Why Terraform?
I could have clicked buttons in the Azure portal and spun up resources manually. I mean I have already done that in the past before and that would also defeat the whole purpose, I wanted something different, a different challenge without straying from my main goal.
The goal of this project was to showcase DevOps discipline:
- Repeatable deployments (no โ...but it works or worked on my machineโ)
- Version-controlled infrastructure (IaC mindset)
- Security baked in (no leaking secrets in YAML files)
Thatโs why I chose Terraform.
๐ The Infrastructure
Hereโs what I needed for my app to live in the cloud:
- Azure Container App โ the service to run my Dockerized portfolio
- Resource Group & Networking โ to organize and isolate resources
- Terraform Cloud โ remote state storage & secure variable management
Notice something? I didnโt use Azure Container Registry (ACR).
Instead, I built my images in GitHub Actions and pushed them to AWS ECR.
Why? Because it showed I could integrate AWS + Azure in one pipeline โ To showcase a multi-cloud DevSecOps project. A valuable real-world skill.

Provisioned Resource Group and Container App

Terraform Cloud Run Sequence Triggered by GITHUB Actions to provision resources. Status: Successful!
๐ฐ Cost Estimation in Terraform Cloud
But did you also pause to notice something?
One underrated feature of Terraform Cloud is Cost Estimation. Every time a terraform plan runs in TFC, it doesnโt just show what resources will change โ it also estimates the monthly cloud bill of those changes.
For example, this simple VM resource:
resource "azurerm_linux_virtual_machine" "myvm" {
name = "vm1"
size = "Standard_B2s"
admin_username = "adminuser"
resource_group_name = azurerm_resource_group.rg.name
location = azurerm_resource_group.rg.location
network_interface_ids = [
azurerm_network_interface.myvm_nic.id,
]
}
Might give me an output like:
Cost estimate: +$24.50/month
This is huge because it helps avoid surprise cloud bills and keeps infra spending predictable. On bigger teams, you can even enforce policies (e.g., block applies if cost > $100/month).
๐ Lesson learned: Donโt just plan for resources, plan for costs. Infrastructure as Code is also Finance as Code.
๐ Secret Management with Terraform Cloud
One of the trickiest parts was handling secrets.
Terraform needed:
- My ECR authentication token (from AWS)
- My Azure credentials (to deploy resources)
I refused to hardcode them anywhere.
๐ My solution: store them as sensitive variables inside Terraform Cloud.
That way:
- They were encrypted
- Not visible in logs
- Automatically injected into Terraform runs

Terraform ENV Variables configured and set for Authentication to ECS & Deployment to Azure
This gave me a secure, enterprise-style workflow without having to build a full secret management system.
You can get the ECR Auth Token from AWS using the following command:
aws ecr get-login-password --region region
๐ก๏ธ Security Checks with TFSEC
Terraform is code โ which means it can also have vulnerabilities.
For example:
- Misconfigured networking rules
- Exposed storage accounts
- Overly permissive IAM roles
To catch these, I integrated TFSEC into my pipeline.
Every time Terraform code ran, TFSEC checked it against security best practices.
This meant my IaC wasnโt just functional โ it was hardened.
๐ A Simplified Terraform Snippet
Hereโs a safe example (trimmed down for clarity) of how I deployed my Azure Container App:
provider "azurerm" {
features {}
}
resource "azurerm_resource_group" "rg" {
name = "portfolio-rg"
location = "West Europe"
}
resource "azurerm_container_app" "portfolio" {
name = "portfolio-app"
resource_group_name = azurerm_resource_group.rg.name
container_app_environment_id = azurerm_container_app_environment.env.id
template {
container {
name = "portfolio"
image = "ACCOUNT_ID.dkr.ecr.eu-west-1.amazonaws.com/portfolio:${var.image_tag}"
cpu = 0.5
memory = "1.0Gi"
}
}
}
Notice the image is pulled from AWS ECR โ not ACR.
This cross-cloud integration was a deliberate choice to highlight flexibility.
โ The Result
When everything was wired up, hereโs what I got:
- A Dockerized portfolio app running on Azure Container Apps
- Fully managed via Terraform IaC
- Secrets stored securely in Terraform Cloud
- IaC continuously scanned with TFSEC
- Container image scanned with Trivy

Docker build with Trivy Image Scanner.
This setup wasnโt about having the fanciest app.
It was about** proving I could deploy apps securely, consistently, and across multiple clouds.**

Final Application up and running from the Container App Live. Fully automated to make deployment changes based upon new image build
๐ก Why This Matters
Employers donโt just want someone who can write code.
They want engineers who can:
- Deploy infrastructure securely
- Work across multi-cloud environments
- Use IaC for repeatability and control
- Integrate security scanning into DevOps (aka DevSecOps)
Thatโs what this part of the project showed.
๐ Next Up
In Part 4, Iโll share the biggest lessons learned along this journey: the frustrations, the wins, and how Iโd improve this pipeline even further.
Stay tuned! Because thatโs where it all comes together.
Click here to view the final part. Part 4 โก๏ธ

Top comments (0)