How to Write Ansible Playbooks for Fedora System Configuration

Ansible playbooks let you automate Fedora system configuration tasks — from installing packages to managing services and users — using simple, repeatable YAML files.

You just finished a fresh Fedora install

You spent the last hour installing git, configuring firewalld, creating a user, and tweaking SSH settings. Now you need to do the exact same thing on a second machine, or you are terrified you will break the first one and won't remember how you fixed it. Ansible turns that manual drudgery into a repeatable script. You write the desired state once, and the tool enforces it everywhere.

How Ansible enforces state

Ansible connects over SSH, runs a small Python script, checks the current state, and makes changes only if the state does not match what you asked for. Think of it like a checklist inspector. If the inspector sees the package is already installed, they mark it "ok" and move on. If the package is missing, they install it. This property is called idempotency. Running the playbook ten times produces the same result as running it once, without side effects.

Ansible does not install an agent on the target machine. It relies on SSH and Python, both of which ship with Fedora. This keeps the target system clean and reduces the attack surface.

Install the automation engine

Install the package from the default repositories. Fedora ships a recent version of Ansible that integrates cleanly with the system Python. The ansible-core package provides the engine and essential modules. Extra functionality comes from collections installed separately.

sudo dnf install ansible-core
# ansible-core provides the engine and core modules.
# The full 'ansible' package pulls in extra collections, but core is the stable base.
# Fedora updates ansible-core via regular dnf upgrade cycles.

Install ansible-core first. Collections fill in the gaps later.

Write a minimal playbook

A playbook is a YAML file that lists tasks. Each task calls a module to enforce a specific state. Here is a minimal playbook that configures a Fedora workstation with development tools and a user account.

---
- name: Configure Fedora workstation
  hosts: localhost
  become: true
  # become: true runs tasks as root via sudo.
  # Most system changes require elevated privileges.

  tasks:
    - name: Install development packages
      ansible.builtin.dnf:
        name:
          - git
          - vim
          - htop
          - tmux
        state: present
        # state: present ensures packages are installed.
        # Ansible skips this task if all packages are already at the latest version.

    - name: Enable and start sshd
      ansible.builtin.systemd:
        name: sshd
        enabled: true
        state: started
        # enabled: true adds the service to the boot target.
        # state: started launches the service immediately if not running.

    - name: Create a developer user
      ansible.builtin.user:
        name: devuser
        groups: wheel
        shell: /bin/bash
        create_home: true
        # groups: wheel grants sudo access on Fedora.
        # Fedora uses the wheel group for privilege escalation, not sudo.

Use ansible.builtin.dnf for packages. The module handles dependencies and conflicts automatically.

Run playbooks locally and remotely

You can test playbooks on the machine you are sitting at before pushing them to a server. This avoids SSH key issues and network latency during development.

ansible-playbook -i localhost, -c local fedora-setup.yml
# -i localhost, defines a single-host inventory for the current machine.
# -c local forces the connection plugin to run commands directly without SSH.
# Local connection is faster for debugging syntax and logic errors.

For remote management, define your targets in an inventory file. This separates host definitions from the playbook logic.

[fedora_servers]
192.168.1.10
192.168.1.11
# Groups allow you to target subsets of machines in playbooks.
# The 'all' group implicitly contains every host defined in the file.
# Hostnames resolve via DNS or /etc/hosts on the control machine.

Run the playbook against the inventory to configure remote hosts.

ansible-playbook -i inventory.ini fedora-setup.yml --ask-become-pass
# --ask-become-pass prompts for the sudo password on remote hosts.
# Configure SSH keys to avoid typing the host password on every run.
# The inventory file must be readable by the user running the command.

Test locally with -c local before touching production hosts. A syntax error in YAML stops the run immediately.

Verify the result

Ansible reports the state of every task. Look for changed or ok. A changed result means the system state was modified. An ok result means the desired state was already achieved.

TASK [Install development packages]
ok: [localhost]
# The packages were already installed. No action was taken.

TASK [Enable and start sshd]
changed: [localhost]
# The service was enabled and started. The system state was modified.

Read the output color. Green means changed, yellow means ok. Red means failure.

Handle Fedora-specific requirements

Fedora enables SELinux by default. Ansible tasks that modify files or ports may trigger denials if the context is wrong. Check the logs before disabling security policies. The control machine running Ansible also needs SELinux Python libraries to communicate correctly with SELinux-enabled targets.

fatal: [localhost]: FAILED! => {"msg": "Aborting, target uses selinux but python bindings (libselinux-python) aren't installed!"}
# This error appears when the control machine lacks SELinux Python libraries.
# Install 'libselinux-python' on the machine running Ansible, not the target.
# The error stops the playbook before any tasks execute on the target.

When managing firewalls, use the ansible.posix.firewalld collection. Standard firewall commands in playbooks can drift from the runtime state. Always reload the firewall after rule changes to sync persistent and runtime configurations.

ansible-galaxy collection install ansible.posix community.general
# ansible.posix provides the firewalld module for Fedora firewall management.
# community.general adds utilities for COPR repositories and other Fedora-specific tasks.
# Collections are installed in the user's .ansible/collections directory by default.

Install libselinux-python on the control node. SELinux errors often point to the controller, not the target.

Common pitfalls and error messages

Ansible is idempotent. This means running the playbook repeatedly converges the system to the desired state without side effects. Use --check mode to simulate the run. This flag evaluates tasks but makes no changes. It is essential for validating logic before applying it to a live system.

ansible-playbook --check fedora-setup.yml
# --check enables dry-run mode.
# Tasks report what would change without modifying the system.
# Some modules do not support check mode and will still execute.

Never store passwords or API keys in plain text playbooks. Ansible Vault encrypts sensitive data. Encrypt individual files or strings and reference them in your tasks.

ansible-vault encrypt_string 'mysecret' --name 'db_password'
# Encrypts a string and outputs YAML suitable for pasting into a playbook.
# The encrypted value can be decrypted at runtime using a vault password file.
# Protect the vault password file with strict file permissions.

When managing packages, the ansible.builtin.dnf module respects Fedora's repository configuration. It handles GPG keys and dependencies automatically. If you need to force a metadata refresh before installing, add update_cache: true to the task. This mimics the behavior of dnf upgrade --refresh and ensures you get the latest package versions from the mirrors.

    - name: Install package with cache refresh
      ansible.builtin.dnf:
        name: firefox
        state: present
        update_cache: true
        # update_cache: true forces a metadata refresh before the transaction.
        # This ensures the module sees the latest versions available in repos.
        # Use this sparingly to avoid slowing down the playbook run.

Run --check mode before applying changes to a live system. Dry runs catch logic errors without risking data loss.

Choose the right tool

Use Ansible when you need to configure multiple Fedora machines with a single source of truth. Use a simple shell script when you are setting up one laptop and don't care about reproducibility. Use rpm-ostree when you are running Silverblue and need immutable transactional updates. Use dnf system-upgrade when you are crossing major Fedora releases on a mutable system. Stay with manual commands if you are experimenting and want to understand every step before automating it.

Where to go next