Modules
Modules are the units of work in Ansible. Each task in a playbook calls a module, and Ansible ships with thousands of them. Modules are idempotent — they check the current state before making changes, so running them twice produces the same result.
Package Management
Section titled “Package Management”apt (Debian/Ubuntu)
Section titled “apt (Debian/Ubuntu)”- name: Install nginx apt: name: nginx state: present update_cache: true
- name: Install multiple packages apt: name: - nginx - curl - git - htop state: present
- name: Remove a package apt: name: apache2 state: absent
- name: Upgrade all packages apt: upgrade: dist update_cache: trueyum / dnf (RHEL/CentOS/Fedora)
Section titled “yum / dnf (RHEL/CentOS/Fedora)”- name: Install nginx yum: name: nginx state: present
- name: Install with dnf (Fedora/RHEL 8+) dnf: name: nginx state: presentpackage (Generic)
Section titled “package (Generic)”Automatically uses the right package manager for the OS:
- name: Install nginx (works on any distro) package: name: nginx state: presentpip (Python)
Section titled “pip (Python)”- name: Install Python packages pip: name: - flask - gunicorn state: present virtualenv: /opt/myapp/venvFile Management
Section titled “File Management”Copy files from the control node to managed hosts:
- name: Copy nginx config copy: src: files/nginx.conf dest: /etc/nginx/nginx.conf owner: root group: root mode: "0644" backup: true # keep a backup of the original
- name: Write content directly copy: content: | server { listen 80; server_name example.com; } dest: /etc/nginx/sites-available/defaulttemplate
Section titled “template”Copy a Jinja2 template, rendering variables:
- name: Deploy app config template: src: templates/app.conf.j2 dest: /etc/app/config.yml owner: deploy mode: "0640"See Variables and Templates for Jinja2 syntax.
Manage file/directory properties or create symlinks:
- name: Create a directory file: path: /opt/myapp state: directory owner: deploy group: deploy mode: "0755"
- name: Create a symlink file: src: /opt/myapp/current/public dest: /var/www/html state: link
- name: Delete a file file: path: /tmp/old-file.txt state: absentlineinfile
Section titled “lineinfile”Ensure a specific line exists in a file (or is absent):
- name: Set timezone in config lineinfile: path: /etc/app/config.ini regexp: '^TIMEZONE=' line: 'TIMEZONE=UTC'
- name: Remove a line lineinfile: path: /etc/crontab regexp: '^.*old-job.*$' state: absentblockinfile
Section titled “blockinfile”Insert or update a block of text in a file:
- name: Add SSH banner blockinfile: path: /etc/ssh/sshd_config block: | Banner /etc/ssh/banner.txt MaxAuthTries 3 marker: "# {mark} ANSIBLE MANAGED BLOCK - SSH hardening"synchronize
Section titled “synchronize”Wraps rsync for syncing directories or large file sets to many hosts. Use it when you need to push an entire directory (e.g. app tree, static assets) rather than a single file. By default it runs on the control node and rsyncs to each host in turn; the source path is relative to the control node.
Key arguments: src (local path), dest (path on the managed host), delete (set to yes to mirror — remove files on the host that are not in source), rsync_opts for extra rsync flags. Use delegate_to: "{{ inventory_hostname }}" if you want the sync to run on each host (e.g. pull from a shared location) instead of from the control node.
- name: Sync app directory to hosts synchronize: src: app/ dest: /opt/myapp/ delete: yes rsync_opts: - "--exclude=.git"Service Management
Section titled “Service Management”service
Section titled “service”Start, stop, restart, or enable services:
- name: Start and enable nginx service: name: nginx state: started enabled: true
- name: Restart nginx service: name: nginx state: restarted
- name: Stop a service service: name: old-app state: stopped enabled: falsesystemd
Section titled “systemd”More control over systemd-specific features:
- name: Reload systemd daemon systemd: daemon_reload: true
- name: Enable and start a service systemd: name: myapp state: started enabled: trueUser and Group Management
Section titled “User and Group Management”- name: Create a group group: name: deploy state: present
- name: Create a user user: name: deploy group: deploy groups: sudo,docker append: true shell: /bin/bash create_home: true
- name: Add authorized SSH key authorized_key: user: deploy key: "{{ lookup('file', 'files/deploy_key.pub') }}" state: present
- name: Remove a user user: name: olduser state: absent remove: true # also remove home directoryCommands and Scripts
Section titled “Commands and Scripts”command
Section titled “command”Run a command (no shell features like pipes or redirects):
- name: Check app version command: /opt/myapp/bin/myapp --version register: app_version changed_when: falseRun a command through the shell (supports pipes, redirects, env vars):
- name: Find large log files shell: find /var/log -name "*.log" -size +100M | head -20 register: large_logs changed_when: falsescript
Section titled “script”Copy a local script to the remote host and execute it:
- name: Run setup script script: scripts/setup.sh args: creates: /opt/myapp/.installed # skip if this file existsExecute a command without the module system (useful for bootstrapping Python):
- name: Install Python on minimal hosts raw: apt-get install -y python3 when: ansible_python_interpreter is not definedWhen to Use Which
Section titled “When to Use Which”| Module | Use When |
|---|---|
command | Simple commands, no shell features needed |
shell | Need pipes, redirects, or shell variables |
script | Running a multi-line local script on remote hosts |
raw | Bootstrapping (no Python on target yet) |
Prefer specific modules (like apt, file, service) over command/shell whenever possible — specific modules are idempotent, command and shell are not.
Downloading and Archiving
Section titled “Downloading and Archiving”- name: Download a file get_url: url: https://example.com/app-v2.tar.gz dest: /tmp/app-v2.tar.gz checksum: sha256:abc123...
- name: Extract archive unarchive: src: /tmp/app-v2.tar.gz dest: /opt/myapp/ remote_src: trueCron Jobs
Section titled “Cron Jobs”- name: Schedule a backup cron: name: "Daily backup" minute: "0" hour: "2" job: "/opt/scripts/backup.sh >> /var/log/backup.log 2>&1" user: rootFinding Module Documentation
Section titled “Finding Module Documentation”ansible-doc apt # show docs for the apt moduleansible-doc -l # list all available modulesansible-doc -l | grep aws # find AWS modulesansible-doc -s copy # show short snippet/exampleOr browse the Ansible module index online.
Writing Custom Modules
Section titled “Writing Custom Modules”When no built-in module fits your needs, you can write your own in Python. Custom modules follow a simple pattern: receive JSON input, do work, return JSON output.
Module Location
Section titled “Module Location”Place custom modules in a library/ directory next to your playbook or role:
project/ playbooks/ site.yml library/ my_custom_module.py # available to all playbooks roles/ myapp/ library/ app_health.py # available only in this roleMinimal Module Template
Section titled “Minimal Module Template”#!/usr/bin/pythonfrom ansible.module_utils.basic import AnsibleModule
def run_module(): # Define accepted arguments module_args = dict( name=dict(type='str', required=True), state=dict(type='str', default='present', choices=['present', 'absent']), force=dict(type='bool', default=False), )
# Initialize the module module = AnsibleModule( argument_spec=module_args, supports_check_mode=True, )
# Get parameters name = module.params['name'] state = module.params['state']
# Initialize result result = dict( changed=False, message='', )
# Check mode — report what would change without doing it if module.check_mode: result['changed'] = True module.exit_json(**result)
# Do the actual work try: if state == 'present': # Create or ensure resource exists result['changed'] = True result['message'] = f'Resource {name} created' else: # Remove resource result['changed'] = True result['message'] = f'Resource {name} removed'
module.exit_json(**result)
except Exception as e: module.fail_json(msg=f'Error: {str(e)}', **result)
def main(): run_module()
if __name__ == '__main__': main()Using Your Custom Module
Section titled “Using Your Custom Module”- name: Use custom module my_custom_module: name: myresource state: present force: true register: result
- debug: var: result.messageKey Patterns
Section titled “Key Patterns”Idempotency — Check current state before making changes:
# Check if resource already existscurrent = get_current_state(name)if current and state == 'present': result['changed'] = False result['message'] = f'{name} already exists' module.exit_json(**result)Check mode support — Return what would change without doing it:
module = AnsibleModule( argument_spec=module_args, supports_check_mode=True, # declare support)
if module.check_mode: result['changed'] = would_change(name, state) module.exit_json(**result)Running commands — Use module.run_command() instead of subprocess:
rc, stdout, stderr = module.run_command(['myapp', 'status', name])if rc != 0: module.fail_json(msg=f'Command failed: {stderr}')Returning data — Include useful info in the result for register:
result['resource_id'] = resource.idresult['resource_url'] = resource.urlmodule.exit_json(**result)Custom Module Plugins (Action Plugins)
Section titled “Custom Module Plugins (Action Plugins)”For more advanced use cases — action plugins run on the control node (not the target) and can manipulate how modules are executed. These go in action_plugins/ and are less common.
When to Write a Custom Module
Section titled “When to Write a Custom Module”| Situation | Recommendation |
|---|---|
| A built-in module almost works | Use command/shell with changed_when first |
| You need this logic in multiple playbooks | Write a custom module |
| Complex API interaction with idempotency | Write a custom module |
| Simple one-off task | Use command or uri module instead |
Key Takeaways
Section titled “Key Takeaways”- Prefer specific modules over
command/shell— they’re idempotent and declarative. - Use
copyfor static files,templatefor files with variables,lineinfile/blockinfilefor surgical edits. Usecopyfor single or few files; usesynchronizefor directory sync or large transfers. serviceorsystemdto manage services;apt/yum/packagefor packages.- Use
commandwithchanged_when: falsefor read-only checks. - Use
ansible-doc <module>to look up any module’s arguments and examples. - Custom modules go in
library/. UseAnsibleModule, support check mode, and be idempotent.