Managing one Raspberry Pi manually is fine. Managing ten is a chore. Managing fifty is a full-time job — unless you use Ansible. Ansible is an open-source configuration management and automation tool that lets you define the desired state of your Pi boards in simple YAML files (called playbooks) and apply those configurations to an entire fleet simultaneously over SSH, without installing any agent software on the Pi boards themselves.
This tutorial is practical: we’ll go from zero to a working Ansible setup that can provision a fleet of fresh Raspberry Pi boards, configure WiFi, install and configure software, set up monitoring, and enforce security settings — all from a single command on your laptop or management server.
Why Ansible for Raspberry Pi Fleets?
The typical alternative to Ansible for Pi fleets is either a custom shell script that you SSH into each Pi to run, or a disk image that you flash to each SD card. Both approaches have serious problems:
- Custom shell scripts are not idempotent — running them twice often breaks things. They’re hard to version control meaningfully, and debugging failures across 20 Pi boards over SSH is painful.
- Golden disk images require reflashing every time you change configuration. Distributing 8GB image files across a network is slow, and you lose the history of what changed and when.
Ansible solves all of this. Its key properties for Pi fleet management are:
- Agentless — only SSH and Python (2.7+) required on the Pi. Raspberry Pi OS ships with both.
- Idempotent — running a playbook multiple times produces the same result. Safe to re-run after adding a change.
- Declarative — you describe the desired state, Ansible figures out how to achieve it.
- Parallel execution — by default, Ansible runs tasks on 5 hosts simultaneously (configurable). A 50-host fleet doesn’t take 50× as long.
Setting Up Ansible on Your Control Machine
Ansible runs on your laptop or a dedicated management server (not on the Pi boards being managed). Install it on Ubuntu/Debian/macOS:
# Ubuntu / Debian
sudo apt update && sudo apt install -y ansible
# macOS
brew install ansible
# Verify
ansible --version
Before Ansible can manage your Pi boards, you need SSH key-based authentication set up. Generate a keypair on your control machine if you don’t have one, then distribute the public key to all Pi boards:
# Generate key (if not already done)
ssh-keygen -t ed25519 -C "ansible-fleet"
# Copy to each Pi (default Pi OS credentials: pi / raspberry)
# Or use the username set during Raspberry Pi Imager setup
ssh-copy-id -i ~/.ssh/id_ed25519.pub [email protected]
ssh-copy-id -i ~/.ssh/id_ed25519.pub [email protected]
# ... and so on for each Pi
Pro tip: When flashing Pi OS with Raspberry Pi Imager, go to Advanced Options (the gear icon) and pre-configure SSH keys, WiFi credentials, hostname, and username before flashing. This means your Pi boots directly into a state where Ansible can connect — no manual SSH-copy-id needed for each board.
Building Your Pi Inventory File
Ansible’s inventory defines which hosts to manage and how to group them. Create a project directory structure:
mkdir -p ~/pi-fleet/inventory ~/pi-fleet/playbooks ~/pi-fleet/roles
cd ~/pi-fleet
Create inventory/hosts.ini:
[pi_all]
pi-sensor-01 ansible_host=192.168.1.101
pi-sensor-02 ansible_host=192.168.1.102
pi-sensor-03 ansible_host=192.168.1.103
pi-display-01 ansible_host=192.168.1.111
pi-display-02 ansible_host=192.168.1.112
pi-gateway-01 ansible_host=192.168.1.120
[pi_sensors]
pi-sensor-01
pi-sensor-02
pi-sensor-03
[pi_displays]
pi-display-01
pi-display-02
[pi_gateway]
pi-gateway-01
[pi_all:vars]
ansible_user=pi
ansible_ssh_private_key_file=~/.ssh/id_ed25519
ansible_python_interpreter=/usr/bin/python3
Test connectivity to all Pi boards at once:
ansible all -i inventory/hosts.ini -m ping
You should see "ping": "pong" from every host. If some fail, check SSH connectivity individually before proceeding.
Your First Playbook: Base Configuration
This playbook applies a standard base configuration to every Pi in your fleet — the same things you’d do manually on a fresh install, but automated:
---
# playbooks/base.yml
- name: Base Raspberry Pi configuration
hosts: pi_all
become: yes # Run tasks as root (sudo)
vars:
timezone: Asia/Kolkata
ntp_servers:
- 0.in.pool.ntp.org
- 1.in.pool.ntp.org
packages_to_install:
- vim
- git
- htop
- curl
- python3-pip
- fail2ban
- unattended-upgrades
packages_to_remove:
- wolfram-engine
- libreoffice*
- minecraft-pi
tasks:
- name: Update apt cache and upgrade packages
apt:
update_cache: yes
upgrade: safe
cache_valid_time: 3600
- name: Install required packages
apt:
name: "{{ packages_to_install }}"
state: present
- name: Remove bloatware packages
apt:
name: "{{ packages_to_remove }}"
state: absent
purge: yes
autoremove: yes
- name: Set timezone
timezone:
name: "{{ timezone }}"
- name: Configure NTP servers
lineinfile:
path: /etc/systemd/timesyncd.conf
regexp: '^#?NTP='
line: "NTP={{ ntp_servers | join(' ') }}"
notify: Restart timesyncd
- name: Disable swap (extend SD card life)
command: dphys-swapfile swapoff
ignore_errors: yes
- name: Set vm.swappiness to 1
sysctl:
name: vm.swappiness
value: '1'
state: present
sysctl_set: yes
- name: Add noatime to root filesystem mount
mount:
path: /
src: "{{ ansible_mounts | selectattr('mount', 'equalto', '/') | map(attribute='device') | first }}"
fstype: ext4
opts: defaults,noatime
state: present
- name: Change default pi user password
user:
name: pi
password: "{{ vault_pi_password | password_hash('sha512') }}"
- name: Disable SSH password authentication
lineinfile:
path: /etc/ssh/sshd_config
regexp: '^#?PasswordAuthentication'
line: 'PasswordAuthentication no'
notify: Restart SSH
- name: Set unique hostname based on inventory name
hostname:
name: "{{ inventory_hostname }}"
handlers:
- name: Restart timesyncd
systemd:
name: systemd-timesyncd
state: restarted
- name: Restart SSH
systemd:
name: ssh
state: restarted
Run this playbook against all Pi boards:
ansible-playbook -i inventory/hosts.ini playbooks/base.yml
Watch as Ansible SSHes into each Pi in parallel, runs every task, and reports a colour-coded summary. Green means the task ran successfully and the system was already in the desired state (or was changed to reach it). Yellow means something was changed. Red means an error occurred. Each task is idempotent — running this again after it completes will show all green with zero changes.
Organising Playbooks with Roles
As your fleet grows more complex, organise tasks into reusable roles. A role is a directory structure with tasks, handlers, templates, and variables organised by function:
ansible-galaxy init roles/sensor_node
# Creates: roles/sensor_node/{tasks,handlers,templates,vars,defaults,meta}/
A sensor node role might install Python sensor libraries, configure a systemd data-collection service, and set up log rotation. A display role might install pygame and configure autostart for a kiosk application. The gateway role might install and configure Mosquitto MQTT broker.
Your top-level site playbook then composes roles:
---
# playbooks/site.yml
- import_playbook: base.yml
- name: Configure sensor nodes
hosts: pi_sensors
become: yes
roles:
- sensor_node
- monitoring
- name: Configure display nodes
hosts: pi_displays
become: yes
roles:
- display_kiosk
- monitoring
- name: Configure gateway
hosts: pi_gateway
become: yes
roles:
- mqtt_broker
- monitoring
Advanced Tasks: WiFi, GPIO, and Monitoring
Managing WiFi Configuration
Deploy WiFi credentials to all Pi boards without storing them in plaintext in your playbook (using Vault, covered next):
- name: Configure WiFi
template:
src: templates/wpa_supplicant.conf.j2
dest: /etc/wpa_supplicant/wpa_supplicant.conf
mode: '0600'
owner: root
group: root
notify: Reconfigure wlan0
The Jinja2 template wpa_supplicant.conf.j2:
ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev
update_config=1
country=IN
network={
ssid="{{ vault_wifi_ssid }}"
psk="{{ vault_wifi_password }}"
key_mgmt=WPA-PSK
priority=10
}
Deploying and Managing Systemd Services
Use Ansible to deploy your Python sensor scripts as systemd services across all sensor nodes:
- name: Copy sensor script
copy:
src: files/sensor_daemon.py
dest: /usr/local/bin/sensor_daemon.py
mode: '0755'
notify: Restart sensor daemon
- name: Deploy systemd service unit
template:
src: templates/sensor-daemon.service.j2
dest: /etc/systemd/system/sensor-daemon.service
notify: Reload systemd
- name: Enable and start sensor daemon
systemd:
name: sensor-daemon
enabled: yes
state: started
daemon_reload: yes
Setting Up Prometheus Node Exporter for Monitoring
Install Node Exporter on all Pi boards so a central Prometheus instance can scrape metrics (CPU, RAM, disk, temperature):
- name: Download Node Exporter for ARM64
get_url:
url: https://github.com/prometheus/node_exporter/releases/download/v1.7.0/node_exporter-1.7.0.linux-arm64.tar.gz
dest: /tmp/node_exporter.tar.gz
checksum: sha256:expected_checksum_here
- name: Extract Node Exporter
unarchive:
src: /tmp/node_exporter.tar.gz
dest: /usr/local/bin/
remote_src: yes
extra_opts: [--strip-components=1]
- name: Create Node Exporter systemd service
copy:
dest: /etc/systemd/system/node_exporter.service
content: |
[Unit]
Description=Prometheus Node Exporter
After=network.target
[Service]
ExecStart=/usr/local/bin/node_exporter
Restart=always
[Install]
WantedBy=multi-user.target
notify: Restart node_exporter
- name: Enable Node Exporter
systemd:
name: node_exporter
enabled: yes
state: started
daemon_reload: yes
Handling Secrets with Ansible Vault
Never store passwords, WiFi keys, or API tokens in plain text in your playbooks. Ansible Vault encrypts sensitive values with AES-256:
# Create an encrypted vars file
ansible-vault create inventory/group_vars/all/vault.yml
Inside the vault file, define your secrets:
vault_pi_password: your_secure_password_here
vault_wifi_ssid: YourWifiNetwork
vault_wifi_password: YourWifiPassword123
vault_mqtt_password: mqtt_broker_secret
vault_monitoring_api_key: prometheus_remote_write_key
Run playbooks that use vault secrets:
# Prompt for vault password:
ansible-playbook -i inventory/hosts.ini playbooks/site.yml --ask-vault-pass
# Or use a vault password file (for CI/CD pipelines):
ansible-playbook -i inventory/hosts.ini playbooks/site.yml --vault-password-file ~/.vault_pass
Store the vault password file outside your git repository and add it to .gitignore. The encrypted vault file itself can be safely committed to git — without the vault password, it’s just ciphertext.
Frequently Asked Questions
Can Ansible manage Raspberry Pi boards on different subnets or over the internet?
Yes, as long as SSH port 22 is reachable from your control machine to the Pi. For Pi boards in remote locations, use SSH jump hosts (bastion servers) — configure them in your inventory with ansible_ssh_common_args='-o ProxyJump=bastion_user@bastion_ip'. For Pi boards behind NAT, set up SSH tunnels or use a VPN like WireGuard connecting all your Pi boards to a central hub.
What if some Pi boards are offline when I run a playbook?
Ansible reports an error for unreachable hosts but continues running tasks on reachable ones. Use --limit pi-sensor-01,pi-sensor-02 to target specific boards, or -e 'ansible_timeout=5' to fail faster on unreachable hosts. The serial: 1 playbook option runs tasks one host at a time, which is safer for rolling updates.
Is Ansible suitable for a fleet of 100+ Raspberry Pi boards?
Absolutely. Ansible was designed for fleets of hundreds to thousands of nodes. Increase parallelism with -f 20 (20 simultaneous connections) in your ansible command. For very large fleets, consider ansible-pull — where each Pi runs Ansible locally, pulling its configuration from a git repository, eliminating the need for the control node to maintain hundreds of simultaneous SSH connections.
Can I use Ansible to update Pi OS firmware and bootloader?
Yes. Use the command or shell module to run rpi-update (kernel/firmware) and rpi-eeprom-update -a (bootloader). Be aware these changes require a reboot — use Ansible’s reboot module to trigger and wait for the reboot automatically as part of the playbook.
How is Ansible different from other Pi management tools like Puppet or Chef?
Puppet and Chef require a persistent agent daemon running on every managed node, plus a central server. Ansible is agentless — it only needs SSH. For Pi fleets where resource efficiency matters (the agent would consume RAM on every Pi), Ansible’s agentless approach is a significant advantage. Salt (SaltStack) is another agentless alternative but has a steeper learning curve than Ansible.
Ready to scale your Raspberry Pi projects? Find all Pi 5 models, accessories, and components at zbotic.in/product-category/raspberry-pi/ — shipped fast across India.
Add comment