Skip to main content
In this lesson you will use the Claude Code For Beginners CLI to generate and refine an Ansible playbook that installs and configures the Apache (httpd) web server on RHEL-based hosts. The walkthrough covers setting up a control-node workspace, generating a baseline playbook with Claude Code, refactoring to best practices (FQCNs, templates, lineinfile, handlers), validating, and running the playbook.
A presentation slide titled "Writing Playbook With Claude Code CLI" showing an illustration of a person at a computer. The slide notes testing Claude Code CLI’s ability to generate an Ansible playbook for Apache (httpd) setup.
Overview — workflow
  • Create a working directory and inventory
  • Configure ansible.cfg
  • Use Claude Code to generate site.yml (the playbook)
  • Validate and refactor the playbook (use FQCNs, add template, lineinfile, handlers)
  • Run ansible-lint / validate
  • Execute the playbook and verify the result
For quick reference, here’s the workflow in a compact table:
StepActionWhy
1Create workspace + inventoryTell Ansible which hosts to manage
2Configure ansible.cfgEnsure consistent behaviour and privilege escalation
3Generate site.yml via Claude CodeQuickly scaffold a working playbook
4Refactor (FQCNs, template, lineinfile)Improve clarity, maintainability, and idempotence
5Lint/validateCatch issues early with ansible-lint
6Run playbook and verifyConfirm the web server is configured correctly
A dark-themed slide titled "Demo" listing six numbered steps. It outlines setting up a working directory, configuring the Claude Code CLI, generating and validating a playbook (site.yml), asking Claude to refactor it, and validating/executing the refactored playbook.
Preparation — create the control-node workspace Run these commands in your Claude Code workspace to create a working directory and files:
student@control:~/claude$ pwd
/home/student/claude
student@control:~/claude$ vim inventory
student@control:~/claude$ vim ansible.cfg
student@control:~/claude$ claude
Example inventory (inventory)
[webservers]
servera
Example ansible.cfg
[defaults]
inventory = inventory

[privilege_escalation]
become = true
become_user = root
become_method = sudo
become_ask_pass = false
Generate an initial playbook with Claude Code Start an interactive Claude Code session and ask it to create a playbook named site.yml that installs httpd on hosts in the webservers group (RHEL-based systems). Example CLI snippet:
student@control:~/claude$ claude

Claude Code v2.0.37

Welcome back andrei!
/home/student/claude

> Create an ansible playbook file called site.yml which installs httpd on the group of hosts called webservers which are RHEL-based systems.
Claude Code will typically generate a simple playbook like this. Open site.yml in your editor to inspect it. Initial generated site.yml
---
- name: Install and configure httpd on webservers
  hosts: webservers
  become: yes

  tasks:
    - name: Install httpd package
      yum:
        name: httpd
        state: present

    - name: Start and enable httpd service
      service:
        name: httpd
        state: started
        enabled: yes
Refactor to use Fully Qualified Collection Names (FQCNs) Best practice: use FQCNs such as ansible.builtin.yum and ansible.builtin.service to avoid ambiguity and ensure the intended module is executed. Ask Claude Code to refactor the playbook to FQCNs or update it yourself. site.yml with FQCNs
---
- name: Install and configure httpd on webservers
  hosts: webservers
  become: yes

  tasks:
    - name: Install httpd package
      ansible.builtin.yum:
        name: httpd
        state: present

    - name: Start and enable httpd service
      ansible.builtin.service:
        name: httpd
        state: started
        enabled: yes
Add a Jinja2 template to serve host facts Create a template that renders host-specific facts (hostname and fqdn) to /var/www/html/index.html. Example template (index.html.j2): index.html.j2
<!DOCTYPE html>
<html>
<head>
    <title>Welcome</title>
</head>
<body>
    <h1>Welcome to {{ ansible_hostname }}</h1>
    <p>This page is served from host: {{ ansible_fqdn }}</p>
</body>
</html>
Deploy the template and use lineinfile for attribution Rather than embedding the attribution directly into the template, demonstrate combining modules: deploy the template using ansible.builtin.template, then add an attribution line using ansible.builtin.lineinfile. Notify a handler to reload httpd when the file changes. Tasks to add
- name: Deploy index.html from template
  ansible.builtin.template:
    src: index.html.j2
    dest: /var/www/html/index.html
    owner: root
    group: root
    mode: '0644'
  notify: Reload httpd

- name: Add attribution line to index.html
  ansible.builtin.lineinfile:
    path: /var/www/html/index.html
    line: "This was created by ansible and claude"
    insertbefore: '</body>'
  notify: Reload httpd
Add the handler
handlers:
  - name: Reload httpd
    ansible.builtin.service:
      name: httpd
      state: reloaded
Consolidated site.yml Below is the full playbook that combines installation, service management, template deployment, line insertion, and the handler.
---
- name: Install and configure httpd on webservers
  hosts: webservers
  become: yes

  tasks:
    - name: Install httpd package
      ansible.builtin.yum:
        name: httpd
        state: present

    - name: Start and enable httpd service
      ansible.builtin.service:
        name: httpd
        state: started
        enabled: yes

    - name: Deploy index.html from template
      ansible.builtin.template:
        src: index.html.j2
        dest: /var/www/html/index.html
        owner: root
        group: root
        mode: '0644'
      notify: Reload httpd

    - name: Add attribution line to index.html
      ansible.builtin.lineinfile:
        path: /var/www/html/index.html
        line: "This was created by ansible and claude"
        insertbefore: '</body>'
      notify: Reload httpd

  handlers:
    - name: Reload httpd
      ansible.builtin.service:
        name: httpd
        state: reloaded
Linting and validation Before running the playbook in production, consider running ansible-lint to catch common issues:
ansible-lint site.yml
Execute the playbook Run the playbook from your control node:
student@control:~/claude$ ansible-playbook /home/student/claude/site.yml
Example playbook execution (trimmed)
PLAY [Install and configure httpd on webservers] ********************************

TASK [Gathering Facts] *********************************************************
ok: [servera]

TASK [Install httpd package] ***************************************************
changed: [servera]

TASK [Start and enable httpd service] ******************************************
changed: [servera]

TASK [Deploy index.html from template] *****************************************
changed: [servera]

TASK [Add attribution line to index.html] **************************************
changed: [servera]

RUNNING HANDLER [Reload httpd] *************************************************
changed: [servera]
Verify on the managed host Confirm the web page is served and contains the expected facts and attribution.
student@servera:~$ curl localhost:80
<!DOCTYPE html>
<html>
<head>
    <title>Welcome</title>

</head>
<body>
    <h1>Welcome to servera</h1>
    <p>This page is served from host: servera</p>
This was created by ansible and claude
</body>
</html>
student@servera:~$
Wrapping up You have used Claude Code For Beginners to scaffold an Ansible playbook, refactored it to follow best practices (FQCNs, templates, handlers), and executed it against a managed RHEL-based host. This pattern—generate, inspect, refactor, and validate—helps you move quickly while keeping playbooks maintainable and predictable. Links and references
Use FQCNs (ansible.builtin.*) in playbooks to avoid ambiguity and ensure the intended module is executed.

Watch Video