Install & project setup¶
This page covers installing Ansible on the control node, SSH key setup, and the project structure. The vocabulary (control node, playbook, role, task, handler…) is explained once in the tutorial's Core Concepts; the complete set of files is in the Base Configuration worked example.
Official Ansible docs: https://docs.ansible.com/projects/ansible/latest/getting_started/index.html
How It Works¶
hostname (control node) --SSH--> server1, server2, server3 (managed nodes)
|
~/ansible/
ansible.cfg # how Ansible behaves (user, key, inventory path)
inventory/hosts.ini # what hosts exist and how they're grouped
group_vars/all.yml # variables shared by all hosts
group_vars/vault.yml # encrypted secrets (passwords, tokens)
playbooks/base.yml # what to do (calls roles in order)
roles/base/ # how to do it (tasks, templates, handlers)
Ansible reads the playbook, which targets hosts from the inventory, applies roles containing tasks, and fills in templates using variables. It connects via SSH and runs everything through sudo.
Table of Contents¶
- Concepts
- Installation
- SSH Key Setup
- Project Structure
- Configuration Files
- Inventory
- Variables
- Vault
- Roles
- Playbooks
- Ad-hoc commands & running playbooks
- Compose Templates
- Base Configuration
- Troubleshooting
Installation¶
Run on Control Node only. Managed nodes do not need Ansible installed.
The simplest option on Debian 13 is the package from the Debian repository (ansible 12 with ansible-core 2.19 on trixie):
The Debian package can lag behind upstream. For a more recent version, the Ansible docs point Debian users at the Ubuntu PPA, using the matching Ubuntu codename (noble for Debian 13):
UBUNTU_CODENAME='noble'
FINGERPRINT='6125E2A8C77F2818FB7BD15B93C4A3FD7BB9C367'
wget -O- "https://keyserver.ubuntu.com/pks/lookup?fingerprint=on&op=get&search=0x${FINGERPRINT}" \
| sudo gpg --dearmor -o /usr/share/keyrings/ansible-archive-keyring.gpg
cat <<EOF | sudo tee /etc/apt/sources.list.d/ansible.sources > /dev/null
Types: deb
URIs: https://ppa.launchpadcontent.net/ansible/ansible/ubuntu/
Suites: ${UBUNTU_CODENAME}
Components: main
Signed-By: /usr/share/keyrings/ansible-archive-keyring.gpg
EOF
sudo apt update && sudo apt install ansible
SSH Key Setup¶
Ansible uses SSH to connect to managed nodes. You need two keys: your personal key (with passphrase) and a dedicated Ansible key (no passphrase, for automation).
# Generate a dedicated Ansible key (no passphrase)
ssh-keygen -t ed25519 -C "ansible" -f ~/.ssh/ansible -N ""
# Copy it to each managed node (do this once per host)
ssh-copy-id -i ~/.ssh/ansible.pub username@server1.example.com
ssh-copy-id -i ~/.ssh/ansible.pub username@server2.example.com
ssh-copy-id -i ~/.ssh/ansible.pub username@server3.example.com
ssh-copy-id -i ~/.ssh/ansible.pub username@nas.example.com
Test that it works without a password prompt:
If that prints the hostname without asking for a password, you're good.
Test connectivity with Ansible:
Debian Install Issue?
Fresh Debian minimal install doesn't include sudo by default. You need to install it and configure it. SSH into server1 as root:
ssh root@server1.example.com "apt update && apt install -y sudo && echo 'username ALL=(ALL) NOPASSWD:ALL' > /etc/sudoers.d/username && chmod 440 /etc/sudoers.d/username"
If root SSH is disabled, log in as username and use su:
ssh username@server1.example.com
su -
apt update && apt install -y sudo
echo 'username ALL=(ALL) NOPASSWD:ALL' > /etc/sudoers.d/username
chmod 440 /etc/sudoers.d/username
exit
exit
Then test again:
Project Structure¶
Create this on Control Node. This is where all your Ansible code lives.
Final structure:
~/ansible
├── ansible.cfg
├── inventory
│ ├── group_vars
│ │ ├── all.yml
│ │ └── vault.yml
│ ├── hosts.ini
│ └── host_vars
├── playbooks
│ ├── base.yml
│ ├── stacks.yml
│ └── update.yml
└── roles
├── base
│ ├── files
│ ├── handlers
│ │ └── main.yml
│ ├── tasks
│ │ └── main.yml
│ └── templates
│ └── sshd_config.j2
├── borgmatic
│ ├── tasks
│ │ └── main.yml
│ └── templates
│ ├── borgmatic-config.yml.j2
│ └── borgmatic.cron.j2
├── codeserver
│ └── tasks
│ └── main.yml
├── docker
│ ├── handlers
│ │ └── main.yml
│ ├── tasks
│ │ └── main.yml
│ └── templates
│ └── daemon.json.j2
├── mounts
│ ├── tasks
│ │ └── main.yml
│ └── templates
│ └── nas-creds.j2
├── stacks_apps
│ ├── tasks
│ └── templates
├── stacks_infra
│ ├── tasks
│ │ └── main.yml
│ └── templates
│ ├── cloudflared-compose.yml.j2
│ ├── homeassistant-compose.yml.j2
│ ├── homepage-compose.yml.j2
│ ├── homepage-env.j2
│ ├── monitoring-compose.yml.j2
│ ├── npm-compose.yml.j2
│ └── vaultwarden-compose.yml.j2
├── tailscale
│ └── tasks
│ └── main.yml
└── update_containers
└── tasks
└── main.yml
Create the skeleton:
cd ~/ansible
mkdir -p inventory/group_vars inventory/host_vars
mkdir -p playbooks
mkdir -p roles/base/{tasks,handlers,templates,files}
mkdir -p roles/docker/{tasks,handlers,templates}
mkdir -p roles/borgmatic/{tasks,templates}
mkdir -p roles/tailscale/tasks
mkdir -p roles/mounts/{tasks,templates}
mkdir -p roles/stacks_infra/{tasks,templates}
mkdir -p roles/stacks_apps/{tasks,templates}
Ref: Ansible Docs