Skip to content

Commit 3b81494

Browse files
committed
Adding post: Ansible roles to deploy Ubuntu servers
1 parent bdd6b1a commit 3b81494

File tree

9 files changed

+337
-0
lines changed

9 files changed

+337
-0
lines changed
Lines changed: 337 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,337 @@
1+
---
2+
author: dmac
3+
title: "Ansible roles to deploy Ubuntu servers"
4+
date: 2025-12-04 00:00:00 +100
5+
categories: [tools]
6+
tags: [automation, ansible]
7+
image:
8+
path: "/assets/img/posts/2025-12-04/img-preview.webp"
9+
---
10+
11+
12+
13+
If you've ever had to manually configure servers one by one, you know how painful it is to maintain consistency across users, groups, SSH keys, Docker, ZSH, editors, and all the other tools you rely on.
14+
15+
In this article, we'll use Ansible to automate the deployment of 6 Ubuntu servers with identical configurations and tooling.
16+
17+
18+
## Use cases
19+
20+
This week I needed to build 10 Ubuntu VMs to deploy new services in my lab.
21+
22+
My motivations to automate this deployment were:
23+
- Keep a consistent environment between all my servers that will match the tools I use on my local machine.
24+
- Save time by not having to manually configure all these VMs myself
25+
- Have my code in version control to add more features in the future with minimal risk.
26+
- Reduce the time it takes me to rebuild one of those VMs in the future.
27+
28+
Beyond my specific scenario, this approach is valuable for:
29+
30+
- **Homelab enthusiasts** who want to quickly rebuild or add new servers with preferred tooling already configured
31+
- **Network engineers** testing automation tools like containerlab, infrahub, netbox, or nautobot who need fresh environments regularly
32+
- **DevOps teams** managing development servers where everyone needs the same shell, editor, and CLI tools
33+
- **Certification prep** (CCNA, CCNP, CKA, etc.) where you need consistent lab environments you can tear down and rebuild quickly
34+
- **Training and workshops** where all participants need identical setups
35+
36+
37+
## Project overview
38+
39+
All the code shown in this example lives in the [dmac-ansible](https://github.com/danielmacuare/dmac-ansible) repo, where you'll find detailed documentation and additional guidance.
40+
41+
This repo currently provides 5 main roles:
42+
43+
![Ansible-Ubuntu Roles](/assets/img/posts/2025-12-04/fig1-roles-description.webp){: w="900" h="600" }
44+
_Figure 1 - Ansible Project Roles_
45+
46+
- **[ubuntu](https://github.com/danielmacuare/dmac-ansible/tree/main/roles/ubuntu)** - The base role that creates users, groups, SSH keys, and installs APT and Snap packages.
47+
- **[zerotier](https://github.com/danielmacuare/dmac-ansible/tree/main/roles/zerotier)** - Installs ZeroTier and joins your specified network.
48+
- **[zsh](https://github.com/danielmacuare/dmac-ansible/tree/main/roles/zsh)** - Installs ZSH with Oh-My-Zsh and the Powerlevel10k theme, plus useful plugins like fzf-tab and zsh-autosuggestions.
49+
- **[docker](https://github.com/geerlingguy/ansible-role-docker)** - Installs Docker CE, Docker Compose, and manages Docker users. Huge thanks to [Jeff Geerling](https://www.jeffgeerling.com/) for maintaining this excellent role.
50+
- **[neovim](https://github.com/danielmacuare/dmac-ansible/tree/main/roles/neovim)** - Installs Neovim with Lazy as the plugin manager and Mason for LSP management, along with additional plugins for a great editing experience.
51+
52+
> Note: Only use the neovim role if you want to deploy a custom configuration. If you just need neovim installed, add it to the `apt_packages` list in the ubuntu role instead.
53+
{: .prompt-tip }
54+
55+
## Getting started
56+
57+
We'll use Ansible to configure the servers once they're created and reachable via SSH.
58+
59+
We will divide this goal in 3 main tasks:
60+
61+
1. Create 6 VMs to test our playbook.
62+
2. Clone the repository and update the inventory, vars, playbook, and other necessary files.
63+
3. Configure all 6 servers in a single Ansible run.
64+
65+
### 1 - Creating the VMs
66+
67+
For this step, I'm using Orbstack since I'm on MacOS. Check out the [Orbstack Installation Docs](https://docs.orbstack.dev/quick-start#installation) if you need to install it.
68+
69+
Alternative options:
70+
- **Windows**: Use WSL2 or VirtualBox
71+
- **Linux**: KVM or LXC work great
72+
73+
Once Orbstack is installed, create the 6 VMs:
74+
75+
![Orbstack VMs](/assets/img/posts/2025-12-04/fig2-orb-vms.webp){: w="900" h="600" }
76+
_Figure 2 - 6 x Ubuntu VMs_
77+
78+
### 2 - Cloning the repo and updating files
79+
80+
#### 2.1 - Clone and install dependencies
81+
```bash
82+
git clone https://github.com/danielmacuare/dmac-ansible.git
83+
cd dmac-ansible
84+
uv sync
85+
uv run ansible-galaxy install -r requirements.yml -p ./roles
86+
```
87+
88+
89+
#### 2.2 - Customize your deployment
90+
91+
> For a detailed step-by-step guide on updating all necessary files before running the playbook, check out the [Configuration Guide](https://github.com/danielmacuare/dmac-ansible/blob/main/docs/configuration.md#initial-setup)
92+
{: .prompt-tip }
93+
94+
You'll need to modify these files:
95+
96+
- **[vars.yaml](https://github.com/danielmacuare/dmac-ansible/blob/main/inventories/group_vars/all/vars.yaml)** - Stores all variables used by each role. This controls how your servers are configured.
97+
- **[vault.yaml](https://github.com/danielmacuare/dmac-ansible/blob/main/inventories/group_vars/all/example.vault.yaml)** - Stores the password for encrypting/decrypting sensitive Ansible files containing secrets.
98+
- **[inventory.ini](https://github.com/danielmacuare/dmac-ansible/blob/main/inventories/inventory.ini)** - Defines which targets are available in each Ansible group.
99+
- **[ubuntu.yml](https://github.com/danielmacuare/dmac-ansible/blob/main/playbooks/ubuntu.yml)** - Controls which roles get applied to target servers. Mix and match as needed. The ubuntu role (base) is recommended; all others are optional.
100+
- **[dmac.pub](https://github.com/danielmacuare/dmac-ansible/blob/main/roles/ubuntu/files/dmac.pub)** - Your SSH public key. The filename should match the username configured in `vars.yaml`.
101+
102+
#### 2.2.1 - Example configuration
103+
104+
Here's what my configuration looks like:
105+
106+
```bash
107+
---
108+
# UBUNTU ROLE
109+
auth_key_dir: "{{ vault_auth_dir_key }}"
110+
111+
users:
112+
- username: dmac
113+
sudo_access: true
114+
ssh_access: true
115+
ssh_pub_key: "{{ lookup('file', 'dmac.pub') }}"
116+
ssh_pass: "{{ vault_dmac_ssh_pass }}"
117+
custom_alias_file: "aliases_dmac.j2"
118+
custom_functions_file: "functions_dmac.j2"
119+
- username: svmt
120+
sudo_access: true
121+
ssh_access: true
122+
ssh_pub_key: "{{ lookup('file', 'svmt.pub') }}"
123+
ssh_pass: "{{ vault_svmt_ssh_pass }}"
124+
custom_alias_file: "aliases_svmt.j2"
125+
custom_functions_file: "functions_svmt.j2"
126+
127+
apt_packages:
128+
- whois
129+
- sshpass
130+
- nmap
131+
- bat
132+
- ripgrep
133+
- zoxide
134+
- jq
135+
- fzf
136+
- tldr
137+
- duf
138+
- btop
139+
- tree
140+
- tcpdump
141+
- openssh-server # ORB VMs won't install it by default
142+
143+
snap_packages:
144+
- name: rustscan # NMAP Faster Alternative
145+
classic: false
146+
version: "latest/stable"
147+
- name: termshark # Wireshark-like TUI
148+
classic: false
149+
version: "latest/stable"
150+
151+
152+
# ZEROTIER ROLE
153+
zerotier_api_accesstoken: "{{ vault_zerotier_api_accesstoken }}"
154+
zerotier_api_url: "https://api.zerotier.com/api/v1"
155+
zerotier_network_id: "83048a0632608eee"
156+
157+
# ZSH ROLE
158+
zsh_users:
159+
- username: dmac
160+
oh_my_zsh:
161+
theme: "powerlevel10k/powerlevel10k"
162+
plugins:
163+
- git
164+
update_mode: auto
165+
update_frequency: 5
166+
write_zshrc: true
167+
168+
zsh_p10k_users:
169+
- dmac
170+
171+
zsh_plugins:
172+
- "zsh-autosuggestions"
173+
- "zsh-fast-syntax-highlighting"
174+
- "fzf-tab"
175+
- "zsh-completions"
176+
177+
zsh_plugins_path: "{{ item.username }}/.oh-my-zsh/custom/plugins"
178+
179+
# DOCKER ROLE
180+
docker_edition: 'ce'
181+
docker_packages:
182+
- "docker-{{ docker_edition }}"
183+
- "docker-{{ docker_edition }}-cli"
184+
- "docker-{{ docker_edition }}-rootless-extras"
185+
- "containerd.io"
186+
- docker-buildx-plugin
187+
docker_packages_state: present
188+
docker_obsolete_packages:
189+
- docker
190+
- docker.io
191+
- docker-engine
192+
- docker-doc
193+
- docker-compose
194+
- docker-compose-v2
195+
- podman-docker
196+
- containerd
197+
- runc
198+
199+
# Docker Compose Plugin options.
200+
docker_install_compose_plugin: true
201+
docker_compose_package: docker-compose-plugin
202+
docker_compose_package_state: present
203+
204+
# Docker Compose options.
205+
docker_install_compose: false # Prevents legacy binary
206+
docker_compose_version: "v2.40.3" # Latest as of 11/2025
207+
docker_compose_path: /usr/local/bin/docker-compose
208+
209+
# A list of users who will be added to the docker group.
210+
docker_users:
211+
- dmac
212+
- svmt
213+
214+
215+
# NEOVIM ROLE
216+
neovim_set_default_editor: true
217+
neovim_deploy_config: true
218+
neovim_users:
219+
- username: dmac
220+
221+
```
222+
{: file="group_vars/all/vars.yaml" }
223+
224+
```bash
225+
[all:vars]
226+
ansible_user=dmac
227+
ansible_python_interpreter=/usr/bin/python3
228+
229+
[ubuntu_hosts]
230+
ub01-2204 ansible_host=ub01-2204@orb ansible_user=ub01-2204 zerotier_hosted_on=orb-ub01-2204
231+
ub02-2204 ansible_host=ub02-2204@orb ansible_user=ub02-2204 zerotier_hosted_on=orb-ub02-2204
232+
ub03-2404 ansible_host=ub03-2404@orb ansible_user=ub03-2404 zerotier_hosted_on=orb-ub03-2404
233+
ub04-2404 ansible_host=ub04-2404@orb ansible_user=ub04-2404 zerotier_hosted_on=orb-ub04-2404
234+
ub05-2504 ansible_host=ub05-2504@orb ansible_user=ub05-2504 zerotier_hosted_on=orb-ub05-2504
235+
ub06-2504 ansible_host=ub06-2504@orb ansible_user=ub06-2504 zerotier_hosted_on=orb-ub06-2504
236+
237+
[proxmox]
238+
max ansible_host=max-01
239+
```
240+
{: file="inventories/inventory.ini" }
241+
242+
```bash
243+
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILLLdt13LmYyZmOn4bwbgVctSuejlc7iPAE46s9KlePs
244+
245+
```
246+
{: file="roles/ubuntu/files/dmac.pub" }
247+
248+
```bash
249+
# Example File - FAKE CREDS
250+
vault_auth_dir_key: "/etc/ssh/authorized_keys"
251+
vault_zerotier_api_accesstoken: "Put your Zero Tier API Token HERE"
252+
# See docs/password-generation.md for instructions on how to generate the password hashes below
253+
vault_dmac_ssh_pass: "$6$Vqo2MXAAQt6z0KxG$2/XYsLfVbnLRhMveU9YV2lwzxnTRD8gk3jnjKWc894lApaFlJHhAo/m.FV/pqbD3EXV26Iia9otiiKBKTmXDCS"
254+
vault_svmt_ssh_pass: "$6$U4CbBz7gOuK11hjd$YOtOM6s5ftzqRDr408qsXDnyRBJ4a/lQLFufb1LJM1Q7R9ATcG/CvkybfJhRpaSDb5Z1GdEwsJIvZhyCnnADEX"
255+
```
256+
{: file="group_vars/all/vault.yaml" }
257+
258+
**- Playbook**
259+
```bash
260+
---
261+
- name: Configure Ubuntu Development Servers
262+
hosts: ubuntu_hosts
263+
gather_facts: true
264+
roles:
265+
- { role: ubuntu, tags: ["ubuntu"] }
266+
- { role: zsh, tags: ["zsh"] }
267+
- { role: geerlingguy.docker, tags: ["docker"], become: true }
268+
- { role: zerotier, tags: ["zerotier"] }
269+
- { role: neovim, tags: ["neovim"] }
270+
```
271+
{: file="playbooks/ubuntu.yaml" }
272+
273+
274+
### 3 - Running the Playbook
275+
276+
Once you've updated all the variables and files, it's time to execute the playbook:
277+
278+
```bash
279+
# Run all roles
280+
uv run ansible-playbook playbooks/ubuntu.yml -K
281+
282+
# Or run specific roles only
283+
uv run ansible-playbook playbooks/ubuntu.yml -K --tags ubuntu
284+
uv run ansible-playbook playbooks/ubuntu.yml -K --tags zerotier
285+
uv run ansible-playbook playbooks/ubuntu.yml -K --tags zsh
286+
uv run ansible-playbook playbooks/ubuntu.yml -K --tags docker
287+
```
288+
289+
> I had to run the playbook twice since it froze halfway through on the first attempt. This was likely due to my machine (hosting Orbstack and all 6 VMs) only having 16GB of RAM.
290+
{: .prompt-warning }
291+
292+
#### 3.1 - Results
293+
294+
After running the playbook, all 6 servers were successfully configured. Here's what the deployment looks like:
295+
296+
![Ansible Playbook Execution](/assets/img/posts/2025-12-04/fig3-results.webp){: w="900" h="600" }
297+
_Figure 3 - Ansible playbook completed successfully on the second attempt_
298+
299+
![Neovim](/assets/img/posts/2025-12-04/fig4-neovim.webp){: w="900" h="600" }
300+
_Figure 4 - Neovim installed and configured with all plugins_
301+
302+
![ZSH](/assets/img/posts/2025-12-04/fig5-zsh.webp){: w="900" h="600" }
303+
_Figure 5 - ZSH installed with useful plugins like fzf-tab_
304+
305+
![Zerotier](/assets/img/posts/2025-12-04/fig6-zerotier.webp){: w="900" h="600" }
306+
_Figure 6 - ZeroTier installed and connected_
307+
308+
![Docker Verification](/assets/img/posts/2025-12-04/fig7-docker.webp){: w="900" h="600" }
309+
_Figure 7 - Docker and Docker Compose installed_
310+
311+
312+
## Closing thoughts
313+
314+
The modularity of these roles allows us to create different playbooks to target different sets of servers. For example:
315+
316+
1 - Ubuntu base playbook
317+
- Ubuntu role: To only apply the base ubuntu config (Users, groups, SSH Keys, etc)
318+
319+
2 - Ubuntu + Docker Playbook
320+
321+
3 - Ubuntu + Docker + Tools + Remote Access
322+
- Ubuntu role
323+
- Docker role
324+
- Tools
325+
- ZSH
326+
- Neovim
327+
- Zerotier role
328+
329+
330+
After completing this automation we can now:
331+
- Replace VMs effortlessly: If one VM dies or gets too annoying we can quickly create a new one and configure it with the playbook.
332+
- Get a consistent set of tools and the same experience across all our servers.
333+
- Save time when creating new servers.
334+
335+
If you're managing more than a couple of servers or frequently spinning up new VMs for testing, I highly recommend giving this approach a try. The initial setup takes some time, but once you have your vars and inventory configured, deploying new servers becomes almost effortless.
336+
337+
Feel free to fork the [dmac-ansible repo](https://github.com/danielmacuare/dmac-ansible) and adapt it to your needs. I'd love to hear if there are any improvements you'd suggest!
234 KB
Loading
40.5 KB
Loading
101 KB
Loading
93.8 KB
Loading
178 KB
Loading
4.94 KB
Loading
15.3 KB
Loading
28.9 KB
Loading

0 commit comments

Comments
 (0)