Skip to main content

Orchestrate Your Systems with Ansible Playbooks - How to Home Lab Part 10



Comments: counting...

In this segment, you will learn how to set up and use Ansible to manage servers efficiently.

What is Ansible

Ansible is an administrative tool that aims to make server management easier by using declarative and idempotent configuration files.

Declarative VS Imparative

Declarative means you are expressing the what without the how, this makes things easier to read and make sense of. Below is a contrived example of the difference:


  1. The file settings.conf should include the line some_setting=true


  1. Check if settings.conf exists
  2. Check if settings.conf includes the line some_setting
  3. If not, add the line some_setting=true
  4. Else check if the value of some_setting is true
  5. If not, change the line to some_setting=true


For declarative to work, the operations must be idempotent.

In simple terms, idempotent configurations don’t need to know about the current state, they must handle all possible starting states and ensure that whatever state is declared will be the final state upon completion. This means that you can define the state you want, and applying the same configuration again shouldn’t make any changes that aren’t needed.

For example, if we want to ensure that a package is installed, applying the configuration should either install the package if it isn’t already, or do nothing at all.

Note: You may see a warning in the Ansible documentation for some plugins about not being idempotent, this means you have to handle state checking yourself, this is pretty rare but worth mentioning


Picture this; something is failing on your server, and you’ve spent hours combing over configuration files and scanning logs trying to figure out why and get it working again. In this case you’re treating your server as a pet, it’s special and irreplaceable. Wouldn’t it be better if you could treat servers as livestock - expendable and easily replaced?

For example; something is failing on your server, so you just re-apply the configuration. It’s fast and easy, and if that won’t cure it, you can put the poor thing down, spin up a new one, and copy over any mutable data. Sure, you still need to investigate, but you’re no longer under pressure because the service is up and running on a new host or virtual machine.

Another benefit is that backups will be faster, cheaper, and easier to manage if you only need to back up your data. There is no need to back up an entire server when all the configuration you need is already tucked away in a git repository. Save all that pricey off-site storage space for your mutable data instead of filling it with the entire contents of a hard drive.


First, you should install Ansible, on most distributions you can use pip to install it, but there are some more detailed instructions if you need them.

Create a folder to use, I went with one called ansible in my home directory, but you can keep it and call it whatever you like, you’ll just have to keep that in mind when creating the Ansible config file.

Now we need a configuration file called ansible.cfg, there are a lot of configuration options for Ansible and we’re only using a few here, you should check out the Ansible documentation as well, they did a great job with that.

The inventory file will list all of your hosts, we will get into that and roles in a bit, first let’s address that vault password file.

Ansible Vault is a utility for securely storing sensitive information, like passwords and such. The “vault” files are encrypted so they can be safely stored in version control, like GitHub for example.

Create the file ~/.ansible_vault (you can call this something different if you want, just change it in ansible.cfg to match), in this file you should enter a very long random string, save it in your password manager or write it down and keep it somewhere safe.

Once that is done, working with vaults will be seamless, you won’t be asked to enter the password when working with vault files, Ansible will read it from that file automatically.


Create the file inventory.yml, here we will define all hosts managed by Ansible, and optionally some global and host-specific variables. Don’t go crazy with variables here, there are better ways to store host-specific variables.

Vaults and Variables

Let’s create a global vault first, if we wanted unencrypted variables we could use the same filename without Ansible Vault, it would work the same way.

Now we can add global variables that are secure, we can do the same for other host groups like webservers, etc. in the inventory file.

For host variables, we just put the file in a different place.

Here we can add variables for third_host.


Now we can write playbooks to configure one or more of the hosts in our inventory. Let’s start with third_host as a simple example.

We’ll start with main.yml, I like to have one main file and import tasks from other files to keep things easy to parse, but you can also put the tasks inline under the tasks key instead.

Note: tags can be used to only run specific sets of tasks, e.g. ansible-playbook ~/ansible/playbooks/third_host/main.yml --tags firewall,some_other_tag. And tasks tagged with never will only run if another tag on that task is specified in the commands tag list.

Then create the task for configuring UFW, this is a good example of looping and list handling in Ansible; anything defined in with_items will be looped over in the rule and each item in the list will be available as {{ item }}.

Note: the line become: true means “run this task with sudo


The files directory is for, well, files. These are static files, maybe certificates, or configuration files although it usually makes more sense to use lineinfile to manipulate the configuration file that exists on the remote host, again check out the Ansible documentation, lots of good stuff in there.

Another example of a task, this one ensures the directory /etc/docker exists, and ensures the file daemon.json exists and matches the copy we have locally. The {{ inventory_dir }} variable is one of many built-in variables in Ansible, and points to the inventory file path, in this case that would be ~/ansible/files/third_host/docker/daemon.json.


Templates are for dynamic files, very similar to files, but you can use Ansible variables in the files, and they should have a .j2 (Jinja2) file extension.

Here is an example template for a Traefik config file that uses the variable admin_email.

Then we can use that template in a task, and any variables will be replaced by their files in the output file.


This is where Ansible gets really powerful really quickly, roles are re-usable sets of playbooks that can be applied to multiple top-level playbooks. There is also a public repository of roles called Ansible Galaxy, where you can share your roles or use roles created by others.

Creating a Role

There is a handy command to scaffold out a new role for Ansible, let’s try that out now.

We now have a bunch of new files ready to go for our new role, these are pretty well documented by Ansible, but here’s a quick overview:

The variables in the defaults directory can be overwritten in other places like the playbook and host or group variables, as you might expect.

You’ll see a lot of patterns repeated here because a role is kind of like a self-contained playbook of its own.


One new directory that is significant here is handlers, these are like tasks, but a handler is run once at the end of the role execution. This is useful for things like restarting services when configuration files change, if three configuration files are changed and they are all tied to the same handler, the service will restart once after all the changes were made.

We can now call that handler from a task using the notify key, in this case we are using lineinfile to make sure certain configuration lines are set the way we want them to be.

Assign Roles to a Playbook

Now all we need to do is declare any roles we want to be applied in our playbook.

A Few More Neat Tricks

I’ve only just scratched the surface of what Ansible can do here, and I’ve got a couple more necessities to share, but there is so much more! I encourage you to poke around and experiment!


Register lets you assign a variable dynamically, for example if you wanted to know if a task changed or was already applied.

You can then use that variable in a later task. In this case we want to force recreate the Traefik container if the configuration was changed, otherwise only recreate it if Docker thinks it’s necessary.

You can also use these dynamic variables with the when key.


Arguably the most important thing to know is how to log things out to the console! Fortunately, that’s pretty easy.


Now you’ve seen what Ansible is capable of, or at least a glimpse of it, and how to get started. I hope you found this useful!

If you want to see a working example I’ve set up a demo playbook that runs a Traefik instance for Docker apps, but the catch is you might have to do a little research to get it working, let me know how you do.