yq for YAML
yq (Mike Farah’s Go version) brings jq-like syntax to YAML processing. Essential for Kubernetes manifests, Ansible playbooks, and any YAML configuration.
Installation
# Arch Linux
pacman -S yq
# Ubuntu/Debian
wget https://github.com/mikefarah/yq/releases/latest/download/yq_linux_amd64 -O /usr/local/bin/yq
chmod +x /usr/local/bin/yq
# macOS
brew install yq
# Verify
yq --version
| This guide covers Mike Farah’s yq (Go version), not the Python yq wrapper. Syntax differs significantly. |
Basic Operations
Read YAML
# Pretty print
yq . file.yaml
# Read field
yq '.metadata.name' file.yaml
# Read nested
yq '.spec.containers[0].image' file.yaml
Write/Update
# Update field
yq -i '.metadata.name = "new-name"' file.yaml
# Add field
yq -i '.metadata.labels.env = "production"' file.yaml
# Delete field
yq -i 'del(.metadata.annotations)' file.yaml
Convert Formats
# YAML to JSON
yq -o json file.yaml
# JSON to YAML
yq -P file.json
# YAML to props
yq -o props file.yaml
Syntax Comparison: jq vs yq
| Operation | jq | yq |
|---|---|---|
Read field |
|
|
Array index |
|
|
Update in-place |
N/A (use sponge) |
|
Iterate |
|
|
Select/filter |
|
|
Multi-doc |
N/A |
|
Kubernetes Manifests
Read Resources
# Get resource name
yq '.metadata.name' deployment.yaml
# Get container images
yq '.spec.template.spec.containers[].image' deployment.yaml
# Get all labels
yq '.metadata.labels' deployment.yaml
Update Resources
# Change image
yq -i '.spec.template.spec.containers[0].image = "nginx:1.25"' deployment.yaml
# Add label
yq -i '.metadata.labels.version = "v2"' deployment.yaml
# Update replicas
yq -i '.spec.replicas = 3' deployment.yaml
# Add environment variable
yq -i '.spec.template.spec.containers[0].env += [{"name": "DEBUG", "value": "true"}]' deployment.yaml
Multi-Document YAML
Kubernetes manifests often contain multiple resources separated by ---.
# Read all documents
yq ea '.' multi-doc.yaml
# Select specific document
yq 'select(documentIndex == 0)' multi-doc.yaml
# Select by kind
yq 'select(.kind == "Deployment")' multi-doc.yaml
# Update across all documents
yq -i 'select(.kind == "Deployment") | .spec.replicas = 3' multi-doc.yaml
Extract from kubectl
# Get deployment as YAML
kubectl get deployment myapp -o yaml | yq '.spec.template.spec.containers[0].image'
# All images in namespace
kubectl get pods -o yaml | yq '.items[].spec.containers[].image'
# Resources with specific label
kubectl get all -o yaml | yq 'select(.metadata.labels.app == "myapp")'
Ansible Processing
Read Playbooks
# List all task names
yq '.[].tasks[].name' playbook.yaml
# Get variables
yq '.vars' playbook.yaml
# Find handlers
yq '.[].handlers[].name' playbook.yaml
Update Playbooks
# Change become setting
yq -i '.[0].become = true' playbook.yaml
# Add variable
yq -i '.[0].vars.new_var = "value"' playbook.yaml
Inventory
# Read hosts
yq '.all.children.webservers.hosts' inventory.yaml
# Add host
yq -i '.all.children.webservers.hosts.web-03 = null' inventory.yaml
# Get host vars
yq '.all.children.webservers.hosts.web-01' inventory.yaml
Helm Values
Read Values
# Get image
yq '.image.repository' values.yaml
# Get all exposed ports
yq '.service.ports[].port' values.yaml
# Get resources
yq '.resources' values.yaml
Override Values
# Change image tag
yq -i '.image.tag = "v2.0.0"' values.yaml
# Enable ingress
yq -i '.ingress.enabled = true' values.yaml
# Set resource limits
yq -i '.resources.limits.memory = "512Mi"' values.yaml
Merge Values
# Merge two values files (right wins)
yq ea 'select(fi == 0) * select(fi == 1)' values.yaml values-prod.yaml
# Merge and output
yq ea '. as $item ireduce ({}; . * $item)' base.yaml override.yaml
Antora Configuration
Read antora.yml
# Get component name
yq '.name' docs/antora.yml
# Get all attributes
yq '.asciidoc.attributes' docs/antora.yml
# List attribute keys
yq '.asciidoc.attributes | keys' docs/antora.yml
Update Attributes
# Add attribute
yq -i '.asciidoc.attributes.new-attr = "value"' docs/antora.yml
# Update version
yq -i '.version = "2.0"' docs/antora.yml
Playbook Processing
# List all content sources
yq '.content.sources[].url' antora-playbook.yml
# Get UI bundle
yq '.ui.bundle.url' antora-playbook.yml
# Add extension
yq -i '.antora.extensions += ["@antora/lunr-extension"]' antora-playbook.yml
Advanced Operations
Conditional Updates
# Update only if field exists
yq 'select(has("metadata")) | .metadata.labels.updated = "true"' file.yaml
# Update based on condition
yq '(.spec.containers[] | select(.name == "app")).image = "new:tag"' deployment.yaml
Variables in yq
# Use environment variable
IMAGE="nginx:1.25"
yq -i ".spec.template.spec.containers[0].image = \"$IMAGE\"" deployment.yaml
# Use yq variable
yq '.items[] | .metadata.name as $name | {name: $name, kind: .kind}' resources.yaml
Comments
yq preserves YAML comments:
# Add comment
yq -i '.metadata.name line_comment="This is the app name"' file.yaml
# Read with comments
yq '... comments=""' file.yaml
Anchors & Aliases
# sample.yaml
defaults: &defaults
timeout: 30
retries: 3
production:
<<: *defaults
timeout: 60
# Expand aliases
yq 'explode(.)' sample.yaml
Practical Patterns
Batch Update Deployments
# Update all deployments in directory
for f in manifests/*.yaml; do
yq -i 'select(.kind == "Deployment") | .spec.replicas = 3' "$f"
done
Generate from Template
# template.yaml
# apiVersion: v1
# kind: ConfigMap
# metadata:
# name: PLACEHOLDER
# data: {}
# Generate multiple
for name in config-a config-b config-c; do
yq ".metadata.name = \"$name\"" template.yaml > "${name}.yaml"
done
Validate Structure
# Check required fields
yq '
if has("metadata") and has("spec") then
"valid"
else
"invalid: missing " + (["metadata","spec"] - keys | join(", "))
end
' deployment.yaml
Diff YAML Files
# Compare two files
diff <(yq -o json file1.yaml | jq -S .) <(yq -o json file2.yaml | jq -S .)
Extract to Multiple Files
# Split multi-doc YAML by kind
yq -s '.kind + "-" + .metadata.name' multi-doc.yaml
yq vs jq Decision Tree
Is input YAML?
├── Yes → Use yq
│ ├── Need in-place edit? → yq -i
│ ├── Multi-document? → yq ea
│ └── Convert to JSON? → yq -o json
└── No (JSON)
├── Simple transform? → jq
└── Need YAML output? → jq | yq -P
Common Gotchas
Quoting
# Shell variable in single quotes WON'T work
yq '.name = "$NAME"' # Literal $NAME
# Use double quotes for variables
yq ".name = \"$NAME\""
# Or use env()
NAME=myapp yq '.name = env(NAME)' file.yaml
Boolean vs String
# YAML treats unquoted true/false as boolean
yq '.enabled = true' # Boolean true
yq '.enabled = "true"' # String "true"
Null vs Empty
# Null value
yq '.field = null'
# Empty string
yq '.field = ""'
# Delete field
yq 'del(.field)'
Integration with kubectl
# Dry-run apply, capture diff
kubectl apply -f deployment.yaml --dry-run=client -o yaml | yq '.metadata.annotations'
# Patch via yq
kubectl get deployment myapp -o yaml | \
yq '.spec.replicas = 5' | \
kubectl apply -f -
# Extract secrets (base64 decoded)
kubectl get secret mysecret -o yaml | yq '.data | map_values(@base64d)'
Quick Reference
# Read
yq '.field' # Get field
yq '.array[0]' # Array index
yq '.array[]' # Iterate
yq '.nested.field' # Nested access
# Write
yq -i '.field = "value"' # Update
yq -i '.new = "value"' # Add
yq -i 'del(.field)' # Delete
# Convert
yq -o json # To JSON
yq -P # From JSON to YAML
# Multi-doc
yq ea '.' # Evaluate all
yq 'select(.kind == "X")' # Filter docs
# Merge
yq ea '. as $item ireduce ({}; . * $item)'
Key Takeaways
-
yq -i for in-place - Modifies file directly
-
yq -o json - Convert to JSON for jq
-
yq ea - Handle multi-document YAML
-
Same syntax as jq - Most jq knowledge transfers
-
Preserves formatting - Comments and structure kept
-
env() - Use environment variables safely