YAML is everywhere in server work: Docker Compose files, Kubernetes manifests, Ansible playbooks, GitHub Actions, netplan, and many app configs. yq lets you read and edit YAML from the command line with jq-like expressions, which makes it ideal for scripts and CI pipelines, and much safer than editing YAML with sed.
There are two different tools called yq. This guide uses the popular Go version by Mike Farah (v4). The older Python yq (a jq wrapper) has different syntax.
Install
sudo wget -qO /usr/local/bin/yq https://github.com/mikefarah/yq/releases/latest/download/yq_linux_amd64
sudo chmod +x /usr/local/bin/yq
yq --version
(Snap: sudo snap install yq. Check the repository’s releases page for other architectures.)
Read values
Given compose.yaml:
services:
web:
image: nginx:1.27
ports:
- "80:80"
db:
image: postgres:16
environment:
POSTGRES_DB: app
yq '.services.web.image' compose.yaml # nginx:1.27
yq '.services | keys' compose.yaml # [web, db]
yq '.services[].image' compose.yaml # all images
yq '.services.db.environment.POSTGRES_DB' compose.yaml
yq -o=json '.' compose.yaml # convert to JSON
Edit files in place
yq -i '.services.web.image = "nginx:1.28"' compose.yaml
yq -i '.services.web.ports += ["443:443"]' compose.yaml
yq -i '.services.db.environment.TZ = "UTC"' compose.yaml
yq -i 'del(.services.web.ports[0])' compose.yaml
-i edits in place, and keeps comments and most formatting. Keep the file in Git, so you can review the diff.
Use shell variables safely
TAG=1.28.0
yq -i '.services.web.image = "nginx:" + strenv(TAG)' compose.yaml
export IMAGE=registry.example.com/app:$GIT_SHA
yq -i '(.spec.template.spec.containers[] | select(.name == "app")).image = strenv(IMAGE)' deploy.yaml
strenv() reads environment variables without quoting problems. It’s typical in CI/CD deploys. See CI/CD pipeline explained.
Filter and select
yq '.services | to_entries | .[] | select(.value.image | test("postgres")) | .key' compose.yaml
yq 'select(.kind == "Deployment") | .metadata.name' k8s/*.yaml # multi-document files
yq '.spec.replicas' deploy.yaml
Kubernetes manifests often contain several documents separated by ---. yq processes each one.
Merge files
yq eval-all '. as $item ireduce ({}; . * $item)' base.yaml override.yaml > merged.yaml
* deep-merges maps. Later files override earlier ones. That’s useful for environment-specific config.
Convert formats
yq -p=json -o=yaml '.' data.json # JSON → YAML
yq -o=json '.' config.yaml | jq '.x' # YAML → JSON, then jq
yq -p=xml -o=yaml '.' file.xml # XML → YAML
yq -o=props '.' application.yaml # → properties
See jq tutorial.
Validate YAML
yq '.' file.yaml > /dev/null && echo valid
Common YAML pitfalls that yq exposes quickly: tabs instead of spaces, unquoted values like yes/no/on turning into booleans, and 08 being parsed as a string or an octal-like value. Quote values when in doubt.
For Docker Compose, docker compose config also validates and shows the final merged config. See Docker Compose tutorial.
yq in CI pipelines
A typical deployment step updates an image tag in a Compose file or Kubernetes manifest, commits it, and lets the server pull the change. Because yq parses the YAML properly, the edit can’t accidentally match the wrong line, as a sed replacement might. Pin the yq version in CI (download a specific release rather than “latest”), so expression behaviour doesn’t change under you between runs, and print the resulting diff in the job log for review.
Quick reference
| Task | Command |
|---|---|
| Read | yq '.a.b' file.yaml |
| Edit in place | yq -i '.a.b = "x"' file.yaml |
| Append to a list | yq -i '.list += ["x"]' file.yaml |
| Delete | yq -i 'del(.a.b)' file.yaml |
| Env variable | strenv(VAR) |
| To JSON | yq -o=json '.' file.yaml |
Related: kubectl cheat sheet and Ansible playbooks.