The Roman Empire was famous for its architectural plans — every aqueduct, road, and fortification was first designed on paper before construction began. This allowed them to be reproduced in different provinces with identical results. In DevOps, the same philosophy is realized by Infrastructure as Code (IaC) — defining infrastructure in configuration files instead of manually clicking through admin panels.
IaC is an approach in which infrastructure (servers, databases, networks) is defined in code — versioned, testable, and repeatable:
1// Manual management vs IaC
2const comparison = {
3 manual: {
4 method: 'Clicking in AWS Console / Heroku Dashboard',
5 problems: [
6 'No repeatability — every environment is different',
7 'No change history — who changed what?',
8 'No code review — nobody verifies changes',
9 'Hard to recover after failure',
10 ],
11 },
12 iac: {
13 method: 'Terraform / Pulumi / CloudFormation',
14 benefits: [
15 'Repeatability — identical environments',
16 'Git history — full change history',
17 'Code review — team verifies infrastructure',
18 'Disaster recovery — restoration in minutes',
19 ],
20 },
21};Terraform (HashiCorp) is the most popular IaC tool. It uses the declarative HCL (HashiCorp Configuration Language):
1# Install Terraform
2brew install terraform
3
4# Initialize project
5terraform init
6
7# Preview changes
8terraform plan
9
10# Apply changes
11terraform apply
12
13# Destroy infrastructure
14terraform destroy1// Terraform workflow
2const terraformWorkflow = {
3 step1_init: 'terraform init — download providers and modules',
4 step2_plan: 'terraform plan — show what will change (DRY RUN)',
5 step3_apply: 'terraform apply — apply changes to infrastructure',
6 step4_destroy: 'terraform destroy — remove all infrastructure',
7};
8
9// Plan is like a "blueprint" — you see changes BEFORE applying them
10// Apply is like "construction" — Terraform creates resources in the cloudA Provider is a plugin connecting Terraform to a cloud provider:
1# main.tf — Provider configuration
2terraform {
3 required_providers {
4 aws = {
5 source = "hashicorp/aws"
6 version = "~> 5.0"
7 }
8 }
9
10 # Backend — where to store state
11 backend "s3" {
12 bucket = "roman-imperium-terraform-state"
13 key = "production/terraform.tfstate"
14 region = "eu-central-1"
15 }
16}
17
18provider "aws" {
19 region = var.aws_region
20
21 default_tags {
22 tags = {
23 Project = "roman-imperium"
24 Environment = var.environment
25 ManagedBy = "terraform"
26 }
27 }
28}1# variables.tf — Variable definitions
2variable "aws_region" {
3 description = "AWS Region"
4 type = string
5 default = "eu-central-1"
6}
7
8variable "environment" {
9 description = "Environment (dev, staging, production)"
10 type = string
11}
12
13variable "app_name" {
14 description = "Application name"
15 type = string
16 default = "roman-imperium-api"
17}
18
19variable "db_instance_class" {
20 description = "Database instance class"
21 type = string
22 default = "db.t3.micro"
23}
24
25variable "app_port" {
26 description = "NestJS application port"
27 type = number
28 default = 4000
29}1# compute.tf — Application server
2resource "aws_instance" "api_server" {
3 ami = "ami-0c55b159cbfafe1f0" # Amazon Linux 2
4 instance_type = "t3.small"
5
6 vpc_security_group_ids = [aws_security_group.api_sg.id]
7 subnet_id = aws_subnet.public.id
8
9 user_data = <<-EOF
10 #!/bin/bash
11 yum update -y
12 yum install -y docker
13 systemctl start docker
14 docker pull ${var.app_name}:latest
15 docker run -d -p ${var.app_port}:${var.app_port} ${var.app_name}:latest
16 EOF
17
18 tags = {
19 Name = "${var.app_name}-server"
20 }
21}
22
23resource "aws_security_group" "api_sg" {
24 name = "${var.app_name}-sg"
25 description = "Security group for NestJS API"
26 vpc_id = aws_vpc.main.id
27
28 ingress {
29 from_port = var.app_port
30 to_port = var.app_port
31 protocol = "tcp"
32 cidr_blocks = ["0.0.0.0/0"]
33 }
34
35 ingress {
36 from_port = 443
37 to_port = 443
38 protocol = "tcp"
39 cidr_blocks = ["0.0.0.0/0"]
40 }
41
42 egress {
43 from_port = 0
44 to_port = 0
45 protocol = "-1"
46 cidr_blocks = ["0.0.0.0/0"]
47 }
48}1# database.tf — Database
2resource "aws_docdb_cluster" "mongodb" {
3 cluster_identifier = "${var.app_name}-db"
4 engine = "docdb"
5 master_username = "imperator"
6 master_password = var.db_password
7 skip_final_snapshot = var.environment != "production"
8
9 vpc_security_group_ids = [aws_security_group.db_sg.id]
10 db_subnet_group_name = aws_docdb_subnet_group.db.name
11}
12
13resource "aws_docdb_cluster_instance" "mongodb_instance" {
14 count = var.environment == "production" ? 2 : 1
15 identifier = "${var.app_name}-db-${count.index}"
16 cluster_identifier = aws_docdb_cluster.mongodb.id
17 instance_class = var.db_instance_class
18}Terraform stores state — a mapping between configuration and actual infrastructure:
1// Terraform state
2const terraformState = {
3 localState: {
4 file: 'terraform.tfstate',
5 problems: ['Not shared', 'No locking', 'Risk of loss'],
6 },
7 remoteState: {
8 backend: 'S3, Azure Blob, GCS, Terraform Cloud',
9 benefits: ['Shared', 'Locking', 'Versioning'],
10 },
11};
12
13// NEVER commit terraform.tfstate to the repository!
14// It contains sensitive data (passwords, keys)Modules are reusable infrastructure blocks — like prefabricated elements of Roman buildings:
1# modules/nestjs-app/main.tf
2module "nestjs_app" {
3 source = "./modules/nestjs-app"
4
5 app_name = "roman-imperium-api"
6 environment = "production"
7 instance_type = "t3.small"
8 app_port = 4000
9 db_instance_class = "db.t3.medium"
10}
11
12# The module can be used multiple times
13module "staging_app" {
14 source = "./modules/nestjs-app"
15
16 app_name = "roman-imperium-api"
17 environment = "staging"
18 instance_type = "t3.micro"
19 app_port = 4000
20 db_instance_class = "db.t3.micro"
21}1# outputs.tf
2output "api_server_ip" {
3 description = "API server IP address"
4 value = aws_instance.api_server.public_ip
5}
6
7output "database_endpoint" {
8 description = "Database endpoint"
9 value = aws_docdb_cluster.mongodb.endpoint
10 sensitive = true
11}
12
13output "app_url" {
14 description = "Application URL"
15 value = "http://${aws_instance.api_server.public_ip}:${var.app_port}"
16}1# 1. Initialize project
2terraform init
3
4# 2. Check formatting
5terraform fmt
6
7# 3. Validate configuration
8terraform validate
9
10# 4. Preview changes (DRY RUN)
11terraform plan -out=tfplan
12
13# 5. Apply changes
14terraform apply tfplan
15
16# 6. Check results
17terraform outputTerraform is the architectural plans of our Empire — we define infrastructure in code, version it in Git, review it in code reviews, and apply it automatically. Thanks to IaC we can recreate an entire environment in minutes, not days.