Focus: Azure architecture fundamentals, DevOps maturity, Infrastructure as Code, and production‑grade thinking
| Pillar | How It’s Addressed |
|---|---|
| Operational Excellence | CI/CD automation, reproducible Terraform plans |
| Security | Network Security Groups, Service Principal, least privilege |
| Reliability | Redundant Node.js containers, stateless app tier |
| Performance Efficiency | NGINX load distribution, lightweight containers |
| Cost Optimization | Single environment, automated teardown via CI/CD |
- Terraform provisions all Azure infrastructure
- Azure Virtual Network (VNet) provides network isolation
- Docker runs all workloads on the host
- NGINX acts as reverse proxy and load distributor
- Two Node.js containers (
web1,web2) provide redundancy - Redis container maintains shared request state
- GitHub Actions automates deployment and teardown
Traffic flows: Internet → NGINX → Node.js containers → Redis
This project demonstrates end-to-end Azure cloud engineering ownership using first principles.
- Designed and deployed a production-style Azure architecture using Terraform (IaC)
- Built a real CI/CD pipeline with GitHub Actions (build → provision → deploy)
- Used Docker + NGINX + Redis to show strong container fundamentals before orchestration
- Made intentional architectural trade-offs (avoided AKS to reduce complexity and cost)
- Treated infrastructure and application code as versioned, repeatable, and auditable
- Showcased DevOps maturity: automation, scalability thinking, and operational clarity
This repository demonstrates an end‑to‑end DevOps workflow on Microsoft Azure using Terraform, Docker, and GitHub Actions.
A simple Node.js + Redis request‑counter application is used to showcase:
- Infrastructure as Code on Azure
- CI/CD‑driven infrastructure lifecycle
- Container‑based application design
- Clear architectural trade‑offs aligned with the Azure Well‑Architected Framework
The application is intentionally simple. The value lies in how it is built, deployed, automated, and evolved.
- No PaaS abstractions or managed load balancers
- Single Azure environment to avoid premature complexity
- Reverse proxy for traffic control
- Horizontally scalable application tier
- Shared state managed explicitly
This mirrors how many real systems start simple and evolve responsibly.
- Terraform provisions all Azure infrastructure
- Azure Virtual Network (VNet) provides network isolation
- Docker runs all workloads on the host
- NGINX acts as reverse proxy and load distributor
- Two Node.js containers (
web1,web2) provide redundancy - Redis container maintains shared request state
- GitHub Actions automates deployment and teardown
Traffic flows: Internet → NGINX → Node.js containers → Redis
A Request Counter application that:
-
Increments a global counter stored in Redis
-
Displays:
- Total request count
- Hostname serving the request (
web1orweb2)
-
Built with Node.js (Express)
-
Served through NGINX
-
Includes a clean, minimal HTML UI
This design demonstrates stateless services + shared state — a foundational cloud pattern.
- Terraform – Azure Infrastructure as Code
- Docker & Docker Compose – Containerization
- NGINX – Reverse proxy & traffic distribution
- Node.js (Express) – Application layer
- Redis – Shared state
- GitHub Actions – CI/CD automation
docker compose up --build- Builds all images
- Starts Redis, Node.js apps, and NGINX
- Application exposed on port 80
Visit:
http://localhost:80cd terra-config/azure
terraform init
terraform plan -out=tfplan
terraform apply -auto-approve tfplanTerraform provisions:
- Azure networking
- Security rules
- Compute host
- Bootstrap configuration for Docker workloads
Add the following secrets to your GitHub repository:
| Secret | Description |
|---|---|
| AZURE_CLIENT_ID | Service Principal App ID |
| AZURE_CLIENT_SECRET | Service Principal Secret |
| AZURE_TENANT_ID | Azure AD Tenant ID |
| AZURE_SUBSCRIPTION_ID | Azure Subscription ID |
Path:
GitHub Repo → Settings → Secrets → Actions
-
Developer pushes code
-
GitHub Actions pipeline triggers
-
Pipeline:
- Validates Terraform
- Provisions Azure infrastructure
- Deploys containers
- Optionally destroys resources
-
Application becomes available automatically
This mirrors real‑world DevOps delivery pipelines.
- Connects to Redis on port
6379 - Increments and displays request count
- Displays hostname of serving container
- Runs on port
5000
- Reverse proxy configuration
- Load balances traffic across Node.js containers
- Redis container
- Two Node.js containers
- NGINX container
- Azure‑specific Terraform configuration
- Networking and security
- Docker bootstrap via cloud‑init
- VM Scale Sets for horizontal scaling
- Azure Load Balancer or Application Gateway
- Azure Cache for Redis (managed)
- Azure DNS for custom domains
- Migration to AKS
- Kubernetes Deployments & Services
- Horizontal Pod Autoscaling
- Managed Identity instead of secrets
- Azure Key Vault integration
- TLS with Azure Certificates
- Terraform linting & policy checks
- Multi‑environment pipelines (dev / prod)
- Blue‑green or canary deployments
- Azure‑focused architecture clarity
- Strong Infrastructure as Code fundamentals
- CI/CD‑driven infrastructure lifecycle
- Intentional simplicity with a clear growth path
- Demonstrates system‑level thinking, not just tooling
This repository reflects how real Azure platforms are designed, delivered, and evolved.
-
Azure VM SKUs are not always available
Valid VM sizes can fail at runtime due to regional capacity constraints (especially smallBseries inaustraliaeast). -
Terraform cannot detect real-time Azure capacity
A successfulterraform plandoes not guaranteeterraform applywill succeed. -
Simplicity beats clever Terraform logic
Overengineering with dynamic SKUs and conditional logic increased failures. Keeping Terraform simple and moving retries to CI worked better. -
CI/CD pipelines expose timing issues
VM creation ≠ application readiness. Startup scripts, Docker installs, and containers need retries and wait logic. -
Azure resource deletion is eventually consistent
Networking resources (NICs, public IPs) may remain locked briefly after VM deletion, causing destroy failures. -
Static resource names don’t work in CI/CD
Re-runs and parallel executions require unique naming to avoid collisions. -
Cloud-init and Docker failures are often silent
Startup issues don’t always surface clearly in Terraform or CI logs. -
Small VMs are not “reliably available”
Popular low-cost SKUs are often the first to run out of capacity.
Key takeaway: Cloud infrastructure must be designed for failure and retries — not just correctness.
This project deliberately avoids Azure Kubernetes Service (AKS) to demonstrate foundational cloud engineering judgment rather than managed-platform dependency.
- Docker on Azure Compute allows full control over networking, ingress, and deployment mechanics
- Keeps the architecture understandable and debuggable for smaller teams
- Demonstrates container fundamentals before orchestration abstraction
- Mirrors real-world cost-conscious environments where AKS may be overkill
AKS is intentionally deferred as a future evolution, not a missing skill.
Key architectural choices made in this project:
| Decision | Reasoning |
|---|---|
| Terraform for IaC | Cloud-agnostic skills, repeatability, auditability |
| GitHub Actions | Native GitHub integration, OIDC-ready, no secrets sprawl |
| Docker over VM apps | Immutable builds, consistency across environments |
| NGINX as reverse proxy | Simple, battle-tested ingress layer |
| Redis caching | Stateless web tier, improved latency |
- Code pushed to
main - GitHub Actions pipeline triggered
- Docker images built and tagged
- Terraform plan + apply executed
- Azure infrastructure provisioned/updated
- Containers deployed and traffic routed via NGINX
This pipeline demonstrates end-to-end automation from commit to production.
Good DevOps is not about complexity — it’s about simplicity,clarity, automation, and disciplined evolution.
If this repository helped you, feel free to ⭐ star it, fork it, or extend it.
