How to Manage Several Servers with Ansible (Run the Same Change on All of Them)

Ansible: one command, every machine. Updates, users, firewall rules and packages — the same on the server, the Pi and the VPS.

Once there's a server, a Pi, a VPS and a backup box at a relative's, "apply updates", "add my new SSH key" and "set the time zone" become four SSH sessions each. Ansible does them in one: it connects over SSH to every machine in a list and runs tasks described in YAML — install this package, ensure this line is in that file, start this service — and only changes what needs changing. Nothing to install on the targets but Python, which Ubuntu already has.

Quick reference

TaskCommand
Install (on the control machine)pipx install ansible or sudo apt install ansible -y
Inventoryinventory.ini listing hosts and groups
Can I reach them?ansible all -m ping
Run a command everywhereansible all -a "df -h /"
Update everythingansible all -b -m apt -a "update_cache=yes upgrade=dist"
Run a playbookansible-playbook site.yml
Dry runansible-playbook site.yml --check --diff

1. Install Ansible on the control machine

Your laptop (Mac/Linux/WSL) or the main server — wherever you already SSH from:

sudo apt install pipx -y && pipx install ansible
ansible --version

2. The inventory

mkdir -p ~/ansible && cd ~/ansible
nano inventory.ini

[home]
homeserver ansible_host=192.168.1.50
pi ansible_host=192.168.1.60

[remote]
vps ansible_host=203.0.113.10
mum ansible_host=192.168.2.50 # over the site-to-site tunnel

[all:vars]
ansible_user=john
ansible_python_interpreter=/usr/bin/python3

SSH keys must already work to each host as john (tutorial 3). Test:

ansible -i inventory.ini all -m ping

Every host should answer pong. Save typing with ansible.cfg in the folder:

[defaults]
inventory = inventory.ini
host_key_checking = True

3. Ad-hoc commands

ansible all -a "uptime"
ansible home -a "df -h /"
ansible all -b -m apt -a "update_cache=yes upgrade=dist"
ansible all -b -m reboot --limit pi

-b means become (sudo). -m picks a module: apt, copy, service, user, shell. The apt one above is "update every machine" in one line; --limit narrows to a host or group.

4. A first playbook

A playbook is a list of tasks, each describing a state. Run it twice and the second run changes nothing — that's idempotence, and it's why Ansible beats a shell script.

nano baseline.yml

---
- name: Baseline every machine
hosts: all
become: true
vars:
my_key: "ssh-ed25519 AAAA... john-laptop"

tasks:
- name: Time zone
community.general.timezone:
name: Europe/London

- name: Packages I always want
apt:
name: [htop, tmux, ncdu, fail2ban, unattended-upgrades, ufw]
state: present
update_cache: true

- name: My SSH key on every box
authorized_key:
user: john
key: "{{ my_key }}"

- name: Password login off
lineinfile:
path: /etc/ssh/sshd_config
regexp: '^#?PasswordAuthentication'
line: 'PasswordAuthentication no'
notify: restart ssh

- name: UFW allows SSH and is on
community.general.ufw:
rule: allow
name: OpenSSH
- community.general.ufw:
state: enabled
policy: deny

- name: Fail2ban running
service:
name: fail2ban
state: started
enabled: true

handlers:
- name: restart ssh
service:
name: ssh
state: restarted

ansible-galaxy collection install community.general
ansible-playbook baseline.yml --check --diff # what would change
ansible-playbook baseline.yml

The summary shows changed= per host. Run it again: changed=0. Add a new machine to the inventory, run it once, and it's set up like the others. That's tutorials 3 to 7 as one command.

5. Useful modules

copy and template (files with variables), lineinfile / blockinfile (edit configs), user, cron, systemd, docker_compose_v2 (bring a compose stack up), git, uri (hit an API), mount, apt_repository. ansible-doc -l lists thousands; ansible-doc apt documents one.

6. Secrets

Passwords and tokens go in Ansible Vault: ansible-vault create secrets.yml, refer to the variables in playbooks, run with --ask-vault-pass. Keep the whole ~/ansible folder in Gitea (tutorial 45) — vault files are encrypted, so they're safe to commit.

Ansible and Docker

Use Ansible for the host: packages, users, firewall, Docker itself, and docker_compose_v2 to deploy stacks from your git repo. Keep the apps' own config in compose files as before.

Troubleshooting

"Permission denied (publickey)". Your key isn't on that host for ansible_user; fix with SSH first.

"Missing sudo password". Passwordless sudo isn't set on the target; add -K to be asked, or configure NOPASSWD for your user in sudoers.

Module not found (community.general.ufw). ansible-galaxy collection install community.general.

A task changes every run. It's a shell or command task — those always "change". Use the specific module, or add changed_when.

Wrapping up

An inventory, ansible all -m ping, one playbook that captures your baseline. Updates become one line, a new machine becomes one run, and the state of every box in your care is a file in git rather than a memory of what you typed where.

Call us