Here Comes the Swarm 03

Table of Contents
We’ve got our VMs running. Now it’s time to turn them into something useful. That’s where Ansible comes in—my trusty configuration management tool that does all the heavy lifting.
The Ansible Setup #
The playbooks live in ansible/. I use a few external roles (thank you, Galaxy) plus some custom roles I wrote myself:
- geerlingguy.docker - installs Docker (because why reinvent the wheel)
- geerlingguy.pip - Python dependencies
- fresh_install - my custom role for base system configuration
- docker_swarm - initializes the Swarm cluster
- portainer - deploys the management UI
- github_runner - sets up self-hosted GitHub Actions runners
Inventory: Knowing Your Nodes #
Here’s how Ansible sees my cluster:
[swarm_managers]
dkr-srv-1 ansible_host=10.0.30.21
dkr-srv-2 ansible_host=10.0.30.22
dkr-srv-3 ansible_host=10.0.30.23
[swarm_workers]
dkr-wrkr-1 ansible_host=10.0.30.31
dkr-wrkr-2 ansible_host=10.0.30.32
dkr-wrkr-3 ansible_host=10.0.30.33
[github_runners]
gh-runner-1 ansible_host=10.0.30.40
The Main Playbook #
The site.yml playbook is where the magic happens. It runs in two phases:
Phase 1: Configure All Nodes #
- name: Configure Swarm Manager Nodes
hosts: all
become: true
roles:
- geerlingguy.docker
- geerlingguy.pip
- fresh_install
- docker_swarm
The fresh_install role does a lot of heavy lifting:
- Locale setup
- User management (adds my SSH key)
- System updates
- Package installation
- LVM volume creation
- Python setup
- ZSH configuration
- SSH banner (because why not)
- CephFS mount
- App data directories
- NFS mounts (if needed)
Phase 2: Deploy Portainer #
- name: Post-Swarm Setup (Portainer)
hosts: swarm_managers[0] # Only on the leader
roles:
- portainer
Only runs on the first manager—Portainer handles the rest.
The Docker Swarm Role #
Here’s the fun part—initializing the swarm. Ansible does this intelligently:
- First manager (
dkr-srv-1): Runsdocker swarm init - Other managers: Grab the join token and join as managers
- Workers: Grab the worker token and join as workers
- name: Initialize Docker Swarm on the first manager
command: docker swarm init --advertise-addr {{ ansible_host }}
when: inventory_hostname == groups['swarm_managers'][0]
- name: Join other managers to the swarm
command: docker swarm join --token {{ swarm_manager_token }} ...
when: inventory_hostname in groups['swarm_managers']
It also creates the shared Docker data directory on CephFS for persistent volumes.
Running It #
# Preview what will change
task ansible:site:plan
# Apply the configuration
task ansible:site:apply
Or run specific tags if you only need to update certain parts:
ansible-playbook site.yml --tags docker
ansible-playbook site.yml --tags swarm
ansible-playbook site.yml --tags portainer
The Result #
After Ansible finishes, I’ve got:
- Docker installed on all nodes
- All nodes joined to the swarm (3 managers, 3 workers)
- Portainer running at
portainer.krapulax.net - CephFS mounted at
/mnt/cephfs - My user account with SSH access everywhere
Next up: deploying actual applications to the swarm!