Skip to content

Workflow

I use automation tools as much as possible but a lot of managing the infrastructure is done manually.

Terraform is used to manage the network and hardware, and I run it manually when I want to change something.

To change the network settings, to add a new subdomian for example, I edit the Terraform code in terraform/dns.tf.

I then run

Terminal window
terraform apply

to change the network settings.

To change the hardware I edit the terraform/web.tf file.

I would do this to change the settings of the Pi, or to change the Pi to a VPS.

I then run

Terminal window
terraform apply

to change the hardware.

Ansible is used to manage the software, and I run it manually when I want to change something.

To change the software, to add a new website to host for example, I edit the Ansible code.

I then run

Terminal window
ansible-playbook site.yaml

to apply the site.yaml playbook which contains the whole setup. The name site is just an old name I keep using for the “main” Ansible playbook.

I can run only parts of the setup using tags:

Terminal window
ansible-playbook site.yaml --tags web, caddy

which I would do if I added a new site to deploy (part of the web role) and changed the Caddyfile to serve it (part of the caddy role).

The playbook and the roles it uses are set up so that I don’t need to edit them directly to make simple changes like adding a new site to host.

To make a simple change, like adding a new site to host, I would edit the ansible/group_vars/all/main.yaml file:

ansible/group_vars/all/main.yaml
web_sites_root: "/srv/www"
web_sites:
- "paultibbetts.uk"
- "infra.paultibbetts.uk"
- "dev.paultibbetts.uk"
web_deploy_user: "deploy"
web_deploy_home: "/home/deploy"
web_deploy_ssh_keys:
- "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIK+5ESd3EGl8/Wm3VDkQ4PKTp4uDLrEDXpdgYD1nuKmV"
web_group: "deploy"
web_keep_releases: 3
caddy_caddyfile_template: "{{ playbook_dir }}/templates/Caddyfile.j2"
caddy_install_method: download
caddy_plugins:
- github.com/caddy-dns/cloudflare
caddy_manage_systemd_env_file: true
caddy_systemd_env:
CF_API_TOKEN: "{{ vault_cf_api_token }}"

Ansible is written in Python.

I use uv to install and manage Python versions and dependencies, because it’s reliable and 100x faster than Pip, and so the command to run the playbook is:

Terminal window
uv sync
uv run ansible-playbook site.yaml

I use make to collect the commands that I use when working with Ansible.

These are listed in a Makefile as a reference and also to run using make <target>.

Makefile
.PHONY: setup deps deps-upgrade site
GALAXY_FLAGS ?=
PLAYBOOK_FLAGS ?=
setup:
@command -v uv >/dev/null || \
(echo "uv is not installed" && exit 1)
uv sync
deps: setup
uv run ansible-galaxy role install -r requirements.yaml -p roles/galaxy $(GALAXY_FLAGS)
uv run ansible-galaxy collection install -r requirements.yaml $(GALAXY_FLAGS)
deps-upgrade: setup
uv run ansible-galaxy role install -r requirements.yaml -p roles/galaxy --force $(GALAXY_FLAGS)
uv run ansible-galaxy collection install -r requirements.yaml --force $(GALAXY_FLAGS)
site: deps
uv run ansible-playbook site.yaml $(PLAYBOOK_FLAGS)

where site is the recipe that runs the Ansible playbook.

Deployments happen when the main branch of the code for the website gets updated.

I can trigger a deployment by making a change to one of the websites and it will be live a few minutes later.

This process is fully automated and deployed websites should always be in sync with their code.