DEV Community

POTHURAJU JAYAKRISHNA YADAV
POTHURAJU JAYAKRISHNA YADAV

Posted on

Learning Ansible Without Opening the Docs Website

How I used ansible-doc, local collection files, and a few terminal commands to
understand Ansible instead of blindly copying YAML.

I've been poking at Ansible lately, and somewhere along the way I made a small
decision that changed how I learn it: I stopped reaching for the browser.

Every time I didn't know how something worked, my reflex was to Google it, land on
the official docs, copy a snippet, move on. It worked, but I never really kept
anything. So this time I tried something different. I wanted to see how far I could
get using only what's already sitting on my machine. Turns out, pretty far.

Here's the trail I followed.

It started with a broken command

I ran a simple scaffolding command and got slapped with this:

ERROR! Unexpected Exception, this is probably a bug: module 'lib' has no attribute 'SSL_load_error_strings'
Enter fullscreen mode Exit fullscreen mode

Not exactly welcoming. The traceback pointed at pyOpenSSL calling into
cryptography, so I checked what versions were actually installed — making sure I
queried the same Python that runs Ansible:

python3 -m pip show pyOpenSSL cryptography
Enter fullscreen mode Exit fullscreen mode

The version check revealed a compatibility problem: an older pyOpenSSL was running
alongside a much newer cryptography package. The older library expected an OpenSSL
binding that was no longer available in that combination. This kind of drift is easy
to end up with when system packages and pip installs get mixed together.

Rather than modifying system-managed Python packages, I created a virtual
environment and installed Ansible in isolation:

python3 -m venv ~/.venvs/ansible
source ~/.venvs/ansible/bin/activate
pip install --upgrade pip ansible
Enter fullscreen mode Exit fullscreen mode

This gave me a cleaner, more predictable environment and avoided changing
dependencies the operating system relies on. One ansible --version later and I had
a modern ansible-core with everything matched up. Lesson one, re-learned the hard
way: when your Python environment is a mess, don't patch it, isolate it.

The command that does most of the work

The thing I kept coming back to is ansible-doc. It ships with Ansible, works
completely offline, and documents basically everything.

Want to know what you can do? List it all:

ansible-doc -l
Enter fullscreen mode Exit fullscreen mode

On my setup that printed thousands of available plugins and modules, because I'd
installed the full community bundle — the exact number depends on which Ansible
packages and collections you have installed. Searching through them is just grep:

ansible-doc -l | grep -i copy
Enter fullscreen mode Exit fullscreen mode

And to actually learn a module, you read its page:

ansible-doc copy
Enter fullscreen mode Exit fullscreen mode

The part I now rely on most is the EXAMPLES section that many module pages carry
near the bottom. That's where you see real, working YAML you can adapt. No website
needed. For a quick skeleton of just the options, there's a snippet mode too:

ansible-doc -s copy
Enter fullscreen mode Exit fullscreen mode

The confusion that finally made things click

Here's where I got stuck for a bit, and honestly it was the most useful stumble of
the whole thing.

I tried to read up on loop:

ansible-doc loop
Enter fullscreen mode Exit fullscreen mode
[WARNING]: loop was not found
Enter fullscreen mode Exit fullscreen mode

Not found? I use loops all the time. Then I tried tasks, when, same thing.
Nothing.

The penny dropped when I realised Ansible has a few distinct pieces, and I'd been
treating them as one. The mental model that helped me was simple:

  • Modules perform actions, such as copying files, installing packages, or managing services. These are what ansible-doc <name> looks up.
  • Module arguments specify what the module should do, such as the package name or the desired state.
  • Keywords control how Ansible organises and executes those actions, such as when, loop, become, and register. These live under a different flag.

So keywords need this:

ansible-doc -t keyword loop
ansible-doc -t keyword when
Enter fullscreen mode Exit fullscreen mode

And suddenly it told me exactly what I wanted:

loop:
  applies_to: [Task, Handler]
  description: Takes a list for the task to iterate over, saving each element into
    the item variable
  type: list
Enter fullscreen mode Exit fullscreen mode

Once I understood those three pieces — module, arguments, keywords — reading an
Ansible task became much easier. A task is a module invocation, with its arguments,
optionally wrapped in task-level keywords. Plays add their own play-level keywords on
top of that:

tasks:                          # play-level keyword
  - name: Install nginx         # task-level keyword
    ansible.builtin.package:    # module
      name: nginx               # module argument
      state: present            # module argument
    when: ansible_os_family == "Debian"   # task-level keyword
Enter fullscreen mode Exit fullscreen mode

Every line traces back to one of those categories. Once I saw that, I didn't feel
like I was guessing anymore.

A rough edge, and how I got past it

A couple of times ansible-doc just fell over on me:

ERROR! ... CLI.pager_pipe() takes 1 positional argument but 2 were given
Enter fullscreen mode Exit fullscreen mode

It was intermittent, which made it annoying to pin down. The fix that worked for me
was to force a plain pager for that invocation:

PAGER=cat ansible-doc -s copy
Enter fullscreen mode Exit fullscreen mode

That disables interactive paging for the command. I now just prefix everything with
PAGER=cat — it does nothing when it isn't needed and saves me when it is. If the
error shows up for you, it's worth checking your installed Ansible version and
whether it reproduces with your default pager, since the behaviour can depend on
both. If you'd rather not type it every time, bury it in an alias:

alias adoc='PAGER=cat ansible-doc'
Enter fullscreen mode Exit fullscreen mode

Keywords don't have examples, so I went looking for them

One frustration with keywords: their doc pages describe what they do but never show
them in use. There's no EXAMPLES section for when or loop.

But the collections I'd installed came with lots of real YAML files bundled right on
disk. So instead of searching the internet for "ansible when example", I searched my
own machine.

First, find where the collections actually live (the path varies by machine and
Python version, so don't hardcode it):

ansible-galaxy collection list
Enter fullscreen mode Exit fullscreen mode

That prints the collection paths. Then grep the relevant directory for the keyword
you want to see used — searching the whole tree, not just an examples/ folder,
since not every collection keeps one:

grep -rl "when:" <collections-path> --include="*.yml"
Enter fullscreen mode Exit fullscreen mode

That surfaced real examples and reusable patterns from the installed collections.
Worth knowing: these files can also include tests, fixtures, and other development
content, so not everything you find is a polished playbook. But plenty of it is
genuinely useful to read. Here's the kind of thing that came back:

- name: Add command completion
  ansible.builtin.include_tasks: command_completion.yml
  when: gcloud_command_completion
Enter fullscreen mode Exit fullscreen mode

Reading other people's real tasks, right there offline, taught me more about idiom
and style than any tutorial had.

Putting it together in one small playbook

All of this only clicked once I assembled the pieces into something that runs. Here's
a tiny playbook that installs nginx and makes sure it's running:

---
- name: Install and start nginx
  hosts: webservers
  become: true

  tasks:
    - name: Install nginx
      ansible.builtin.package:
        name: nginx
        state: present

    - name: Ensure nginx is running
      ansible.builtin.service:
        name: nginx
        state: started
        enabled: true
Enter fullscreen mode Exit fullscreen mode

Writing it, I leaned entirely on the offline docs to get the module arguments right:

ansible-doc ansible.builtin.package
ansible-doc ansible.builtin.service
Enter fullscreen mode Exit fullscreen mode

And before running anything, I checked the file:

ansible-playbook site.yml --syntax-check
Enter fullscreen mode Exit fullscreen mode

The one thing that playbook assumes is an inventory. hosts: webservers only means
something if a group called webservers exists, so I wrote a tiny inventory file to
go with it:

# inventory.ini
[webservers]
192.168.1.10
Enter fullscreen mode Exit fullscreen mode

I didn't have to guess the format, either — the INI inventory plugin documents
itself, examples included:

ansible-doc -t inventory ansible.builtin.ini
Enter fullscreen mode Exit fullscreen mode

Then I pointed the playbook at it:

ansible-playbook -i inventory.ini site.yml --syntax-check
Enter fullscreen mode Exit fullscreen mode

A couple of honest caveats here. --syntax-check only validates the playbook's
structure — it doesn't confirm the hosts are reachable or that the tasks will
actually succeed. It just catches the silly mistakes before you waste a real run. If
you want to see how Ansible reads your inventory back, ansible-inventory -i
inventory.ini --graph
prints the groups and hosts as a tree, which is a nice offline
sanity check before you run anything.

Where I've landed

After all this, my loop for learning any new piece of Ansible is pretty tight:

  1. Find the module: ansible-doc -l | grep -i <topic>
  2. Read its arguments and examples: ansible-doc <module>
  3. Look up any structure keywords I need: ansible-doc -t keyword <word>
  4. Borrow patterns from the bundled collection files with a quick grep
  5. Check my own file before running it: ansible-playbook file.yml --syntax-check

The whole thing happens in a terminal, nothing opens in a browser, and because I'm
reading and assembling instead of copy-pasting, it actually sticks.

I think that's the real takeaway for me. The docs were never the thing holding me
back. Slowing down enough to understand why a command is shaped the way it is, and
using the tools already in front of me, is what finally made Ansible feel less like
magic and more like something I can just write.

And the method isn't really about Ansible. Investigating the error instead of
pasting it into a search box, inspecting the references already on disk, reading real
examples, validating my own work — that same loop works just as well for Terraform,
Docker, or Kubernetes.

Still a lot to explore.

Top comments (0)