GitOps and Infrastructure as Code (IaC) are both fundamental practices in modern DevOps, but they serve different purposes and can be used together. Understanding when to use each approach is key to building reliable deployment pipelines.
In this guide, we’ll explore GitOps, IaC, their differences, and how to combine them effectively.
Understanding Infrastructure as Code
What is IaC?
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Infrastructure as Code (IaC) โ
โ โ
โ Instead of: We Write: โ
โ โ
โ Click in console Terraform/CloudFormation/Pulumi โ
โ Manual config Code that creates resources โ
โ Procedural steps Declarative desired state โ
โ โ
โ Example: โ
โ โ
โ # terraform/main.tf โ
โ resource "aws_instance" "web" { โ
โ ami = "ami-12345" โ
โ instance_type = "t3.micro" โ
โ tags = { โ
โ Name = "web-server" โ
โ } โ
โ } โ
โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
IaC Tools
# Popular IaC tools
tools = {
"terraform": {
"type": "Declarative",
"provider": "Multi-cloud",
"state": "State file",
"use_when": "Multi-cloud, complex infrastructure"
},
"cloudformation": {
"type": "Declarative",
"provider": "AWS only",
"state": "Managed by AWS",
"use_when": "AWS-only environments"
},
"pulumi": {
"type": "Declarative (code)",
"provider": "Multi-cloud",
"state": "State file",
"use_when": "Want real programming language"
},
"ansible": {
"type": "Procedural",
"provider": "Configuration management",
"state": "Stateless",
"use_when": "Server configuration, provisioning"
}
}
Terraform Example
# Complete Terraform setup
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
provider "aws" {
region = "us-east-1"
}
# VPC
resource "aws_vpc" "main" {
cidr_block = "10.0.0.0/16"
enable_dns_hostnames = true
enable_dns_support = true
tags = {
Name = "main-vpc"
}
}
# Subnets
resource "aws_subnet" "public" {
vpc_id = aws_vpc.main.id
cidr_block = "10.0.1.0/24"
map_public_ip_on_launch = true
tags = {
Name = "public-subnet"
}
}
# Security Group
resource "aws_security_group" "web" {
name = "web-sg"
description = "Security group for web servers"
vpc_id = aws_vpc.main.id
ingress {
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}
# EC2 Instance
resource "aws_instance" "web" {
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t3.micro"
subnet_id = aws_subnet.public.id
vpc_security_group_ids = [aws_security_group.web.id]
tags = {
Name = "web-server"
Environment = "production"
}
}
Understanding GitOps
What is GitOps?
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ GitOps Concept โ
โ โ
โ Git is the single source of truth: โ
โ โ
โ โโโโโโโโโโโโ โโโโโโโโโโโโ โโโโโโโโโโโโ โ
โ โ Git โโโโโบโ CI/CD โโโโโบโ Cluster โ โ
โ โ Repositoryโ โ Pipeline โ โ โ โ
โ โโโโโโโโโโโโ โโโโโโโโโโโโ โโโโโโโโโโโโ โ
โ โ โ โ
โ โ Changes flow through Git โ โ
โ โ (Pull requests, reviews, merges) โ โ
โ โ โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ
โ Key principles: โ
โ 1. Declarative infrastructure โ
โ 2. Git as source of truth โ
โ 3. Automated sync โ
โ 4. Drift detection โ
โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
GitOps Tools
# GitOps tools
tools = {
"argocd": {
"description": "GitOps for Kubernetes",
"type": "Pull-based",
"features": ["Multi-tenancy", "UI", "Rollbacks"]
},
"flux": {
"description": "GitOps for Kubernetes",
"type": "Pull-based",
"features": ["Lightweight", "Extensible"]
},
"jenkins_x": {
"description": "Cloud-native CI/CD with GitOps",
"type": "Integrated",
"features": ["Preview envs", "GitOps native"]
},
"argoproj_rollouts": {
"description": "Progressive delivery for GitOps",
"features": ["Blue-green", "Canary", "Analysis"]
}
}
ArgoCD Example
# ArgoCD Application
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: myapp
namespace: argocd
spec:
project: default
source:
repoURL: https://github.com/myorg/myapp
targetRevision: main
path: deploy/k8s
destination:
server: https://kubernetes.default.svc
namespace: production
syncPolicy:
automated:
prune: true # Remove old resources
selfHeal: true # Fix drift
allowEmpty: false
syncOptions:
- CreateNamespace=true
retry:
limit: 5
backoff:
duration: 5s
factor: 2
maxDuration: 3m
---
# Application with multiple sources
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: myapp
spec:
sources:
- repoURL: https://github.com/myorg/myapp
targetRevision: main
path: k8s/overlays/production
- repoURL: https://github.com/myorg/infrastructure
targetRevision: main
path: helm-charts/mychart
helm:
valueFiles:
- values-prod.yaml
Key Differences
Comparison Table
| Aspect | IaC | GitOps |
|---|---|---|
| Focus | Provisioning infrastructure | Deploying applications |
| Trigger | Push (CI/CD pipeline) | Pull (GitOps operator) |
| Target | Cloud resources (VPC, RDS) | Kubernetes resources |
| State | State file (tfstate) | Git repository |
| Drift | Manual check | Auto-sync |
| Tools | Terraform, CloudFormation | ArgoCD, Flux |
| When | Initial setup, infra changes | Day-2 operations |
Use Cases
# When to use IaC
iac_use_cases = [
"Create VPCs and networking",
"Provision databases (RDS, DynamoDB)",
"Set up load balancers",
"Configure IAM roles and policies",
"Initial cluster setup",
"One-time infrastructure"
]
# When to use GitOps
gitops_use_cases = [
"Deploy applications to Kubernetes",
"Manage Kubernetes manifests",
"Manage Helm charts",
"Manage configMaps and secrets",
"Continuous deployment",
"Day-2 operations"
]
Combining IaC and GitOps
Architecture
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Combined IaC + GitOps Architecture โ
โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ Git Repository โ โ
โ โ โ โ
โ โ โโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ โ
โ โ โ infra/ โ โ apps/ โ โ โ
โ โ โ (Terraform)โ โ (Kubernetes manifests)โ โ โ
โ โ โโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ โ โ
โ โผ โผ โ
โ โโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโ โ
โ โ Terraform โ โ ArgoCD โ โ
โ โ Pipeline โ โ Operator โ โ
โ โโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโ โ
โ โ โ โ
โ โผ โผ โ
โ โโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโ โ
โ โ Cloud Providerโ โ Kubernetes โ โ
โ โ (AWS/GCP/etc)โ โ Cluster โ โ
โ โโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโ โ
โ โ
โ Terraform creates the cluster โ
โ ArgoCD deploys apps to it โ
โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Implementation
# 1. Terraform creates the cluster
# terraform/eks.tf
resource "aws_eks_cluster" "main" {
name = "my-cluster"
role_arn = aws_iam_role.eks_cluster.arn
version = "1.28"
vpc_config {
subnet_ids = aws_subnet.private[*].id
}
}
# 2. ArgoCD operator watches for deployments
# apps/myapp.yaml (committed to Git)
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: myapp
spec:
project: default
source:
repoURL: https://github.com/myorg/myapp
targetRevision: main
path: k8s
destination:
server: https://kubernetes.default.svc
namespace: default
syncPolicy:
automated:
prune: true
selfHeal: true
Best Practices
GitOps Best Practices
# GitOps best practices
gitops_practices = [
"Use separate repos for infra and apps",
"Enable branch protection",
"Require PR reviews for changes",
"Use meaningful commit messages",
"Enable auto-sync with drift detection",
"Set up notifications for sync failures",
"Use Helm or Kustomize for templating"
]
# Directory structure
structure = """
โโโ infrastructure/ # Terraform
โ โโโ modules/
โ โโโ environments/
โ โ โโโ dev/
โ โ โโโ prod/
โ โโโ main.tf
โ
โโโ apps/ # Kubernetes
โ โโโ myapp/
โ โ โโโ base/
โ โ โโโ overlays/
โ โ โ โโโ dev/
โ โ โ โโโ prod/
โ โ โโโ kustomization.yaml
โ โโโ otherapp/
"""
IaC Best Practices
# IaC best practices
iac_practices = [
"Use modules for reusability",
"Never commit secrets to Git",
"Use workspaces for environments",
"Enable state locking",
"Use remote state (S3/GCS)",
"Enable drift detection",
"Version your modules"
]
# Secret handling
secret_handling = """
# Use:
# - HashiCorp Vault
# - AWS Secrets Manager
# - Azure Key Vault
# - SOPS with age/yaml
# terraform/secrets.tf
data "aws_secrets_manager_secret_version" "db_creds" {
name = "myapp/database"
}
# DON'T:
# - Commit secrets to Git
# - Store in tfstate
# - Hardcode in code
"""
Migration Path
# Moving to GitOps
migration_steps = [
{
"step": "1. Start with IaC",
"tasks": [
"Define infrastructure in Terraform",
"Set up remote state",
"Enable state locking"
]
},
{
"step": "2. Containerize apps",
"tasks": [
"Dockerize applications",
"Set up image registry",
"Add image scanning to CI"
]
},
{
"step": "3. Add Kubernetes",
"tasks": [
"Provision cluster with Terraform",
"Create K8s manifests",
"Set up Helm charts"
]
},
{
"step": "4. Implement GitOps",
"tasks": [
"Install ArgoCD/Flux",
"Create Application CRDs",
"Enable auto-sync",
"Set up PR workflows"
]
},
{
"step": "5. Automate everything",
"tasks": [
"Add drift detection",
"Set up notifications",
"Enable progressive delivery"
]
}
]
Conclusion
Both IaC and GitOps are essential:
- IaC: Provisioning cloud infrastructure (Terraform)
- GitOps: Deploying applications to Kubernetes (ArgoCD)
Use them together: IaC creates the cluster, GitOps manages what’s deployed to it.
Comments