Skip to main content
Back to Category

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.

Read time: 20 min DevOps & Automation
ansibleautomationinfrastructure as codedevopsplaybookyamllinux

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

hljs bash
# 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

hljs bash
# Add EPEL repository
sudo dnf install epel-release -y
sudo dnf install ansible -y

# Alternative: install via pip
pip3 install ansible
hljs bash
# 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

hljs ini
# /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

hljs yaml
# 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:

hljs bash
# 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

hljs yaml
# 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

hljs yaml
# 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

hljs yaml
# 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
hljs yaml
# 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:

hljs bash
# 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
hljs yaml
# 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

hljs bash
# 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
hljs yaml
# 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

hljs yaml
---
- 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

hljs yaml
---
- 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

hljs bash
# 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.