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

jq '.name'

yq '.name'

Array index

jq '.[0]'

yq '.[0]'

Update in-place

N/A (use sponge)

yq -i '.name = "x"'

Iterate

jq '.[]'

yq '.[]'

Select/filter

jq 'select(.x == 1)'

yq 'select(.x == 1)'

Multi-doc

N/A

yq ea (evaluate all)

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

  1. yq -i for in-place - Modifies file directly

  2. yq -o json - Convert to JSON for jq

  3. yq ea - Handle multi-document YAML

  4. Same syntax as jq - Most jq knowledge transfers

  5. Preserves formatting - Comments and structure kept

  6. env() - Use environment variables safely