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)