Infrastructure as Code: Fundamentals
What is Infrastructure as Code
Infrastructure as Code (IaC) is the practice of managing and provisioning infrastructure through machine-readable definition files rather than manual processes or interactive configuration tools.
Infrastructure as Code treats infrastructure configuration as software code that can be version controlled, reviewed, tested, and automatically deployed with consistent results.
Core Definition
IaC treats infrastructure configuration as software code that can be:
- Version controlled
- Reviewed and tested
- Automatically deployed
- Repeatedly applied with consistent results
- Shared and reused across teams
What IaC Manages
Compute Resources:
- Virtual machines
- Containers
- Serverless functions
- Auto-scaling groups
Networking:
- Virtual networks and subnets
- Load balancers
- DNS records
- Firewalls and security groups
- VPNs and network gateways
Storage:
- Block storage volumes
- Object storage buckets
- File systems
- Databases
Identity and Access:
- IAM roles and policies
- Service accounts
- API keys
- Security credentials
Application Services:
- Message queues
- Caching layers
- CDN configurations
- Monitoring and logging
Traditional vs. IaC Approach
| Aspect | Traditional (Manual) | Infrastructure as Code |
|---|---|---|
| Provisioning | Point-and-click in console | Code execution |
| Documentation | Separate documents (often outdated) | Code is documentation |
| Consistency | Manual steps = human error | Automated = consistent |
| Speed | Hours to days | Minutes |
| Scalability | Manual replication | Automated replication |
| Version Control | Limited or none | Full git history |
| Disaster Recovery | Manual rebuild from docs | Automated rebuild from code |
| Testing | Manual verification | Automated testing |
Why IaC Matters
Speed and Efficiency
Faster Provisioning:
- Deploy complete environments in minutes, not hours
- Automate repetitive infrastructure tasks
- Reduce time from request to deployment
Parallel Execution:
- Provision multiple resources simultaneously
- Scale infrastructure quickly
- Handle traffic spikes automatically
Consistency and Reliability
Eliminate Configuration Drift:
- Infrastructure matches code definition
- Detect and correct drift automatically
- Prevent “snowflake” servers with unique configurations
Repeatable Deployments:
- Same code produces identical infrastructure
- Reduce environment-specific bugs
- Predictable outcomes
Risk Reduction
Testing Before Production:
- Test infrastructure changes in staging
- Validate configurations before applying
- Preview changes before execution
Quick Recovery:
- Rebuild infrastructure from code
- Disaster recovery becomes deployment
- Reduce MTTR (Mean Time To Recovery)
Rollback Capability:
- Version control enables rollback
- Revert to known-good configurations
- Minimize downtime from bad changes
Cost Optimization
Resource Lifecycle Management:
- Automatically tear down unused environments
- Schedule resources (dev environments off at night)
- Right-size infrastructure based on metrics
Visibility:
- Track infrastructure costs in code
- Review infrastructure changes like code reviews
- Identify cost optimization opportunities
Collaboration and Knowledge Sharing
Code Review Process:
- Infrastructure changes reviewed like code
- Knowledge sharing through pull requests
- Documented decisions in commit history
Standardization:
- Reusable modules and templates
- Organization-wide best practices
- Reduced learning curve
Transparency:
- Clear visibility into infrastructure
- Self-service for developers
- Reduced dependency on ops team
Core IaC Concepts
Declarative (What)
Describe the desired end state; the tool figures out how to achieve it.
- Define desired state
- Tool determines steps to reach that state
- Idempotent by nature
- Easier to reason about
- Tools: Terraform, CloudFormation, Pulumi
Imperative (How)
Specify the exact steps to execute to achieve the desired state.
- Define specific actions
- Explicit control over execution
- Must handle state checking manually
- More flexible but more complex
- Tools: Ansible, scripts, SDKs
Declarative vs. Imperative
Declarative (What): Describe the desired end state; the tool figures out how to achieve it.
# Terraform example - Declarative
resource "aws_instance" "web" {
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t2.micro"
count = 3
}
Characteristics:
- Define desired state
- Tool determines steps to reach that state
- Idempotent by nature
- Easier to reason about
Tools: Terraform, CloudFormation, Pulumi (declarative mode)
Imperative (How): Specify the exact steps to execute to achieve the desired state.
# Imperative approach
for i in range(3):
create_ec2_instance(
ami="ami-0c55b159cbfafe1f0",
instance_type="t2.micro"
)
Characteristics:
- Define specific actions
- Explicit control over execution
- Must handle state checking manually
- More flexible but more complex
Tools: Ansible (can be declarative), scripts, SDKs
Idempotency
Definition: Running the same IaC code multiple times produces the same result without unintended side effects.
Why it matters:
- Safe to re-run deployments
- Automatic drift correction
- Simplified automation
- Predictable outcomes
Example:
# First run: Creates 3 instances
# Second run: No changes (3 instances already exist)
# Third run: Still no changes
resource "aws_instance" "web" {
count = 3
# ...
}
Achieving idempotency:
- Check current state before acting
- Use unique identifiers
- Avoid hardcoded values that change
- Design for re-execution
Immutability
Immutable Infrastructure: Rather than updating existing resources, replace them with new versions.
Benefits:
- Prevents configuration drift
- Easier rollback (keep old version)
- Simplified testing (test exact deployment artifact)
- Reduced debugging complexity
Mutable vs. Immutable:
| Approach | Update Process | Pros | Cons |
|---|---|---|---|
| Mutable | Modify existing resources | Faster updates | Configuration drift, harder to reproduce |
| Immutable | Replace with new resources | Consistent, reproducible | Longer deployment, more complex |
Implementation:
- Blue-green deployments
- Canary deployments
- Container-based deployments
- AMI/image-based deployments
IaC Approaches
Declarative Approach
Philosophy: Describe what you want, not how to get there.
How it works:
- Define desired state in configuration files
- Tool compares desired state to current state
- Tool calculates and executes necessary changes
Advantages:
- Simpler to understand (what, not how)
- Automatically idempotent
- Easier to maintain
- Tool handles complexity
Disadvantages:
- Less control over execution order (sometimes)
- Abstraction can hide underlying operations
- Learning curve for tool-specific syntax
Best for:
- Cloud infrastructure provisioning
- Consistent, repeatable deployments
- Teams preferring simplicity
- Complex state management
Imperative Approach
Philosophy: Define exact steps to execute.
How it works:
- Write scripts/code with specific actions
- Execute actions in defined order
- Handle state checking and updates manually
Advantages:
- Full control over execution
- Use familiar programming languages
- Fine-grained error handling
- Flexibility for complex scenarios
Disadvantages:
- Must implement idempotency manually
- More code to write and maintain
- Higher risk of errors
- State management complexity
Best for:
- Complex orchestration workflows
- Migration tasks
- Custom automation
- When declarative tools don’t fit
Hybrid Approach
Philosophy: Combine declarative and imperative as needed.
How it works:
- Use declarative tools for infrastructure provisioning
- Use imperative scripts for configuration management
- Orchestrate with higher-level tools
Example:
# Terraform for infrastructure (declarative)
# Ansible for configuration (imperative playbooks)
# CI/CD pipeline orchestrates both
Advantages:
- Best of both worlds
- Right tool for each job
- Flexibility where needed
Disadvantages:
- Multiple tools to learn
- Integration complexity
- Potential consistency issues
Security Considerations for IaC
Critical: Never Commit Secrets
IaC templates often need credentials, API keys, and passwords. Hardcoding these in files that go into version control is a critical security risk. Always use secret management services and mark variables as sensitive.
Never Commit Secrets to Code
The problem: IaC templates often need credentials, API keys, and passwords. Hardcoding these in files that go into version control is a critical security risk.
Terraform example:
# ❌ NEVER do this
resource "aws_db_instance" "db" {
password = "MyPassword123!" # Committed to git = exposed
}
# ✅ Use variables marked as sensitive
variable "db_password" {
type = string
sensitive = true
}
resource "aws_db_instance" "db" {
password = var.db_password
}
CloudFormation example:
# ✅ Reference secrets from AWS Secrets Manager
Resources:
Database:
Type: AWS::RDS::DBInstance
Properties:
MasterUserPassword: !Sub '{{resolve:secretsmanager:${DBSecret}:SecretString:password}}'
Use Secret Management Services
Don’t pass secrets as plain text; retrieve them from secure stores:
- AWS Secrets Manager / Parameter Store: Native AWS secret storage
- HashiCorp Vault: Multi-cloud secret management
- Azure Key Vault: Native Azure secret storage
- GCP Secret Manager: Native GCP secret storage
State Files Contain Secrets
Critical: IaC state files (like Terraform’s terraform.tfstate) contain the actual values of all resources, including secrets.
Protect state files:
- Always use remote state with encryption (covered in IaC State Management)
- Restrict access to state storage (S3 bucket policies, IAM roles)
- Never commit state files to version control
Scan IaC Before Applying
Static analysis tools catch security issues before deployment:
- Checkov: Multi-framework scanner (Terraform, CloudFormation, Kubernetes)
- tfsec: Terraform-focused security scanner
- cfn-lint: CloudFormation linter with security checks
- Terrascan: Policy-as-code scanner
Run security scans before deployment and integrate them into your CI/CD pipeline to automatically fail builds with security issues.
Getting Started
Choosing Your First IaC Tool
For AWS-only projects:
- Start with AWS CloudFormation (native, free, deep integration)
For multi-cloud or flexibility:
- Start with Terraform (most popular, broad ecosystem, declarative)
For developers who prefer code:
- AWS CDK (AWS-focused, TypeScript/Python/Java/C#/.NET, generates CloudFormation)
- Pulumi (multi-cloud, TypeScript/Python/Go/C#/Java, own state management)
For configuration management:
- Start with Ansible (agentless, easy to learn, YAML-based)
Learning Path
1. Start Small
- Deploy a single resource (S3 bucket, EC2 instance)
- Understand the workflow: write configuration, preview changes, apply
- Practice with non-production resources
2. Add Complexity Gradually
- Multiple resources with dependencies
- Variables and outputs
- Modules and reusable components
3. Implement Best Practices
- Remote state management (covered in IaC State Management)
- Version control and code review process
- Automated testing (covered in IaC Testing)
4. Production Readiness
- Security scanning integration
- Multi-environment management
- CI/CD pipeline integration
- Disaster recovery procedures
Found this guide helpful? Share it with your team:
Share on LinkedIn