Mert Özen Further With Every Line

Blog

Mert Özen's blog posts on software development, backend, frontend, and DevOps.

Series
12 posts found
What Is Infrastructure as Code? Terraform, OpenTofu, Bicep, Pulumi and When to Use Which
DevOps PART 01

What Is Infrastructure as Code? Terraform, OpenTofu, Bicep, Pulumi and When to Use Which

Infrastructure as Code means describing infrastructure in code and letting a tool bring it to the desired state; running the same code twice changes nothing, and a manual change shows up as drift in the next plan. We compare a bash script with Terraform on the same job, look at the code-plan-apply-state loop, place Terraform, OpenTofu, Bicep, Pulumi and Ansible, and work out selection criteria for a team after the BSL, OpenTofu and the retirement of CDKTF.

14 Nov 2026 12 min 13
Installing Terraform and Your First Resource: Providers, init, plan, apply and destroy with a Bookstore Resource Group on Azure
DevOps PART 02

Installing Terraform and Your First Resource: Providers, init, plan, apply and destroy with a Bookstore Resource Group on Azure

Installing Terraform means putting a single binary on your PATH; the real work is making everyone use the same versions with required_version, provider version constraints and a committed .terraform.lock.hcl. We write Bookstore's resource group and storage account with AzureRM 5.x, look at the resource provider registration change in 5.0, read the +, ~, -/+ and - symbols in real plan output, and try saved plans, stale plans, destroy, .gitignore and fmt.

14 Nov 2026 12 min 14
Getting to Know HCL: Variables, Outputs, Locals, Expressions, count, for_each and Dynamic Blocks
DevOps PART 03

Getting to Know HCL: Variables, Outputs, Locals, Expressions, count, for_each and Dynamic Blocks

In HCL a variable is a value from outside, a local an intermediate value computed in the code, and an output a result you expose; because count identifies resources by position and for_each by key, deleting an element from the middle of a list recreates its neighbours with count. With typed, validated variables, tfvars and TF_VAR_, merge/lookup/try/convert, for expressions and dynamic blocks, we write Bookstore's App Service, Azure SQL and Storage code.

14 Nov 2026 14 min 10
Terraform State: What It's For, a Remote Backend on Azure Storage, Locking and State Commands
DevOps PART 04

Terraform State: What It's For, a Remote Backend on Azure Storage, Locking and State Commands

The state is the file that maps every resource in the code to its real-world object and keeps secrets in plain text, which is why it lives not on a laptop or in Git but in a remote backend with restricted access and locking. We set up the backend with a bootstrap stack, move the state with -migrate-state, read a real lock error, write the Azure Storage backend with Entra ID, and try state mv, rm, import and -refresh-only.

14 Nov 2026 14 min 13
The Dependency Graph, Data Sources and Lifecycle: depends_on, create_before_destroy, prevent_destroy and ignore_changes
DevOps PART 05

The Dependency Graph, Data Sources and Lifecycle: depends_on, create_before_destroy, prevent_destroy and ignore_changes

Terraform creates resources according to a dependency graph built from references, running as much as it can in parallel; depends_on is only for dependencies the code cannot see, and lifecycle changes replace and delete behaviour. We draw the graph with Mermaid, read shared resources with data sources, and see with real output how create_before_destroy gets stuck on a fixed name, plus prevent_destroy, ignore_changes, replace_triggered_by, preconditions, destroy = false and check blocks.

14 Nov 2026 12 min 12
Writing and Using Modules: Input/Output Contracts, Registry Modules, Versioning and Dynamic Sources
DevOps PART 06

Writing and Using Modules: Input/Output Contracts, Registry Modules, Versioning and Dynamic Sources

A module is a repeated group of resources that offers a contract through its variables and outputs; moving resources into a module means deleting and recreating them unless you use moved blocks. We move the API into a bookstore-service module and the Web App into bookstore-webapp, see why prevent_destroy did not protect anything, download and inspect an Azure Verified Module, and try version pinning, const variables in module sources and deprecated inputs and outputs from Terraform 1.15.

14 Nov 2026 13 min 16
Separating Environments: Workspaces, Directories or tfvars? Blast Radius and Folder Layout
DevOps PART 07

Separating Environments: Workspaces, Directories or tfvars? Blast Radius and Folder Layout

Every environment needs its own state; for long-lived dev, staging and prod the safest option is to put the shared code in a module and call it from a separate folder per environment with its own backend and values. We try with real output how staging values on the same state replace dev, CLI workspaces, the tfvars and backend config route and the directory layout, and look at splitting state into layers, the risks of terraform_remote_state and promoting module versions with const variables.

14 Nov 2026 15 min 18
Bringing Existing Infrastructure into Terraform: import Blocks, terraform query and list, moved and removed Blocks
DevOps PART 08

Bringing Existing Infrastructure into Terraform: import Blocks, terraform query and list, moved and removed Blocks

The way to bring a hand-built resource into Terraform is not to apply on top of it but to declare with an import block which resource belongs to which address, then decide on every replace line in the first plan. We adopt a hand-built staging with for_each import blocks, see what -generate-config-out and terraform query give you, and try when to delete moved blocks, switching from count to for_each, and handing a resource to another state without deleting it using removed.

14 Nov 2026 15 min 16
Secrets in Terraform: sensitive, Ephemeral Values, Write-only Arguments and Key Vault
DevOps PART 09

Secrets in Terraform: sensitive, Ephemeral Values, Write-only Arguments and Key Vault

sensitive only hides output on screen; the value stays in plain text in the state, in saved plans and in output -json, and the way to keep it out of state is to pass ephemeral values to write-only arguments. We find where secrets leak with real output, see where ephemeral values can be used, try a write-only password and version-based rotation on a real database, and give the Azure SQL administrator password to SQL Server and Key Vault without ever writing it to state.

14 Nov 2026 11 min 18
Validating and Testing Terraform Code: fmt, validate, tflint, terraform test, mock_provider and Policy
DevOps PART 10

Validating and Testing Terraform Code: fmt, validate, tflint, terraform test, mock_provider and Policy

validate only tells you the code can run; to protect your own rules you need layers of tflint, security scanning and terraform test with mock_provider, none of which needs a cloud account. We see validate let a Web App lowered to TLS 1.1 through while a test stops it, triage tflint and Trivy findings, apply a whole Azure environment against mocks, write a real-provider test with Docker and put it all in one script.

14 Nov 2026 13 min 18
Terraform with GitHub Actions: Plan/Apply with OIDC, a Plan Summary on the PR, Approved Apply and Drift Checks
DevOps PART 11

Terraform with GitHub Actions: Plan/Apply with OIDC, a Plan Summary on the PR, Approved Apply and Drift Checks

In a safe Terraform pipeline the plan on the PR is for review; on main the plan is made again and saved, and exactly that file is applied after approval, with plan and apply running as separate identities through OIDC. We write workflows with checks, a plan summary comment, environment-approved apply and concurrency, keep fork PRs away from credentials, and build a drift script that is not fooled by check blocks or provider noise, with real trials.

14 Nov 2026 12 min 20
From Terraform 1.5 to 1.17 and AzureRM 3 to 5: What Changed and How to Read Old Code
DevOps PART 12

From Terraform 1.5 to 1.17 and AzureRM 3 to 5: What Changed and How to Read Old Code

Moving old Terraform code to today's version means doing syntax fixes, address moves and the migration of types the provider removed in separate steps, seeing an empty plan at each step. We run 0.11-era code with 1.17, move null_resource to terraform_data, go through the version map from 1.5 to 1.17 and how -minimal-refresh hides drift, and try the upgrade order from AzureRM 3 to 5 and the real compatibility gaps with OpenTofu.

14 Nov 2026 13 min 16
Blog Map