Ansible's built-in modules are the vocabulary of configuration management — they are the named, idempotent, documented operations that playbooks compose to achieve desired system state. Where shell scripts string together raw commands that may or may not be idempotent, modules encode idempotency into their implementation: the 'ansible.builtin.package' module checks whether a package is already installed before attempting installation, the 'ansible.builtin.user' module checks whether a user already exists before creating them, and the 'ansible.builtin.service' module checks current service state before starting or stopping. This idempotency is not magic — it is a design contract that every module author must implement. Understanding the six most-used modules (file, copy, template, package, service, user) covers the vast majority of day-to-day configuration management tasks. Mastering their parameters and idempotency guarantees enables writing reliable playbooks that can be re-run safely in production.
The Six Core Modules
'ansible.builtin.file' manages files, directories and symbolic links: sets path, state (file/directory/link/absent/touch), owner, group, mode, and recurse. It is the primary module for creating directory structures and setting permissions. 'ansible.builtin.copy' copies files from the Ansible control node to managed hosts, or from one location to another on the managed host. Key parameters: src (source file or directory), dest (destination path), content (inline string content, instead of src), owner, group, mode, backup (preserve previous version with timestamp). 'ansible.builtin.template' processes a Jinja2 template from the control node and writes the rendered result to the managed host — variables from inventory, playbook vars, and facts are available in the template. It is the primary module for configuration files that vary per host or environment. 'ansible.builtin.package' is a distribution-agnostic package manager that uses yum, dnf, apt, zypper or the appropriate manager for the target OS, detected from facts. 'ansible.builtin.service' manages system services via systemd, init, upstart or the detected init system. 'ansible.builtin.user' manages UNIX user accounts and group memberships. Together these six modules handle the majority of OS-level configuration tasks in any automation environment.
# Core modules reference — Cricket Analytics Platform configuration
---
- name: Core modules demonstration
hosts: api_servers
become: true
vars:
app_user: cricket-app
app_dir: /opt/cricket-analytics
nginx_version: '1.24.*'
tasks:
# ── ansible.builtin.file — manage files, dirs, symlinks ───────────────────
- name: Create application directory structure
ansible.builtin.file:
path: '{{ item }}'
state: directory
owner: '{{ app_user }}'
group: '{{ app_user }}'
mode: '0750'
loop:
- '{{ app_dir }}'
- '{{ app_dir }}/logs'
- '{{ app_dir }}/config'
- '{{ app_dir }}/data'
# Idempotent: directories already existing are left unchanged
- name: Create symlink for application logs
ansible.builtin.file:
src: '{{ app_dir }}/logs'
dest: /var/log/cricket-analytics
state: link
force: true
- name: Remove obsolete temporary directory
ansible.builtin.file:
path: /tmp/cricket-old-deploy
state: absent # Deletes if exists, no-op if not present
# ── ansible.builtin.copy — copy files from control node ───────────────────
- name: Copy static application configuration
ansible.builtin.copy:
src: files/cricket-api-config.json # Relative to playbook directory
dest: '{{ app_dir }}/config/config.json'
owner: '{{ app_user }}'
group: '{{ app_user }}'
mode: '0640'
backup: true # Keep previous version as .json.YYYYMMDD-HHMMSS
- name: Write inline configuration file
ansible.builtin.copy:
content: |
# Cricket Analytics API — generated by Ansible
# Host: {{ inventory_hostname }}
# Environment: {{ environment }}
[api]
workers = {{ ansible_processor_vcpus * 2 }}
bind = 0.0.0.0:8080
timeout = 60
dest: '{{ app_dir }}/config/gunicorn.conf'
owner: '{{ app_user }}'
mode: '0640'
notify: Restart Cricket API # Handler triggered if content changes
# ── ansible.builtin.template — Jinja2-rendered configuration ─────────────
- name: Deploy Nginx virtual host configuration
ansible.builtin.template:
src: templates/nginx-vhost.conf.j2
dest: '/etc/nginx/conf.d/cricket-api.conf'
owner: root
group: root
mode: '0644'
validate: 'nginx -t -c /etc/nginx/nginx.conf'
notify: Reload Nginx
# ── ansible.builtin.package — distribution-agnostic package management ────
- name: Install required packages
ansible.builtin.package:
name:
- nginx
- python3
- python3-pip
- htop
- vim
state: present
# package module detects apt vs yum vs dnf automatically from facts
- name: Ensure specific Nginx version
ansible.builtin.package:
name: 'nginx={{ nginx_version }}'
state: present
when: ansible_os_family == 'Debian' # Debian-style version pinning
# ── ansible.builtin.service — service state management ───────────────────
- name: Enable and start Nginx
ansible.builtin.service:
name: nginx
enabled: true # Enable at boot
state: started # Ensure running now
# Idempotent: already-running services are left running
- name: Ensure cricket-api service is running
ansible.builtin.service:
name: cricket-api
enabled: true
state: started
failed_when: false # Do not fail if service is not installed yet
# ── ansible.builtin.user — user account management ────────────────────────
- name: Create application service user
ansible.builtin.user:
name: '{{ app_user }}'
comment: 'Cricket Analytics Application User'
system: true # System user (lower UID range)
shell: /sbin/nologin # No interactive login
home: '{{ app_dir }}'
create_home: false # Home already created by file module above
state: present
- name: Add deploy user to cricket-app group
ansible.builtin.user:
name: cricket-deploy
groups: '{{ app_user }}'
append: true # Add to group without removing from existing groups
- name: Set authorised SSH keys for deploy user
ansible.posix.authorized_key:
user: cricket-deploy
key: '{{ lookup("file", "files/deploy_key.pub") }}'
state: present
exclusive: false # Do not remove other existing keys