AnsibleXXV · Check Mode, Diff and Static ValidationCheck mode, diff and static validation
--diff and what it prints
What you'll learn
- Read a --diff output and say precisely what it establishes
- Choose between diff: false and no_log: true for a task that renders a secret
- Name every destination a diff reaches beyond the operator terminal
- Configure diff behaviour deliberately rather than inheriting it
Prerequisites
Verified against ansible-core 2.21.x · ansible (community package) 14.x · Python (controller) 3.12+ · ansible-lint 26.x · Molecule 26.x · Ubuntu 24.04 LTS · Debian 12 (Bookworm) · RHEL / Rocky / AlmaLinux 9.x · 2026-08-11
--check tells you three files would change. --diff tells you which
three lines, in which files.
That is a much larger difference than it sounds, and it is the single most useful artefact this whole part produces. A reviewer cannot audit a change count. They can audit a diff, in the same way they audit a pull request — and unlike a pull request, this diff is rendered against the actual current content of the actual target host, with the actual variables that host will get.
It also prints file contents to standard output, which is where the rest of this lesson comes from.
What a diff establishes
$ ansible-playbook -i inv.ini render.yml --check --diff --limit web01.example.comTASK [Render the application config] *******************************************
--- before
+++ after: /etc/app/app.conf
@@ -0,0 +1,4 @@
+[database]
+host = db01.example.com
+user = appuser
+password = REPLACE_ME
changed: [web01.example.com]The --- before with no path and @@ -0,0 mean the file does not exist
yet; an existing file shows both paths and the changed hunks only.
What that output proves, precisely: the content this task would write differs from the content that is there, in these ways. It is a statement about the file. Whether the resulting configuration is correct, whether the service parses it, whether the value is the one you meant — none of that is in scope, and the previous lessons cover why.
What makes it worth running anyway is that most configuration mistakes are visible in the text. A port that should be 8443 and reads 8080, a hostname pointing at the staging database, an option removed by a template refactor that nobody noticed: all of them are in the diff, and none of them are in a change count.
The disclosure
Look at the last line of that output again.
--diff renders the file. Rendering a template resolves every variable
in it, including the ones that came from a vault, an external secret
store, or a lookup — because the whole point of rendering is to produce
the final content. The final content contains the credential.
The two suppressions, and how they differ
Both exist. They are not interchangeable.
$ ansible-playbook -i localhost, secrets.yml --check --diff -vTASK [Plain, diff shown] *******************************************************
--- before
+++ after: /etc/app/a.conf
@@ -0,0 +1 @@
+password = REPLACE_ME
changed: [localhost] => {"changed": true}
TASK [diff false] **************************************************************
changed: [localhost] => {"changed": true}
TASK [no_log true] *************************************************************
changed: [localhost] => {"censored": "the output has been hidden due to the fact that 'no_log: true' was specified for this result", "changed": true}diff: false | no_log: true | |
|---|---|---|
| Suppresses the diff | Yes | Yes |
| Suppresses the rest of the task result | No | Yes, replaced with a censored message |
Suppresses the task arguments at -vvv | No | Yes |
| You can still see whether the task changed something | Yes | Yes — changed: true survives |
| Suppresses the reason a failure occurred | No | Yes, which is its cost |
- name: Render the application configuration
ansible.builtin.template:
src: app.conf.j2
dest: /etc/app/app.conf
mode: '0640'
diff: false
no_log: true
Use diff: false when the file contains a secret and you still want
ordinary diagnostics. The task reports success, failure, and its error
message normally; only the content is withheld.
Use no_log: true when the task arguments also carry the secret.
A copy with content: "{{ db_password }}", a uri with a bearer
token in headers, a command with a password in argv — all of those
put the secret in the module invocation, where -vvv and a failure
traceback will print it regardless of diff: false.
Most template tasks that render a credential want both: diff: false
because the rendered file has it, no_log: true because a failure dumps
module arguments.
One host at a time
The documentation is explicit and the reason is practical:
Diff mode produces a large amount of output, so it is best used when checking a single host at a time.
ansible-playbook foo.yml --check --diff --limit foo.example.com
Two hundred hosts producing a forty-line diff each is eight thousand lines, and the one host whose diff is different from the other hundred-and-ninety-nine is invisible in it. One host is readable, and if the fleet is homogeneous it is also representative — which is what makes the canary the natural next step after it.
Where the fleet is not homogeneous, diff one host per meaningful class rather than one host in total. The classes are usually obvious from the inventory: one per environment, one per distribution version, one per role.
Knowledge check
Knowledge check · 4 questions
Q1. A template task renders a config containing a vaulted database password, and the play is run with --check --diff in CI. What has happened?
Q2. A copy task uses content: "{{ db_password }}". Which statements are true? Select all that apply.
Q3. Setting always = true in the [diff] section of ansible.cfg is a safe default for a shared controller.
Q4. Why does the documentation recommend --diff with --limit to a single host?
Passing score: 75%. Answers are checked in this browser.