Server Automation with Ansible: Infrastructure as Code
Ansible installation, inventory file, playbook writing, role structure, variables, encrypted vault, ad-hoc commands, and server configuration automation. Step-by-step guide.
Table of Contents
Server Automation with Ansible: Infrastructure as Code
Ansible is an open-source tool that lets you manage server configuration, application deployment, and task automation as code. Thanks to its agentless architecture, it only connects to target servers via SSH.
What is Ansible?
Key features of Ansible:
- Agentless: No additional software installation required on target servers
- Idempotent: Running the same playbook multiple times produces the same result
- YAML-based: Easy to read and write
- Large module library: 3000+ ready-made modules
Ansible connects from the control node to target servers over SSH. WinRM is used for Windows servers.
Ansible Installation
Ubuntu/Debian
# Add PPA repository
sudo apt update
sudo apt install software-properties-common -y
sudo add-apt-repository --yes --update ppa:ansible/ansible
sudo apt install ansible -y
# Check version
ansible --version
RHEL/CentOS/AlmaLinux
# Add EPEL repository
sudo dnf install epel-release -y
sudo dnf install ansible -y
# Alternative: install via pip
pip3 install ansible
Installation via pip (Recommended)
# Create Python virtual environment
python3 -m venv ansible-env
source ansible-env/bin/activate
# Install Ansible and additional tools
pip install ansible ansible-lint
# Check version
ansible --version
Inventory File
Inventory defines the servers Ansible will manage:
INI Format
# /etc/ansible/hosts or custom inventory file
# Single server
web1.example.com
# Group definition
[webservers]
web1.example.com
web2.example.com ansible_port=2222
192.168.1.10 ansible_user=ubuntu
[databases]
db1.example.com
db2.example.com
# Group variables
[webservers:vars]
ansible_user=deploy
ansible_python_interpreter=/usr/bin/python3
# Parent group
[production:children]
webservers
databases
YAML Format
# inventory.yml
all:
children:
webservers:
hosts:
web1.example.com:
ansible_port: 22
web2.example.com:
ansible_port: 2222
vars:
ansible_user: deploy
http_port: 80
databases:
hosts:
db1.example.com:
db2.example.com:
vars:
ansible_user: dbadmin
db_port: 5432
Ad-hoc Commands
Run commands without writing a playbook for quick tasks:
# Ping test
ansible all -m ping
ansible webservers -m ping -i inventory.yml
# Run command
ansible all -m command -a "uptime"
ansible webservers -m shell -a "df -h"
# Copy file
ansible webservers -m copy -a "src=/local/file dest=/remote/path"
# Install package
ansible webservers -m apt -a "name=nginx state=present" --become
# Manage service
ansible webservers -m service -a "name=nginx state=started enabled=yes" --become
# Create/delete file
ansible all -m file -a "path=/tmp/test state=touch"
ansible all -m file -a "path=/tmp/test state=absent"
# Gather facts
ansible web1.example.com -m setup
ansible all -m setup -a "filter=ansible_os_family"
Writing Playbooks
Basic Playbook Structure
# site.yml
---
- name: Configure web servers
hosts: webservers
become: yes # use sudo
vars:
http_port: 80
max_clients: 200
tasks:
- name: Install Nginx
apt:
name: nginx
state: present
update_cache: yes
- name: Copy Nginx configuration
template:
src: templates/nginx.conf.j2
dest: /etc/nginx/nginx.conf
owner: root
group: root
mode: '0644'
notify: Restart Nginx
- name: Start Nginx service
service:
name: nginx
state: started
enabled: yes
handlers:
- name: Restart Nginx
service:
name: nginx
state: restarted
Tasks, Handlers and Roles
# Conditional tasks
- name: Install package on Ubuntu
apt:
name: "{{ item }}"
state: present
loop:
- nginx
- curl
- git
when: ansible_os_family == "Debian"
- name: Install package on RHEL
dnf:
name: "{{ item }}"
state: present
loop:
- nginx
- curl
- git
when: ansible_os_family == "RedHat"
# Create users with loop
- name: Create users
user:
name: "{{ item.name }}"
groups: "{{ item.groups }}"
state: present
loop:
- { name: 'alice', groups: 'sudo' }
- { name: 'bob', groups: 'www-data' }
# Register and condition
- name: Check Nginx version
command: nginx -v
register: nginx_version
ignore_errors: yes
- name: Install Nginx if not installed
apt:
name: nginx
state: present
when: nginx_version.rc != 0
Variables and Facts
# group_vars/webservers.yml
http_port: 80
https_port: 443
app_user: www-data
app_dir: /var/www/html
# host_vars/web1.example.com.yml
ansible_host: 192.168.1.10
server_name: web1.example.com
# Variable usage in playbook
- name: Create application directory
file:
path: "{{ app_dir }}"
state: directory
owner: "{{ app_user }}"
mode: '0755'
# Using facts
- name: Show system information
debug:
msg: |
OS: {{ ansible_distribution }} {{ ansible_distribution_version }}
CPU: {{ ansible_processor_vcpus }} cores
RAM: {{ ansible_memtotal_mb }} MB
IP: {{ ansible_default_ipv4.address }}
Ansible Vault
To encrypt sensitive data:
# Create encrypted file
ansible-vault create secrets.yml
# Encrypt existing file
ansible-vault encrypt vars/passwords.yml
# Edit encrypted file
ansible-vault edit secrets.yml
# View encrypted file
ansible-vault view secrets.yml
# Remove encryption
ansible-vault decrypt vars/passwords.yml
# Change password
ansible-vault rekey secrets.yml
# Run playbook with vault
ansible-playbook site.yml --ask-vault-pass
ansible-playbook site.yml --vault-password-file ~/.vault_pass
# secrets.yml (encrypted content)
db_password: "secret_database_password"
api_key: "secret_api_key"
ssl_cert_key: |
-----BEGIN PRIVATE KEY-----
...
-----END PRIVATE KEY-----
Role Structure
# Create role
ansible-galaxy init roles/nginx
# Role directory structure
roles/nginx/
├── defaults/
│ └── main.yml # Default variables
├── files/
│ └── nginx.conf # Static files
├── handlers/
│ └── main.yml # Handlers
├── meta/
│ └── main.yml # Role metadata
├── tasks/
│ └── main.yml # Main tasks
├── templates/
│ └── vhost.conf.j2 # Jinja2 templates
└── vars/
└── main.yml # Variables
# roles/nginx/tasks/main.yml
---
- name: Install Nginx
package:
name: nginx
state: present
- name: Configure Nginx
template:
src: vhost.conf.j2
dest: /etc/nginx/sites-available/{{ site_name }}
notify: Reload Nginx
- name: Enable site
file:
src: /etc/nginx/sites-available/{{ site_name }}
dest: /etc/nginx/sites-enabled/{{ site_name }}
state: link
notify: Reload Nginx
Example Playbooks
Nginx Installation Playbook
---
- name: Nginx web server installation
hosts: webservers
become: yes
vars:
domain: example.com
web_root: /var/www/{{ domain }}
tasks:
- name: Install Nginx
apt:
name: nginx
state: present
update_cache: yes
- name: Create web directory
file:
path: "{{ web_root }}"
state: directory
owner: www-data
group: www-data
mode: '0755'
- name: Create sample index.html
copy:
content: "<h1>Hello from {{ inventory_hostname }}</h1>"
dest: "{{ web_root }}/index.html"
owner: www-data
- name: Configure virtual host
template:
src: vhost.conf.j2
dest: /etc/nginx/sites-available/{{ domain }}
notify: Reload Nginx
- name: Enable virtual host
file:
src: /etc/nginx/sites-available/{{ domain }}
dest: /etc/nginx/sites-enabled/{{ domain }}
state: link
notify: Reload Nginx
- name: Disable default site
file:
path: /etc/nginx/sites-enabled/default
state: absent
notify: Reload Nginx
- name: Allow Nginx through UFW
ufw:
rule: allow
name: 'Nginx Full'
handlers:
- name: Reload Nginx
service:
name: nginx
state: reloaded
User Management Playbook
---
- name: User management
hosts: all
become: yes
vars_files:
- vars/users.yml
tasks:
- name: Create sudo group
group:
name: sudo
state: present
- name: Create users
user:
name: "{{ item.username }}"
comment: "{{ item.full_name }}"
groups: "{{ item.groups | join(',') }}"
shell: /bin/bash
create_home: yes
state: present
loop: "{{ users }}"
- name: Add SSH public key
authorized_key:
user: "{{ item.username }}"
key: "{{ item.ssh_key }}"
state: present
loop: "{{ users }}"
when: item.ssh_key is defined
- name: Grant sudo access
copy:
content: "{{ item.username }} ALL=(ALL) NOPASSWD:ALL"
dest: /etc/sudoers.d/{{ item.username }}
mode: '0440'
loop: "{{ users }}"
when: item.sudo | default(false)
Running Playbooks
# Basic run
ansible-playbook site.yml
# With specific inventory
ansible-playbook -i inventory.yml site.yml
# Specific host or group
ansible-playbook site.yml --limit webservers
ansible-playbook site.yml --limit web1.example.com
# Dry-run (check without making changes)
ansible-playbook site.yml --check
# Verbose mode
ansible-playbook site.yml -v
ansible-playbook site.yml -vvv
# Run specific tags
ansible-playbook site.yml --tags "nginx,ssl"
ansible-playbook site.yml --skip-tags "debug"
# Extra variables
ansible-playbook site.yml -e "env=production version=1.5"
Conclusion
Ansible is one of the most accessible ways to manage server automation as code. With its agentless architecture, large module library, and YAML-based structure, you can easily manage both small and large-scale infrastructure. Keep sensitive data secure with Vault while creating reusable configurations with roles.
Related Articles
Docker Compose Guide: Multi-Container Application Management
Define and manage multi-container applications with Docker Compose: service dependencies, environment variables, health checks, scaling, and production deployment. Comprehensive guide.
Automated Deployment with GitHub Actions: CI/CD Pipeline Setup
Build CI/CD pipelines with GitHub Actions: Docker build and push, SSH server deployment, test automation, secrets management, and workflow optimization.
Caddy Web Server Setup: Automatic HTTPS and Reverse Proxy
Caddy web server installation, Caddyfile configuration, automatic Let's Encrypt SSL, reverse proxy, static site hosting, PHP-FPM integration, and performance settings.