Complete reference for YAML — syntax, Python, Kubernetes, Docker Compose, Ansible playbooks, GitHub Actions, and comparison with JSON and TOML.
Part of The Direct Path to Linux Ubuntu — free for all.
YAML files use .yaml or .yml extension. Run Python examples with python3 script.py.
All YAML data types and structures in one file.
---
# YAML Cheatsheet — MyWebUniversity Chapter 34
# YAML is a superset of JSON — all JSON is valid YAML
# ── Scalars ──────────────────────────────────────────────────
string_bare: Hello World
string_quoted: "Hello, World!"
string_single: 'No interpolation: ${var}'
integer: 42
float: 3.14159
boolean_true: true
boolean_false: false
null_val: null
null_tilde: ~
date: 2026-06-25
datetime: 2026-06-25T08:00:00Z
# ── Multiline ─────────────────────────────────────────────────
literal_block: |
Line 1 preserved
Line 2 preserved
Trailing newline kept
folded_block: >
These lines are
folded into one space-separated
paragraph with a trailing newline.
no_newline: |-
Last line, no trailing newline
# ── Collections ───────────────────────────────────────────────
sequence:
- item one
- item two
- item three
inline_seq: [one, two, three]
mapping:
key1: value1
key2: value2
key3: value3
inline_map: {a: 1, b: 2, c: 3}
# ── Nested ────────────────────────────────────────────────────
students:
- name: Alice
score: 95
tags: [dean-list, scholarship]
address:
city: LA
state: CA
- name: Bob
score: 87
tags: []
address:
city: NYC
state: NY
# ── Anchors & aliases ─────────────────────────────────────────
defaults: &defaults
timeout: 30
retries: 3
log_level: info
development:
<<: *defaults # merge defaults
log_level: debug # override
production:
<<: *defaults
timeout: 60 # override
replicas: 3 # add new keyFull YAML file I/O with PyYAML.
import yaml
from pathlib import Path
# Write YAML config file
config = {
'app': {
'name': 'MyWebUniversity',
'version': '2.0',
'debug': False,
},
'server': {
'host': '0.0.0.0',
'port': 8080,
'workers': 4,
},
'database': {
'host': 'localhost',
'port': 5432,
'name': 'mywebuniversity',
'pool': {'min': 2, 'max': 10},
},
'features': ['search', 'analytics', 'notifications'],
'admins': [
{'name': 'Alice', 'email': 'alice@mywu.com', 'role': 'superadmin'},
{'name': 'Bob', 'email': 'bob@mywu.com', 'role': 'admin'},
],
}
yaml_str = yaml.dump(config, default_flow_style=False,
allow_unicode=True, sort_keys=False, indent=2)
Path('config.yaml').write_text(yaml_str)
print("=== Written config.yaml ===")
print(yaml_str)
# Read back
with open('config.yaml') as f:
loaded = yaml.safe_load(f)
print(f"App: {loaded['app']['name']} v{loaded['app']['version']}")
print(f"Server: {loaded['server']['host']}:{loaded['server']['port']}")
print(f"DB pool: {loaded['database']['pool']['min']}-{loaded['database']['pool']['max']}")
print(f"Features: {', '.join(loaded['features'])}")
# Merge with overrides
overrides = yaml.safe_load("""
server:
port: 9090
workers: 8
app:
debug: true
""")
def deep_merge(base, override):
result = dict(base)
for k, v in override.items():
if k in result and isinstance(result[k], dict) and isinstance(v, dict):
result[k] = deep_merge(result[k], v)
else:
result[k] = v
return result
merged = deep_merge(loaded, overrides)
print(f"\nAfter merge — port: {merged['server']['port']} debug: {merged['app']['debug']}")
Path('config.yaml').unlink()
# python3 yaml_io.py (pip install pyyaml)Complete Kubernetes Deployment, Service, and ConfigMap.
---
# kubernetes/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: mywebuniversity
namespace: default
labels:
app: mywebuniversity
version: "2.0"
spec:
replicas: 2
selector:
matchLabels:
app: mywebuniversity
template:
metadata:
labels:
app: mywebuniversity
spec:
containers:
- name: web
image: mywebuniversity/web:2.0
ports:
- containerPort: 80
envFrom:
- configMapRef:
name: app-config
resources:
requests: { cpu: "100m", memory: "128Mi" }
limits: { cpu: "500m", memory: "512Mi" }
livenessProbe:
httpGet:
path: /health
port: 80
initialDelaySeconds: 15
periodSeconds: 20
---
apiVersion: v1
kind: Service
metadata:
name: mywebuniversity-svc
spec:
selector:
app: mywebuniversity
ports:
- protocol: TCP
port: 80
targetPort: 80
type: ClusterIP
---
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
data:
NODE_ENV: production
LOG_LEVEL: info
PORT: "80"
SITE_URL: https://www.mywebuniversity.com
# Deploy:
# kubectl apply -f kubernetes/
# kubectl get pods
# kubectl rollout status deployment/mywebuniversity
# kubectl scale deployment mywebuniversity --replicas=5Multi-service Docker Compose with health checks.
# docker-compose.yml — MyWebUniversity full stack
# Usage: docker compose up -d
services:
# ── Web / CGI server ───────────────────────────────────────
web:
image: ubuntu/apache2:latest
container_name: mywebuniversity_web
ports:
- "80:80"
- "443:443"
volumes:
- ./html:/var/www/React/prod:ro
- ./cgi-bin:/usr/lib/cgi-bin:ro
- ./ssl:/etc/ssl/mywebuniversity:ro
- ./apache2.conf:/etc/apache2/sites-enabled/000-default.conf:ro
environment:
APACHE_RUN_USER: www-data
APACHE_RUN_GROUP: www-data
depends_on:
db:
condition: service_healthy
restart: unless-stopped
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 10s
# ── Database ────────────────────────────────────────────────
db:
image: postgres:16-alpine
container_name: mywebuniversity_db
environment:
POSTGRES_DB: mywebuniversity
POSTGRES_USER: ${DB_USER:-admin}
POSTGRES_PASSWORD: ${DB_PASSWORD:?DB_PASSWORD required}
volumes:
- postgres_data:/var/lib/postgresql/data
- ./sql/init.sql:/docker-entrypoint-initdb.d/01_init.sql:ro
ports:
- "127.0.0.1:5432:5432" # localhost only — not public
restart: unless-stopped
healthcheck:
test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"]
interval: 10s
timeout: 5s
retries: 5
# ── Cache ───────────────────────────────────────────────────
cache:
image: redis:7-alpine
container_name: mywebuniversity_cache
command: >
redis-server
--maxmemory 256mb
--maxmemory-policy allkeys-lru
--requirepass ${REDIS_PASSWORD:-changeme}
volumes:
- redis_data:/data
restart: unless-stopped
# ── Database admin ──────────────────────────────────────────
pgadmin:
image: dpage/pgadmin4:latest
environment:
PGADMIN_DEFAULT_EMAIL: admin@mywebuniversity.com
PGADMIN_DEFAULT_PASSWORD: ${PGADMIN_PASSWORD:-admin}
ports:
- "127.0.0.1:5050:80"
depends_on:
- db
profiles:
- tools # only starts with: docker compose --profile tools up
volumes:
postgres_data:
redis_data:
networks:
default:
name: mywebuniversity_net
# .env file (never commit to git):
# DB_PASSWORD=supersecretpassword
# REDIS_PASSWORD=redispassword
# PGADMIN_PASSWORD=pgadminpasswordComplete CI/CD workflow with tests and deployment.
# .github/workflows/deploy.yml
name: Test, Build & Deploy
on:
push:
branches: [main]
paths:
- 'src/**'
- 'cgi-bin/**'
- '.github/workflows/**'
pull_request:
branches: [main]
workflow_dispatch:
inputs:
environment:
description: 'Target environment'
required: true
default: 'staging'
type: choice
options: [staging, production]
env:
PYTHON_VERSION: '3.12'
NODE_VERSION: '20'
jobs:
# ── Lint & Test ─────────────────────────────────────────────
test:
name: Lint & Test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: ${{ env.PYTHON_VERSION }}
cache: pip
- name: Install dependencies
run: |
pip install flake8 pytest pytest-cov pyyaml lxml
pip install -r requirements.txt 2>/dev/null || true
- name: Lint CGI scripts
run: |
flake8 cgi-bin/**/*.cgi --max-line-length=120 --ignore=E501,W503 || true
- name: Validate YAML files
run: |
python3 -c "
import yaml, pathlib, sys
errors = []
for f in pathlib.Path('.').rglob('*.yaml'):
if '.git' in str(f): continue
try:
yaml.safe_load(f.read_text())
print(f' ✅ {f}')
except yaml.YAMLError as e:
errors.append(f' ❌ {f}: {e}')
if errors:
for e in errors: print(e)
sys.exit(1)
"
- name: Run tests
run: |
python3 -m pytest tests/ -v --cov=cgi-bin --cov-report=xml --cov-report=term-missing --tb=short 2>/dev/null || echo "No tests found"
- name: Upload coverage
uses: codecov/codecov-action@v4
if: always()
with:
token: ${{ secrets.CODECOV_TOKEN }}
# ── Deploy ──────────────────────────────────────────────────
deploy:
name: Deploy to ${{ inputs.environment || 'production' }}
needs: test
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
environment:
name: production
url: https://www.mywebuniversity.com
steps:
- uses: actions/checkout@v4
- name: Deploy CGI scripts
env:
SSH_PRIVATE_KEY: ${{ secrets.DEPLOY_SSH_KEY }}
DEPLOY_HOST: ${{ secrets.DEPLOY_HOST }}
DEPLOY_USER: ${{ secrets.DEPLOY_USER }}
run: |
echo "$SSH_PRIVATE_KEY" > /tmp/deploy_key
chmod 600 /tmp/deploy_key
rsync -avz --delete -e "ssh -i /tmp/deploy_key -o StrictHostKeyChecking=no" cgi-bin/ $DEPLOY_USER@$DEPLOY_HOST:/usr/lib/cgi-bin/
rsync -avz --delete -e "ssh -i /tmp/deploy_key -o StrictHostKeyChecking=no" html/ $DEPLOY_USER@$DEPLOY_HOST:/var/www/React/prod/
- name: Notify deployment
run: |
echo "✅ Deployed to production at $(date -u)"
echo " Commit: ${{ github.sha }}"
echo " Author: ${{ github.actor }}"
- name: Smoke test
run: |
sleep 5
curl -sf https://www.mywebuniversity.com/health || exit 1
echo "✅ Health check passed"