YAML Infrastructure as Code Tools: A Comprehensive Guide
YAML Infrastructure as Code Tools
YAML (YAML Ain’t Markup Language) has become the de facto standard for defining infrastructure as code due to its human-readable syntax and simple structure. Unlike domain-specific languages or programming languages, YAML offers a declarative approach that’s easy to learn and widely supported across the DevOps ecosystem.
This guide explores the most popular YAML-based Infrastructure as Code tools, their use cases, and best practices.
Why YAML for Infrastructure as Code?
Advantages of YAML
Human-Readable: YAML’s clean syntax makes it easy for both developers and operations teams to read and write infrastructure definitions.
Language-Agnostic: YAML files can be processed by any programming language, making them universally compatible.
Declarative: YAML naturally supports a declarative approach where you specify what you want, not how to achieve it.
Version Control Friendly: Plain text format works seamlessly with Git and other version control systems.
Wide Adoption: Most modern IaC tools support YAML as a primary or alternative format.
Challenges with YAML
Indentation Sensitivity: YAML relies on spaces for structure, making it prone to indentation errors.
Limited Logic: YAML lacks built-in programming constructs like loops and conditionals (though some tools extend it).
Verbose for Complex Scenarios: Large infrastructure definitions can become unwieldy and repetitive.
No Type Safety: YAML doesn’t provide compile-time type checking.
Popular YAML Infrastructure as Code Tools
1. Ansible
Type: Configuration Management & Orchestration
Primary Use: Server configuration, application deployment, automation
Overview: Ansible is an agentless automation tool that uses YAML playbooks to define tasks, roles, and workflows. It’s particularly strong for configuration management and multi-tier application deployments.
Example:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
# Ansible Playbook - Deploy NGINX Web Server
---
- name: Deploy NGINX Web Server
hosts: webservers
become: yes
vars:
nginx_port: 80
document_root: /var/www/html
tasks:
- name: Update apt cache
apt:
update_cache: yes
cache_valid_time: 3600
- name: Install NGINX
apt:
name: nginx
state: present
- name: Copy website files
copy:
src: files/index.html
dest: "{{ document_root }}/index.html"
owner: www-data
group: www-data
mode: '0644'
- name: Configure NGINX
template:
src: templates/nginx.conf.j2
dest: /etc/nginx/sites-available/default
notify: Reload NGINX
- name: Ensure NGINX is started
service:
name: nginx
state: started
enabled: yes
handlers:
- name: Reload NGINX
service:
name: nginx
state: reloaded
Strengths:
- Simple, readable YAML syntax
- Agentless (uses SSH)
- Large module library
- Strong community support
- Excellent for configuration management
Weaknesses:
- Slower for large-scale infrastructure
- Not ideal for cloud resource provisioning
- Can become complex with many roles
Best For: Server configuration, application deployment, automation tasks, multi-cloud orchestration
2. Kubernetes
Type: Container Orchestration
Primary Use: Container deployment and management
Overview: Kubernetes uses YAML manifests to define containerized applications, services, deployments, and cluster resources. It’s the industry standard for container orchestration.
Example:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
# Kubernetes Deployment and Service
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
labels:
app: nginx
spec:
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.21
ports:
- containerPort: 80
resources:
requests:
memory: "64Mi"
cpu: "250m"
limits:
memory: "128Mi"
cpu: "500m"
livenessProbe:
httpGet:
path: /
port: 80
initialDelaySeconds: 30
periodSeconds: 10
---
apiVersion: v1
kind: Service
metadata:
name: nginx-service
spec:
type: LoadBalancer
selector:
app: nginx
ports:
- protocol: TCP
port: 80
targetPort: 80
Strengths:
- Industry-standard container orchestration
- Self-healing and auto-scaling
- Declarative and idempotent
- Strong ecosystem
- Cloud-agnostic
Weaknesses:
- Steep learning curve
- Complex for simple applications
- Requires cluster management
- Verbose YAML files
Best For: Container orchestration, microservices, cloud-native applications
3. Helm
Type: Kubernetes Package Manager
Primary Use: Kubernetes application packaging and templating
Overview: Helm extends Kubernetes with templating capabilities, allowing you to parameterize YAML manifests and package applications as reusable charts.
Example:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
# Helm Chart - values.yaml
replicaCount: 3
image:
repository: nginx
tag: "1.21"
pullPolicy: IfNotPresent
service:
type: LoadBalancer
port: 80
ingress:
enabled: true
annotations:
kubernetes.io/ingress.class: nginx
hosts:
- host: myapp.example.com
paths:
- path: /
pathType: Prefix
resources:
limits:
cpu: 500m
memory: 128Mi
requests:
cpu: 250m
memory: 64Mi
autoscaling:
enabled: true
minReplicas: 3
maxReplicas: 10
targetCPUUtilizationPercentage: 80
# Deployment template (templates/deployment.yaml)
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "mychart.fullname" . }}
spec:
replicas: {{ .Values.replicaCount }}
selector:
matchLabels:
app: {{ include "mychart.name" . }}
template:
metadata:
labels:
app: {{ include "mychart.name" . }}
spec:
containers:
- name: {{ .Chart.Name }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
ports:
- containerPort: 80
resources:
{{- toYaml .Values.resources | nindent 10 }}
Strengths:
- Templating and parameterization
- Package management for Kubernetes
- Version control for releases
- Large chart repository
- Rollback capabilities
Weaknesses:
- Adds complexity to Kubernetes
- Template syntax can be confusing
- Debugging can be difficult
- Limited to Kubernetes
Best For: Managing complex Kubernetes applications, reusable application templates, versioned deployments
4. Docker Compose
Type: Container Orchestration (Development)
Primary Use: Multi-container application definition
Overview: Docker Compose uses YAML to define multi-container applications, making it easy to spin up development environments and simple production deployments.
Example:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
# docker-compose.yml - Full Stack Application
version: '3.8'
services:
nginx:
image: nginx:alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/conf.d:/etc/nginx/conf.d:ro
- ./nginx/ssl:/etc/nginx/ssl:ro
- static_volume:/static
depends_on:
- web
networks:
- frontend
restart: unless-stopped
web:
build:
context: ./app
dockerfile: Dockerfile
command: gunicorn myapp.wsgi:application --bind 0.0.0.0:8000
volumes:
- ./app:/app
- static_volume:/app/static
environment:
- DEBUG=False
- DATABASE_URL=postgresql://postgres:password@db:5432/myapp
- REDIS_URL=redis://redis:6379/0
depends_on:
- db
- redis
networks:
- frontend
- backend
restart: unless-stopped
db:
image: postgres:14-alpine
volumes:
- postgres_data:/var/lib/postgresql/data
environment:
- POSTGRES_DB=myapp
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=password
networks:
- backend
restart: unless-stopped
redis:
image: redis:7-alpine
volumes:
- redis_data:/data
networks:
- backend
restart: unless-stopped
celery:
build:
context: ./app
dockerfile: Dockerfile
command: celery -A myapp worker -l info
volumes:
- ./app:/app
environment:
- DATABASE_URL=postgresql://postgres:password@db:5432/myapp
- REDIS_URL=redis://redis:6379/0
depends_on:
- db
- redis
networks:
- backend
restart: unless-stopped
volumes:
postgres_data:
redis_data:
static_volume:
networks:
frontend:
backend:
Strengths:
- Simple and intuitive
- Perfect for local development
- Quick multi-container setup
- Great documentation
- Wide adoption
Weaknesses:
- Not suitable for production at scale
- Limited orchestration features
- Single-host deployment
- No built-in load balancing
Best For: Local development environments, simple multi-container applications, testing
5. AWS CloudFormation
Type: Cloud Infrastructure Provisioning
Primary Use: AWS resource management
Overview: CloudFormation uses YAML (or JSON) templates to provision and manage AWS infrastructure as code. It provides native integration with all AWS services.
Example:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
# CloudFormation - VPC with EC2 and RDS
AWSTemplateFormatVersion: '2010-09-09'
Description: 'Web Application Stack with VPC, EC2, and RDS'
Parameters:
Environment:
Type: String
Default: production
AllowedValues:
- development
- staging
- production
InstanceType:
Type: String
Default: t3.micro
AllowedValues:
- t3.micro
- t3.small
- t3.medium
Resources:
VPC:
Type: AWS::EC2::VPC
Properties:
CidrBlock: 10.0.0.0/16
EnableDnsHostnames: true
EnableDnsSupport: true
Tags:
- Key: Name
Value: !Sub ${Environment}-vpc
PublicSubnet:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
CidrBlock: 10.0.1.0/24
AvailabilityZone: !Select [0, !GetAZs '']
MapPublicIpOnLaunch: true
Tags:
- Key: Name
Value: !Sub ${Environment}-public-subnet
PrivateSubnet:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
CidrBlock: 10.0.2.0/24
AvailabilityZone: !Select [1, !GetAZs '']
Tags:
- Key: Name
Value: !Sub ${Environment}-private-subnet
InternetGateway:
Type: AWS::EC2::InternetGateway
Properties:
Tags:
- Key: Name
Value: !Sub ${Environment}-igw
AttachGateway:
Type: AWS::EC2::VPCGatewayAttachment
Properties:
VpcId: !Ref VPC
InternetGatewayId: !Ref InternetGateway
WebServerSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Security group for web server
VpcId: !Ref VPC
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 80
ToPort: 80
CidrIp: 0.0.0.0/0
- IpProtocol: tcp
FromPort: 443
ToPort: 443
CidrIp: 0.0.0.0/0
Tags:
- Key: Name
Value: !Sub ${Environment}-web-sg
WebServer:
Type: AWS::EC2::Instance
Properties:
InstanceType: !Ref InstanceType
ImageId: ami-0c55b159cbfafe1f0
SubnetId: !Ref PublicSubnet
SecurityGroupIds:
- !Ref WebServerSecurityGroup
Tags:
- Key: Name
Value: !Sub ${Environment}-web-server
UserData:
Fn::Base64: !Sub |
#!/bin/bash
yum update -y
yum install -y nginx
systemctl start nginx
systemctl enable nginx
Outputs:
VPCId:
Description: VPC ID
Value: !Ref VPC
Export:
Name: !Sub ${Environment}-VPC-ID
WebServerPublicIP:
Description: Web Server Public IP
Value: !GetAtt WebServer.PublicIp
Strengths:
- Native AWS integration
- Supports all AWS services
- Drift detection
- Free to use
- Stack-based management
Weaknesses:
- AWS-only (vendor lock-in)
- Verbose YAML syntax
- Slower execution
- Limited error messages
- Complex intrinsic functions
Best For: AWS-native infrastructure, enterprise AWS deployments, compliance-driven environments
6. Google Cloud Deployment Manager
Type: Cloud Infrastructure Provisioning
Primary Use: Google Cloud Platform resource management
Overview: Deployment Manager uses YAML to define GCP resources, with optional Python or Jinja2 templating for advanced scenarios.
Example:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
# GCP Deployment Manager - Compute Engine with Load Balancer
resources:
- name: web-instance-template
type: compute.v1.instanceTemplate
properties:
properties:
machineType: n1-standard-1
disks:
- deviceName: boot
type: PERSISTENT
boot: true
autoDelete: true
initializeParams:
sourceImage: projects/debian-cloud/global/images/family/debian-11
networkInterfaces:
- network: global/networks/default
accessConfigs:
- name: External NAT
type: ONE_TO_ONE_NAT
metadata:
items:
- key: startup-script
value: |
#!/bin/bash
apt-get update
apt-get install -y nginx
systemctl start nginx
- name: web-instance-group
type: compute.v1.instanceGroupManager
properties:
baseInstanceName: web-instance
instanceTemplate: $(ref.web-instance-template.selfLink)
targetSize: 3
zone: us-central1-a
- name: web-health-check
type: compute.v1.httpHealthCheck
properties:
port: 80
requestPath: /
checkIntervalSec: 5
timeoutSec: 5
unhealthyThreshold: 2
healthyThreshold: 2
- name: web-backend-service
type: compute.v1.backendService
properties:
backends:
- group: $(ref.web-instance-group.instanceGroup)
balancingMode: UTILIZATION
maxUtilization: 0.8
healthChecks:
- $(ref.web-health-check.selfLink)
protocol: HTTP
port: 80
timeoutSec: 30
- name: web-url-map
type: compute.v1.urlMap
properties:
defaultService: $(ref.web-backend-service.selfLink)
- name: web-target-proxy
type: compute.v1.targetHttpProxy
properties:
urlMap: $(ref.web-url-map.selfLink)
- name: web-forwarding-rule
type: compute.v1.globalForwardingRule
properties:
target: $(ref.web-target-proxy.selfLink)
portRange: 80
outputs:
- name: load-balancer-ip
value: $(ref.web-forwarding-rule.IPAddress)
Strengths:
- Native GCP integration
- Python/Jinja2 templating
- Preview deployments
- Free to use
- Template-based reusability
Weaknesses:
- GCP-only (vendor lock-in)
- Smaller community
- Less mature than competitors
- Limited documentation
- Complex syntax for advanced use cases
Best For: GCP-native infrastructure, GCP-first organizations
7. GitHub Actions
Type: CI/CD and Automation
Primary Use: Workflow automation, CI/CD pipelines
Overview: GitHub Actions uses YAML to define workflows for continuous integration, deployment, and automation tasks triggered by GitHub events.
Example:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
# GitHub Actions - CI/CD Pipeline
name: CI/CD Pipeline
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
workflow_dispatch:
env:
DOCKER_REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ['3.9', '3.10', '3.11']
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
- name: Cache dependencies
uses: actions/cache@v3
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ hashFiles('requirements.txt') }}
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
pip install pytest pytest-cov
- name: Run tests
run: |
pytest --cov=app --cov-report=xml
- name: Upload coverage
uses: codecov/codecov-action@v3
with:
files: ./coverage.xml
lint:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.11'
- name: Install linting tools
run: |
pip install flake8 black isort
- name: Run linters
run: |
flake8 app/
black --check app/
isort --check-only app/
build:
needs: [test, lint]
runs-on: ubuntu-latest
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Log in to Container Registry
uses: docker/login-action@v2
with:
registry: ${{ env.DOCKER_REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata
id: meta
uses: docker/metadata-action@v4
with:
images: ${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=ref,event=branch
type=sha,prefix={{branch}}-
type=semver,pattern={{version}}
- name: Build and push
uses: docker/build-push-action@v4
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
deploy:
needs: build
runs-on: ubuntu-latest
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
environment:
name: production
url: https://myapp.example.com
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Configure kubectl
uses: azure/k8s-set-context@v3
with:
method: kubeconfig
kubeconfig: ${{ secrets.KUBE_CONFIG }}
- name: Deploy to Kubernetes
run: |
kubectl set image deployment/myapp \
myapp=${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}:main-${{ github.sha }}
kubectl rollout status deployment/myapp
- name: Verify deployment
run: |
kubectl get pods -l app=myapp
Strengths:
- Native GitHub integration
- Free for public repositories
- Matrix builds
- Large action marketplace
- Excellent documentation
Weaknesses:
- GitHub-specific
- Limited debugging
- Can be complex for advanced workflows
- Minutes consumption for private repos
Best For: GitHub projects, CI/CD automation, repository workflows
8. GitLab CI/CD
Type: CI/CD and Automation
Primary Use: Pipeline automation, continuous deployment
Overview: GitLab CI/CD uses .gitlab-ci.yml to define pipelines with stages, jobs, and deployment strategies.
Example:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
# .gitlab-ci.yml - Complete CI/CD Pipeline
variables:
DOCKER_DRIVER: overlay2
DOCKER_TLS_CERTDIR: "/certs"
IMAGE_TAG: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA
stages:
- test
- build
- deploy
.test-template: &test-template
stage: test
image: python:3.11
before_script:
- pip install -r requirements.txt
- pip install pytest pytest-cov
coverage: '/(?i)total.*? (100(?:\.0+)?\%|[1-9]?\d(?:\.\d+)?\%)$/'
test:unit:
<<: *test-template
script:
- pytest tests/unit/ --cov=app --cov-report=term --cov-report=xml
artifacts:
reports:
coverage_report:
coverage_format: cobertura
path: coverage.xml
test:integration:
<<: *test-template
services:
- postgres:14
- redis:7
variables:
POSTGRES_DB: testdb
POSTGRES_USER: testuser
POSTGRES_PASSWORD: testpass
script:
- pytest tests/integration/
lint:
stage: test
image: python:3.11
script:
- pip install flake8 black isort
- flake8 app/
- black --check app/
- isort --check-only app/
security:
stage: test
image: python:3.11
script:
- pip install bandit safety
- bandit -r app/
- safety check
build:
stage: build
image: docker:latest
services:
- docker:dind
before_script:
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
script:
- docker build -t $IMAGE_TAG .
- docker push $IMAGE_TAG
only:
- main
- develop
deploy:staging:
stage: deploy
image: alpine:latest
before_script:
- apk add --no-cache curl
- curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"
- chmod +x kubectl
- mv kubectl /usr/local/bin/
script:
- kubectl config use-context staging
- kubectl set image deployment/myapp myapp=$IMAGE_TAG
- kubectl rollout status deployment/myapp
environment:
name: staging
url: https://staging.myapp.example.com
only:
- develop
deploy:production:
stage: deploy
image: alpine:latest
before_script:
- apk add --no-cache curl
- curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"
- chmod +x kubectl
- mv kubectl /usr/local/bin/
script:
- kubectl config use-context production
- kubectl set image deployment/myapp myapp=$IMAGE_TAG
- kubectl rollout status deployment/myapp
environment:
name: production
url: https://myapp.example.com
when: manual
only:
- main
Strengths:
- Integrated with GitLab
- Free CI/CD minutes
- Built-in container registry
- Auto DevOps
- Excellent features
Weaknesses:
- GitLab-specific
- Self-hosted option can be complex
- Learning curve for advanced features
Best For: GitLab projects, integrated DevOps workflows
9. CircleCI
Type: CI/CD Platform
Primary Use: Continuous integration and deployment
Overview: CircleCI uses .circleci/config.yml to define build, test, and deployment workflows with powerful caching and parallelization features.
Example:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
# .circleci/config.yml - Advanced CI/CD Pipeline
version: 2.1
orbs:
node: circleci/node@5.0
docker: circleci/docker@2.1
kubernetes: circleci/kubernetes@1.3
executors:
python-executor:
docker:
- image: cimg/python:3.11
resource_class: medium
jobs:
test:
executor: python-executor
steps:
- checkout
- restore_cache:
keys:
- v1-dependencies-{{ checksum "requirements.txt" }}
- v1-dependencies-
- run:
name: Install dependencies
command: |
python -m venv venv
. venv/bin/activate
pip install -r requirements.txt
- save_cache:
paths:
- ./venv
key: v1-dependencies-{{ checksum "requirements.txt" }}
- run:
name: Run tests
command: |
. venv/bin/activate
pytest --junitxml=test-results/junit.xml --cov=app --cov-report=html
- store_test_results:
path: test-results
- store_artifacts:
path: htmlcov
lint:
executor: python-executor
steps:
- checkout
- run:
name: Install linting tools
command: pip install flake8 black isort mypy
- run:
name: Run linters
command: |
flake8 app/
black --check app/
isort --check-only app/
mypy app/
build:
executor: docker/docker
steps:
- checkout
- setup_remote_docker:
version: 20.10.14
- docker/check
- docker/build:
image: myorg/myapp
tag: ${CIRCLE_SHA1}
- docker/push:
image: myorg/myapp
tag: ${CIRCLE_SHA1}
deploy:
executor: kubernetes/default
steps:
- checkout
- kubernetes/install-kubectl
- run:
name: Deploy to Kubernetes
command: |
kubectl set image deployment/myapp \
myapp=myorg/myapp:${CIRCLE_SHA1}
kubectl rollout status deployment/myapp
workflows:
version: 2
build-and-deploy:
jobs:
- test
- lint
- build:
requires:
- test
- lint
filters:
branches:
only: main
- deploy:
requires:
- build
filters:
branches:
only: main
Strengths:
- Fast execution
- Advanced caching
- Matrix builds
- SSH debugging
- Great performance
Weaknesses:
- Cost for private projects
- Learning curve
- Limited free tier
Best For: Performance-critical CI/CD, enterprise projects
YAML Best Practices for IaC
1. Use Consistent Indentation
Always use 2 spaces for indentation (never tabs):
1
2
3
4
5
6
7
8
9
10
11
12
13
# Good
services:
web:
image: nginx
ports:
- "80:80"
# Bad (inconsistent spacing)
services:
web:
image: nginx
ports:
- "80:80"
2. Use Comments Liberally
Document complex configurations and explain non-obvious decisions:
1
2
3
4
5
6
7
8
9
10
11
12
# Production database configuration
# Uses read replicas for improved performance
database:
primary:
host: db-primary.example.com
port: 5432
# Read-only replicas for SELECT queries
replicas:
- host: db-replica-1.example.com
port: 5432
- host: db-replica-2.example.com
port: 5432
3. Use Anchors and Aliases for Reusability
Avoid repetition with YAML anchors (&) and aliases (*):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# Define common configuration
.common-config: &common
memory: 512Mi
cpu: 250m
restart_policy: always
services:
web:
<<: *common
image: nginx
api:
<<: *common
image: myapi
4. Validate YAML Syntax
Use linters and validators before deployment:
1
2
3
4
5
6
# Use yamllint
yamllint config.yml
# Use online validators
# - https://www.yamllint.com/
# - https://jsonformatter.org/yaml-validator
5. Use Version Control
Always version control your YAML files:
1
2
3
git add infrastructure.yml
git commit -m "Add production database configuration"
git push origin main
6. Separate Environments
Use different files for different environments:
1
2
3
4
5
infrastructure/
├── base.yml # Common configuration
├── development.yml # Dev overrides
├── staging.yml # Staging overrides
└── production.yml # Production configuration
7. Use Variables and Parameters
Parameterize values that change between environments:
1
2
3
4
5
6
7
# Docker Compose with environment variables
services:
web:
image: ${IMAGE_NAME:-nginx:latest}
environment:
- DATABASE_URL=${DATABASE_URL}
- API_KEY=${API_KEY}
8. Keep Files Focused and Modular
Split large configurations into smaller, focused files:
1
2
3
4
5
6
# main.yml
includes:
- network.yml
- compute.yml
- storage.yml
- security.yml
9. Use Multi-line Strings Properly
Choose the right style for multi-line content:
1
2
3
4
5
6
7
8
9
10
11
12
13
# Literal style (preserves newlines and indentation)
script: |
#!/bin/bash
echo "Starting deployment"
kubectl apply -f deployment.yml
# Folded style (folds newlines to spaces)
description: >
This is a long description that will be
folded into a single line with spaces.
# Plain style (for simple strings)
name: my-application
10. Document Required Variables
Use comments to document required environment variables or parameters:
1
2
3
4
5
6
7
8
9
10
11
12
13
# Required environment variables:
# - DATABASE_URL: PostgreSQL connection string
# - REDIS_URL: Redis connection string
# - SECRET_KEY: Application secret key (min 32 characters)
# - AWS_ACCESS_KEY_ID: AWS access key
# - AWS_SECRET_ACCESS_KEY: AWS secret key
services:
app:
environment:
DATABASE_URL: ${DATABASE_URL}
REDIS_URL: ${REDIS_URL}
SECRET_KEY: ${SECRET_KEY}
Comparison Table
| Tool | Type | Learning Curve | Cloud | Best Use Case |
|---|---|---|---|---|
| Ansible | Config Management | Low | Multi | Server configuration, automation |
| Kubernetes | Orchestration | High | Multi | Container orchestration |
| Helm | Package Manager | Moderate | Multi | Kubernetes app packaging |
| Docker Compose | Container Tool | Low | Multi | Local development, simple apps |
| CloudFormation | Cloud IaC | Moderate | AWS | AWS infrastructure |
| GCP Deployment Manager | Cloud IaC | Moderate | GCP | GCP infrastructure |
| GitHub Actions | CI/CD | Low | Multi | GitHub automation |
| GitLab CI/CD | CI/CD | Moderate | Multi | GitLab projects |
| CircleCI | CI/CD | Moderate | Multi | High-performance CI/CD |
Choosing the Right YAML IaC Tool
For Configuration Management
Choose Ansible when you need to:
- Configure servers and applications
- Automate repetitive tasks
- Work across multiple cloud providers
- Use simple, readable syntax
For Container Orchestration
Choose Kubernetes when you need:
- Production-grade container orchestration
- Auto-scaling and self-healing
- Service discovery and load balancing
- Cloud-native applications
Choose Docker Compose when you need:
- Local development environments
- Simple multi-container applications
- Quick prototyping
- Single-host deployments
For Cloud Infrastructure
Choose CloudFormation for AWS-only infrastructure with:
- Deep AWS service integration
- Compliance requirements
- Drift detection needs
- Stack-based management
Choose GCP Deployment Manager for GCP-first organizations
For CI/CD Pipelines
Choose GitHub Actions for GitHub projects with native integration
Choose GitLab CI/CD for comprehensive DevOps platform
Choose CircleCI for performance-critical pipelines
Common Pitfalls and Solutions
Pitfall 1: Indentation Errors
Problem: YAML is indentation-sensitive
Solution: Use a YAML-aware editor with syntax highlighting and validation
1
2
3
4
5
6
7
8
9
# Wrong (mixed spaces and tabs)
services:
web:
image: nginx
# Correct (consistent 2-space indentation)
services:
web:
image: nginx
Pitfall 2: Unquoted Special Characters
Problem: Special characters can break YAML parsing
Solution: Quote strings with special characters
1
2
3
4
5
# Wrong
password: p@ssw0rd!123
# Correct
password: "p@ssw0rd!123"
Pitfall 3: Implicit Type Conversion
Problem: YAML auto-converts values to types
Solution: Quote values to preserve them as strings
1
2
3
4
5
6
7
8
# This becomes boolean true, not string
enabled: yes
# This stays as string "yes"
enabled: "yes"
# Version numbers should be quoted
version: "3.8" # Not 3.8 (float)
Pitfall 4: Duplica Keys
Problem: YAML allows duplicate keys (last wins)
Solution: Use linters to catch duplicates
1
2
3
4
5
6
7
8
9
10
# Wrong (duplicate key)
database:
host: localhost
port: 5432
host: db.example.com # This overwrites the first host
# Correct
database:
host: db.example.com
port: 5432
Pitfall 5: Large Files
Problem: YAML files become unmanageable when large
Solution: Split into multiple files and use includes/imports
1
2
3
4
5
# main.yml
include:
- services/web.yml
- services/database.yml
- services/cache.yml
Security Best Practices
1. Never Commit Secrets
Use secret management tools instead of hardcoding secrets:
1
2
3
4
5
6
7
8
9
# Wrong - secrets in plain text
database:
password: "MyP@ssw0rd123"
api_key: "sk_live_abc123def456"
# Correct - use secret management
database:
password: ${DATABASE_PASSWORD} # From environment
api_key: ${API_KEY} # From secret manager
2. Use Secret Management Tools
- Kubernetes Secrets for Kubernetes
- AWS Secrets Manager for AWS
- HashiCorp Vault for multi-cloud
- Azure Key Vault for Azure
- GCP Secret Manager for GCP
3. Encrypt Sensitive Files
Use tools like git-crypt or ansible-vault:
1
2
3
4
5
# Encrypt with ansible-vault
ansible-vault encrypt secrets.yml
# Use in playbook
ansible-playbook --ask-vault-pass playbook.yml
4. Implement RBAC
Define proper access controls:
1
2
3
4
5
6
7
8
9
# Kubernetes RBAC
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: pod-reader
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "list"]
5. Scan for Vulnerabilities
Use security scanning tools:
1
2
3
4
5
6
7
8
# Scan Kubernetes manifests
kubesec scan deployment.yml
# Scan Docker Compose files
docker-compose config --quiet && echo "Valid"
# Use Checkov for IaC scanning
checkov -f cloudformation.yml
Conclusion
YAML has become the lingua franca of Infrastructure as Code, offering a human-readable format that works across countless tools and platforms. While each tool has its strengths and use cases, they all benefit from YAML’s simplicity and declarative nature.
Key Takeaways:
- YAML’s readability makes it ideal for IaC
- Choose tools based on your specific use case (config management vs. orchestration vs. CI/CD)
- Follow best practices for indentation, validation, and organization
- Never commit secrets - use proper secret management
- Use linters and validators to catch errors early
- Split large files into modular components
- Document your configurations with comments
Whether you’re managing servers with Ansible, orchestrating containers with Kubernetes, or building CI/CD pipelines with GitHub Actions, mastering YAML is essential for modern DevOps practices.