Skip to content

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

  1. Concepts
  2. Installation
  3. SSH Key Setup
  4. Project Structure
  5. Configuration Files
  6. Inventory
  7. Variables
  8. Vault
  9. Roles
  10. Playbooks
  11. Ad-hoc commands & running playbooks
  12. Compose Templates
  13. Base Configuration
  14. 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):

sudo apt update
sudo apt install ansible

# Verify
ansible --version

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

Ref: https://docs.ansible.com/projects/ansible/latest/installation_guide/installation_distros.html#installing-ansible-on-debian


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:

ssh -i ~/.ssh/ansible username@server1.example.com "hostname"

If that prints the hostname without asking for a password, you're good.

Test connectivity with Ansible:

cd ~/ansible
ansible server1.example.com -m ping

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:

ansible server1.example.com -m ping

Project Structure

Create this on Control Node. This is where all your Ansible code lives.

mkdir -p ~/ansible
cd ~/ansible

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}
Verify it looks right:

tree ~/ansible -L 3

Ref: Ansible Docs