← All articles
AUTOMATION Automating Your Home Lab with Ansible 2026-02-09 · ansible · automation · linux

Automating Your Home Lab with Ansible

Automation 2026-02-09 ansible automation linux devops configuration-management

At some point, your homelab crosses a threshold. You go from one or two machines you can configure by hand to five, ten, or more — and suddenly you're SSH-ing into each one to run the same apt update && apt upgrade command, repeating the same config changes, and forgetting which machine has which setup.

Ansible fixes this. It's an agentless automation tool that lets you define what your machines should look like and then makes them look that way. No software to install on target machines (just SSH access), no complex server infrastructure, and the configuration is written in YAML that's readable enough to serve as documentation.

Why Ansible for Homelabs

There are plenty of automation tools — Puppet, Chef, SaltStack, Terraform. Ansible is the right choice for homelabs because:

Installation

Install Ansible on your control machine (your workstation or a dedicated management node):

# Ubuntu/Debian
sudo apt install ansible

# Fedora
sudo dnf install ansible

# macOS
brew install ansible

# Or via pip (any OS)
pip install ansible

You also need SSH key-based access to your target machines. If you haven't set that up:

# Generate a key pair (if you don't have one)
ssh-keygen -t ed25519

# Copy it to each target machine
ssh-copy-id user@192.168.1.10
ssh-copy-id user@192.168.1.11
ssh-copy-id user@192.168.1.12

Inventory: Defining Your Machines

The inventory file tells Ansible what machines to manage. Create a project directory and add an inventory:

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

Create inventory.yml:

all:
  children:
    servers:
      hosts:
        nas:
          ansible_host: 192.168.1.50
          ansible_user: admin
        proxmox:
          ansible_host: 192.168.1.10
          ansible_user: root
    containers:
      hosts:
        pihole:
          ansible_host: 192.168.1.53
          ansible_user: root
        nginx-proxy:
          ansible_host: 192.168.1.80
          ansible_user: root
    pis:
      hosts:
        pi-monitor:
          ansible_host: 192.168.1.60
          ansible_user: pi
        pi-vpn:
          ansible_host: 192.168.1.61
          ansible_user: pi

Groups (servers, containers, pis) let you target subsets of your infrastructure. You can also have groups of groups and host-specific variables.

Test connectivity:

ansible all -i inventory.yml -m ping

You should see a SUCCESS response from each host.

Your First Playbook

Playbooks are YAML files that describe the desired state of your machines. Let's start with the most common homelab task — keeping everything updated.

Create update-all.yml:

---
- name: Update all Debian/Ubuntu machines
  hosts: all
  become: true
  tasks:
    - name: Update apt cache
      apt:
        update_cache: true
        cache_valid_time: 3600

    - name: Upgrade all packages
      apt:
        upgrade: dist

    - name: Remove unnecessary packages
      apt:
        autoremove: true

    - name: Check if reboot is required
      stat:
        path: /var/run/reboot-required
      register: reboot_required

    - name: Reboot if required
      reboot:
        reboot_timeout: 300
      when: reboot_required.stat.exists

Run it:

ansible-playbook -i inventory.yml update-all.yml

That updates every machine in your inventory, removes orphaned packages, and reboots any machine that needs it. What used to take 20 minutes of SSH-ing around now takes one command.

Practical Playbook Examples

Install and Configure Docker

---
- name: Set up Docker on target hosts
  hosts: servers
  become: true
  tasks:
    - name: Install prerequisites
      apt:
        name:
          - apt-transport-https
          - ca-certificates
          - curl
          - gnupg
        state: present

    - name: Add Docker GPG key
      apt_key:
        url: https://download.docker.com/linux/debian/gpg
        state: present

    - name: Add Docker repository
      apt_repository:
        repo: "deb https://download.docker.com/linux/debian {{ ansible_distribution_release }} stable"
        state: present

    - name: Install Docker
      apt:
        name:
          - docker-ce
          - docker-ce-cli
          - containerd.io
          - docker-compose-plugin
        state: present
        update_cache: true

    - name: Start and enable Docker
      systemd:
        name: docker
        state: started
        enabled: true

    - name: Add user to docker group
      user:
        name: "{{ ansible_user }}"
        groups: docker
        append: true

Harden SSH on All Machines

---
- name: Harden SSH configuration
  hosts: all
  become: true
  tasks:
    - name: Disable password authentication
      lineinfile:
        path: /etc/ssh/sshd_config
        regexp: '^#?PasswordAuthentication'
        line: 'PasswordAuthentication no'

    - name: Disable root login
      lineinfile:
        path: /etc/ssh/sshd_config
        regexp: '^#?PermitRootLogin'
        line: 'PermitRootLogin no'

    - name: Disable X11 forwarding
      lineinfile:
        path: /etc/ssh/sshd_config
        regexp: '^#?X11Forwarding'
        line: 'X11Forwarding no'

    - name: Restart SSH
      systemd:
        name: sshd
        state: restarted

Deploy a Docker Compose Stack

---
- name: Deploy monitoring stack
  hosts: pi-monitor
  become: true
  tasks:
    - name: Create monitoring directory
      file:
        path: /opt/monitoring
        state: directory
        mode: '0755'

    - name: Copy docker-compose file
      copy:
        src: files/monitoring-compose.yml
        dest: /opt/monitoring/docker-compose.yml

    - name: Copy Prometheus config
      copy:
        src: files/prometheus.yml
        dest: /opt/monitoring/prometheus.yml

    - name: Start the monitoring stack
      community.docker.docker_compose_v2:
        project_src: /opt/monitoring
        state: present

Using Roles for Organization

As your playbooks grow, roles keep things organized. A role is a structured directory of tasks, files, templates, and variables for a specific purpose.

# Create a role structure
mkdir -p roles/common/{tasks,files,templates,handlers,defaults}

Create roles/common/tasks/main.yml:

---
- name: Install essential packages
  apt:
    name:
      - vim
      - htop
      - tmux
      - curl
      - wget
      - git
      - unzip
    state: present

- name: Set timezone
  timezone:
    name: "{{ timezone }}"

- name: Configure NTP
  apt:
    name: chrony
    state: present

- name: Enable NTP
  systemd:
    name: chrony
    state: started
    enabled: true

Create roles/common/defaults/main.yml:

---
timezone: America/Los_Angeles

Use the role in a playbook:

---
- name: Base configuration for all hosts
  hosts: all
  become: true
  roles:
    - common

A typical homelab might have roles like:

Variables and Templates

Ansible uses Jinja2 templates for dynamic configuration. This is powerful for generating config files that differ slightly per host.

Create roles/monitoring/templates/node_exporter.service.j2:

[Unit]
Description=Node Exporter
After=network.target

[Service]
Type=simple
User=node_exporter
ExecStart=/usr/local/bin/node_exporter \
  --web.listen-address=:{{ node_exporter_port | default('9100') }} \
  --collector.filesystem.mount-points-exclude="^/(sys|proc|dev|host|etc)($$|/)"

[Install]
WantedBy=multi-user.target

Use it in a task:

- name: Create node_exporter systemd unit
  template:
    src: node_exporter.service.j2
    dest: /etc/systemd/system/node_exporter.service
  notify: restart node_exporter

Ansible Vault: Managing Secrets

Don't put passwords in plain text in your playbooks. Ansible Vault encrypts sensitive data:

# Create an encrypted variables file
ansible-vault create group_vars/all/vault.yml

Inside, store your secrets:

vault_smtp_password: "your-email-password"
vault_grafana_admin_password: "your-grafana-password"
vault_wireguard_private_key: "your-wg-key"

Reference them in playbooks:

- name: Configure Grafana
  template:
    src: grafana.ini.j2
    dest: /etc/grafana/grafana.ini
  vars:
    admin_password: "{{ vault_grafana_admin_password }}"

Run playbooks with vault:

ansible-playbook -i inventory.yml site.yml --ask-vault-pass

# Or use a password file
ansible-playbook -i inventory.yml site.yml --vault-password-file ~/.vault_pass

Recommended Project Structure

As your automation grows, organize it like this:

homelab-ansible/
├── inventory.yml
├── site.yml              # Main playbook that includes everything
├── update.yml            # Standalone: update all machines
├── group_vars/
│   ├── all/
│   │   ├── vars.yml      # Shared variables
│   │   └── vault.yml     # Encrypted secrets
│   ├── servers.yml       # Server-specific vars
│   └── pis.yml           # Pi-specific vars
├── host_vars/
│   └── nas.yml           # NAS-specific vars
├── roles/
│   ├── common/
│   ├── docker/
│   ├── monitoring/
│   └── security/
└── files/
    └── monitoring-compose.yml

Your site.yml pulls everything together:

---
- name: Base configuration
  hosts: all
  become: true
  roles:
    - common
    - security

- name: Docker hosts
  hosts: servers
  become: true
  roles:
    - docker

- name: Monitoring
  hosts: all
  become: true
  roles:
    - monitoring

Tips for Homelab Ansible

Start small: Don't try to automate everything at once. Start with updates and SSH hardening, then add roles gradually.

Use --check mode: Run playbooks with --check to see what would change without making changes. Add --diff to see the actual file differences.

ansible-playbook -i inventory.yml site.yml --check --diff

Tag your tasks: Add tags so you can run subsets of your playbooks:

- name: Install Docker
  apt:
    name: docker-ce
  tags: [docker, setup]
ansible-playbook -i inventory.yml site.yml --tags docker

Version control everything: Put your Ansible project in a git repo. This gives you history, rollback, and the ability to collaborate or recreate your setup from scratch.

Don't fight Ansible: If a task is hard to express in Ansible, it might be better as a shell script called by Ansible rather than a complex chain of modules. Use the command or shell modules as escape hatches when needed.

Ansible isn't just for enterprises with hundreds of servers. Even a five-machine homelab benefits from having its configuration defined in code rather than in your memory. When your Proxmox host dies and you rebuild from scratch, you'll be glad you can run one command to get everything back to where it was.