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
- Install Ansible on your control node (Ubuntu / macOS / WSL)
- Set up an inventory file with your homelab servers
- Run ad-hoc commands to test connectivity and perform quick operations
- Write your first playbook — a complete server hardening example
- Understand variables, handlers, and roles for reusable automation
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:
- Groups (
[webservers],[databases]) let you target servers selectively. - Group of groups (
[homelab:children]) lets you run commands against all servers at once. - Host variables (
ansible_host=) override the hostname or IP used for SSH connection. - Group variables (
[all:vars]) apply to every host — perfect for setting SSH user and key path.
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:
become: yes— Run tasks with sudo privileges (equivalent to--becomeon the CLI)vars— Define variables used throughout the playbook (user name, SSH key, port)tasks— The ordered list of operations to performhandlers— Special tasks that only run when notified (e.g., restart SSH only after config changes)lookup('file', ...)— Read a local file's content into a variable at runtime
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:
- Install Ansible on any Linux, macOS, or WSL control node
- Define an inventory with groups and variables in INI or YAML format
- Run ad-hoc commands with modules like
ping,apt, andcommand - Write playbooks with tasks, variables, handlers, and conditional logic
- Encrypt secrets with Ansible Vault for secure automation
- Organize your automation into roles for maintainability at scale
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.
Related Articles
- Linux Server Security Hardening Guide for Self-Hosting — The playbook in this article automates the steps from the manual security guide
- Docker Compose for Self-Hosting: A Complete Beginner's Guide — Use Ansible to provision the host, then Docker Compose to deploy the apps
- Monitoring Your Homelab: A Complete Guide — Automate your monitoring stack installation with Ansible
- WireGuard VPN Setup Guide for Secure Remote Access — Ansible can install and configure WireGuard across all your nodes
- Backup Strategies for Your Homelab — Automate backup scripts and schedules across your fleet with Ansible
- Nginx Reverse Proxy Setup Guide for Self-Hosted Applications — Configure Nginx on all web servers with a reusable Ansible role