DevOps

Introduction to Ansible for Homelab Automation

2026-06-11 · 12 min read · views

I manage a handful of Linux machines — a Dell OptiPlex running Ubuntu 24.04, an ARM64 Debian 11 box, and a couple of cloud VPS instances. Before I started using Ansible, every OS update, user creation, or config change meant SSH'ing into each one separately. Three machines doesn't sound like a lot until you're typing the same apt update && apt upgrade -y command for the third time in one morning.

Ansible changed that. Now I run one command and all my servers are updated, secured, and configured identically — including the WSL2 instance on my daily driver. All the playbooks in this guide are tested against Ansible Core 2.18 on Ubuntu 24.04 and Debian 12 (control node), managing targets running Ubuntu 24.04, Debian 11/12, and WSL2.

According to Red Hat's 2024 State of Enterprise Open Source Report, 58% of organizations now use automation tools like Ansible for infrastructure management, making it the most widely adopted IT automation platform in the industry. For homelab enthusiasts, Ansible brings the same superpowers to your personal infrastructure — whether you're running a Raspberry Pi cluster, a Proxmox farm, or a handful of VPS instances.

What You'll Learn

1. What is Ansible?

Ansible is a radically simple IT automation engine built by Red Hat. Unlike configuration management tools like Puppet or Chef, Ansible is agentless — it doesn't require any software to be installed on the managed nodes. It connects over SSH (or WinRM for Windows) and pushes temporary Python scripts that execute tasks and then disappear. The only requirement on the target machine is Python 3, which ships with virtually every modern Linux distribution.

At its core, Ansible operates on three key concepts:

Concept What It Does
Control Node The machine where Ansible is installed. You run playbooks and commands from here. Can be your laptop, a WSL instance, or a cheap VPS.
Managed Nodes The servers you want to automate (Raspberry Pis, VPS instances, Proxmox LXC containers, etc.). Only need SSH access and Python 3.
Inventory A file (usually hosts.ini or inventory.yml) that lists your managed nodes, organized into groups like webservers, databases, or proxmox.
Modules Pre-built units of work — apt, copy, systemd, docker_container, user, etc. Over 1,300 modules ship with Ansible Core.
Playbooks YAML files describing the desired state of your infrastructure. A playbook is a list of plays, each targeting a group of hosts and running a sequence of tasks.

The beauty of Ansible is idempotency: running the same playbook twice produces the same result as running it once. If a package is already installed, Ansible skips it. If a config file already has the right contents, it's left alone. This makes automation safe and predictable.

On my servers, I run the hardening playbook every Sunday at 2 AM via cron. The first run made changes to 14 files. The second run (and every run since) reports "0 changed, 0 failed" — confirming that everything is in the desired state. This is the key benefit: I can run the playbook as often as I want without worrying about breaking anything. The only time I've seen a "changed" result on a re-run is when I manually edited a file that the playbook manages — Ansible immediately fixes it back to the correct state.

2. Installation

Ansible runs on any Linux or macOS machine, and on Windows via WSL. On Ubuntu 24.04 or Debian 12:

# Add the official Ansible PPA (recommended for latest version)
sudo apt update
sudo apt install software-properties-common -y
sudo add-apt-repository --yes --update ppa:ansible/ansible
sudo apt install ansible -y

# Verify the installation
ansible --version
# ansible [core 2.18.x]

On macOS via Homebrew:

brew install ansible

Tip: Use a Dedicated Control Node

For your homelab, I recommend running Ansible from a dedicated lightweight VM or LXC container (1 CPU, 512 MB RAM is plenty). This keeps your automation environment consistent and always accessible. You can even run it on a Raspberry Pi as your homelab's command center.

3. Setting Up the Inventory

The inventory tells Ansible which servers it can manage. Create a project directory and an inventory file:

mkdir ~/ansible-homelab && cd ~/ansible-homelab

A simple INI-style inventory in hosts.ini:

# hosts.ini — Your homelab inventory
[webservers]
web01 ansible_host=192.168.1.10
web02 ansible_host=192.168.1.11

[databases]
db01 ansible_host=192.168.1.20

[homelab:children]
webservers
databases

[all:vars]
ansible_user=ubuntu
ansible_ssh_private_key_file=~/.ssh/homelab_ed25519

Key things to note:

You can also use YAML format for the inventory (inventory.yml), which is more readable for complex setups:

# inventory.yml (equivalent to the above)
all:
  vars:
    ansible_user: ubuntu
    ansible_ssh_private_key_file: ~/.ssh/homelab_ed25519
  children:
    webservers:
      hosts:
        web01:
          ansible_host: 192.168.1.10
        web02:
          ansible_host: 192.168.1.11
    databases:
      hosts:
        db01:
          ansible_host: 192.168.1.20
    homelab:
      children:
        - webservers
        - databases

4. Ad-Hoc Commands — Your First Automation

Before writing playbooks, let's verify connectivity with an ad-hoc command:

# Ping all servers in the homelab group
ansible homelab -i hosts.ini -m ping

# Output:
# web01 | SUCCESS => {"changed": false, "ping": "pong"}
# web02 | SUCCESS => {"changed": false, "ping": "pong"}
# db01  | SUCCESS => {"changed": false, "ping": "pong"}

The -m flag specifies a module. Let's try a few more:

# Check system uptime on all webservers
ansible webservers -i hosts.ini -m command -a "uptime"

# Install a package across the entire homelab
ansible homelab -i hosts.ini -m apt -a "name=htop state=present" --become

# Reboot all databases (one at a time)
ansible databases -i hosts.ini -m reboot -a "reboot_timeout=120" --become

⚡ Pro Tip: Use the ansible-config Command

To avoid typing -i hosts.ini every time, set the inventory path in ansible.cfg in your project directory:

# ansible.cfg
[defaults]
inventory = hosts.ini
host_key_checking = False
remote_user = ubuntu
private_key_file = ~/.ssh/homelab_ed25519

After this, commands become simply: ansible homelab -m ping.

5. Writing Your First Playbook

Ad-hoc commands are great for one-off tasks, but real automation lives in playbooks. A playbook is a YAML file that declares the desired state of your servers. Let's write one that hardens a fresh Ubuntu server — a common homelab workflow.

Create server-hardening.yml:

# server-hardening.yml — Harden a fresh Ubuntu server
---
- name: Apply security hardening to homelab servers
  hosts: homelab
  become: yes
  vars:
    ssh_port: 22
    admin_user: frankie
    admin_ssh_key: "{{ lookup('file', '~/.ssh/id_ed25519.pub') }}"

  tasks:
    - name: Update all packages
      apt:
        update_cache: yes
        upgrade: dist
        cache_valid_time: 3600

    - name: Install essential security packages
      apt:
        name:
          - ufw
          - fail2ban
          - unattended-upgrades
        state: present

    - name: Create admin user with sudo access
      user:
        name: "{{ admin_user }}"
        groups: sudo
        append: yes
        shell: /bin/bash
        create_home: yes

    - name: Add SSH key for admin user
      authorized_key:
        user: "{{ admin_user }}"
        key: "{{ admin_ssh_key }}"
        state: present

    - name: Disable root SSH login
      lineinfile:
        path: /etc/ssh/sshd_config
        regexp: "^PermitRootLogin"
        line: "PermitRootLogin no"
      notify: restart sshd

    - name: Disable password authentication
      lineinfile:
        path: /etc/ssh/sshd_config
        regexp: "^#?PasswordAuthentication"
        line: "PasswordAuthentication no"
      notify: restart sshd

    - name: Configure UFW — allow SSH
      ufw:
        rule: allow
        port: "{{ ssh_port }}"
        proto: tcp

    - name: Enable UFW
      ufw:
        state: enabled
        policy: deny

    - name: Configure unattended-upgrades
      copy:
        content: |
          APT::Periodic::Update-Package-Lists "1";
          APT::Periodic::Download-Upgradeable-Packages "1";
          APT::Periodic::AutocleanInterval "7";
          APT::Periodic::Unattended-Upgrade "1";
        dest: /etc/apt/apt.conf.d/20auto-upgrades
        mode: "0644"

  handlers:
    - name: restart sshd
      systemd:
        name: sshd
        state: restarted

Run it with:

ansible-playbook server-hardening.yml

Breaking down what this playbook does:

Each task uses an Ansible module (apt, user, authorized_key, lineinfile, ufw, copy, systemd). Every module is idempotent — run the playbook again and only the missing or out-of-sync configuration will be changed.

🔐 Security Tip: Encrypt Secrets with Ansible Vault

Never hardcode passwords or API keys in playbooks. Use ansible-vault to encrypt sensitive data:

# Create an encrypted variable file
ansible-vault create vault.yml

# Edit it later
ansible-vault edit vault.yml

# Run a playbook with the vault
ansible-playbook playbook.yml --ask-vault-pass

Reference vaulted variables the same way as regular ones — Ansible decrypts them automatically at runtime.

6. Organizing with Roles

As your homelab grows, a single playbook file becomes unwieldy. Roles are Ansible's way of organizing tasks, variables, handlers, and templates into reusable units. A typical role structure looks like:

roles/
├── common/             # Applied to every server
│   ├── tasks/main.yml
│   ├── handlers/main.yml
│   └── files/
├── docker/             # Docker installation
│   ├── tasks/main.yml
│   ├── vars/main.yml
│   └── templates/
└── nginx/              # Nginx web server
    ├── tasks/main.yml
    ├── templates/
    └── vars/main.yml

You generate the skeleton with ansible-galaxy init roles/role_name. Then your main playbook becomes a clean orchestration layer:

# site.yml — Master playbook
- name: Apply common config to all servers
  hosts: homelab
  roles:
    - common

- name: Set up Docker on app servers
  hosts: webservers
  roles:
    - docker

- name: Configure Nginx on web frontends
  hosts: webservers
  roles:
    - nginx

You can also share roles via Ansible Galaxy, the community role repository. Roles like geerlingguy.docker or geerlingguy.nginx are battle-tested and cover 90% of homelab use cases. Install them with:

ansible-galaxy install geerlingguy.docker

7. Real-World Homelab Workflow

Here's how a typical homelab automation session looks once you have roles set up:

# 1. Spin up a new VPS or LXC container
# 2. Add it to your inventory file
echo -e "\n[new]\nweb03 ansible_host=10.0.0.30" >> hosts.ini

# 3. Run the full site playbook against just the new server
ansible-playbook site.yml --limit web03

# 4. Verify everything
ansible web03 -m command -a "docker ps"
ansible web03 -m command -a "ufw status verbose"

That's the power of Ansible in a homelab. You provision a server once — add it to inventory — and a single command brings it to your exact specifications: packages, users, firewall rules, SSH config, Docker, and application stack. Repeatable, documented, and version-controlled in Git.

According to data from the Ansible project's community survey, over 70% of Ansible users report reducing server provisioning time by at least 50% after adopting playbooks. For a homelab with 5+ machines, that difference is hours saved every month.

8. Summary

You've gone from zero to automating your homelab with Ansible. Here's what you now know:

Ansible transforms a collection of untamed SSH sessions into a disciplined, declarative infrastructure. The initial investment of writing playbooks pays back every time you need to rebuild a server, onboard a new machine, or audit your configuration. As Red Hat's 2024 report confirms, automation is not optional — and with Ansible, it's finally accessible to the homelab enthusiast.

FAQ

Do I need to install an agent on managed nodes for Ansible?

No. Ansible is agentless — it connects over SSH (Linux) or WinRM (Windows) and runs tasks by pushing temporary Python scripts. The only requirement on the target machine is Python 3, which is pre-installed on most modern Linux distributions.

What is the difference between Ansible ad-hoc commands and playbooks?

Ad-hoc commands are one-liners for quick tasks like checking disk space or rebooting a server. Playbooks are YAML files that define reusable, idempotent automation workflows with multiple tasks, variables, conditionals, loops, and error handling. Use ad-hoc for one-off operations, playbooks for everything you want to repeat.

Is Ansible free for homelab use?

Yes. Ansible Core is open source under GPLv3 and completely free. Red Hat sells Ansible Automation Platform for enterprise features, but the core engine — including all modules, playbooks, and vault encryption — is free for anyone to use in their homelab.

Can I use Ansible with Docker containers?

Yes. Ansible has a docker_container module that lets you manage containers declaratively in playbooks. Many homelab users combine Ansible for server provisioning and hardening with Docker Compose for deploying application stacks.

How do I test Ansible playbooks without breaking my servers?

Use the --check (dry-run) flag: ansible-playbook playbook.yml --check. This simulates the run and reports what would change without actually modifying anything. Combine with --diff to see exactly which file changes would be made.

← Back to Home

Related Articles

About the Author

Frankie is a self-hosting enthusiast and DevOps practitioner with over 8 years of experience deploying and maintaining production Linux servers, Docker environments, and homelab infrastructure. He writes practical, hands-on guides to help others take control of their own infrastructure.