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.

  • Linux
  • YAML
  • DevOps