AnsiblePilot — Master Ansible Automation

AnsiblePilot is the leading resource for learning Ansible automation, DevOps, and infrastructure as code. Browse over 1,400 tutorials covering Ansible modules, playbooks, roles, collections, and real-world examples. Whether you are a beginner or an experienced engineer, our step-by-step guides help you automate Linux, Windows, cloud, containers, and network infrastructure.

Popular Topics

About Luca Berton

Luca Berton is an Ansible automation expert, author of 8 Ansible books published by Apress and Leanpub including "Ansible for VMware by Examples" and "Ansible for Kubernetes by Example", and creator of the Ansible Pilot YouTube channel. He shares practical automation knowledge through tutorials, books, and video courses to help IT professionals and DevOps engineers master infrastructure automation.

Ansible builtin command Module: Complete Guide with Examples and Best Practices

By Luca Berton · Published 2024-01-01 · Category: troubleshooting

Master the ansible.builtin.command module for executing commands on remote hosts. Learn syntax, practical examples, security considerations, idempotency.

The ansible.builtin.command module is one of the most frequently used modules in Ansible automation. It executes commands on remote hosts without processing them through a shell, making it more secure and predictable than the shell module. This comprehensive guide covers everything from basic usage to advanced patterns for production environments.

Understanding the Command Module

The ansible.builtin.command module runs commands directly on remote systems. Unlike the shell module, it does not process commands through /bin/sh, which means shell operators like |, >, <, &&, and || are not available. This restriction actually improves security and predictability.

Key Characteristics

• Executes commands without shell processing • More secure than ansible.builtin.shell (no shell injection risk) • Does NOT support shell operators (pipes, redirects, etc.) • Supports creates and removes for conditional execution • Default module for ansible ad-hoc commands

See also: Enhancing Ansible Documentation: Best Practices for Maintainable Playbooks

Basic Syntax

---
- name: Execute command on remote hosts
  hosts: all
  become: true
  tasks:
    - name: Check disk usage
      ansible.builtin.command:
        cmd: df -h
      register: disk_usage

- name: Display disk usage ansible.builtin.debug: var: disk_usage.stdout_lines

Module Parameters

| Parameter | Required | Default | Description | |-----------|----------|---------|-------------| | cmd | Yes (or free form) | — | The command to execute | | chdir | No | — | Change directory before executing | | creates | No | — | Skip if this file exists | | removes | No | — | Only run if this file exists | | stdin | No | — | Set stdin for the command | | stdin_add_newline | No | true | Add newline to stdin data | | strip_empty_ends | No | true | Strip empty lines from output | | argv | No | — | Pass command as a list (safer) |

See also: Automate Text Capitalization with Ansible Playbooks

Practical Examples

Example 1: Check System Information

---
- name: Gather system information
  hosts: all
  tasks:
    - name: Get hostname
      ansible.builtin.command:
        cmd: hostname -f
      register: host_fqdn

- name: Get kernel version ansible.builtin.command: cmd: uname -r register: kernel_version

- name: Get uptime ansible.builtin.command: cmd: uptime register: system_uptime

- name: Display system info ansible.builtin.debug: msg: | Hostname: {{ host_fqdn.stdout }} Kernel: {{ kernel_version.stdout }} Uptime: {{ system_uptime.stdout }}

Example 2: Idempotent Command with creates/removes

---
- name: Idempotent command execution
  hosts: all
  become: true
  tasks:
    - name: Initialize database (only if not already done)
      ansible.builtin.command:
        cmd: /opt/app/init-db.sh
        creates: /opt/app/.db_initialized
      register: db_init

- name: Run migration (only if migration file exists) ansible.builtin.command: cmd: /opt/app/migrate.sh removes: /opt/app/pending_migrations.flag register: db_migration

Example 3: Using chdir to Change Working Directory

---
- name: Build application from source
  hosts: build_servers
  tasks:
    - name: Run make in project directory
      ansible.builtin.command:
        cmd: make build
        chdir: /opt/project/src

- name: Run tests ansible.builtin.command: cmd: python -m pytest tests/ chdir: /opt/project register: test_results

Example 4: Using argv for Safer Execution

---
- name: Safe command execution with argv
  hosts: all
  tasks:
    - name: Create user with special characters in comment
      ansible.builtin.command:
        argv:
          - useradd
          - -c
          - "John O'Brien - IT Department"
          - -m
          - jobrien
      become: true

Example 5: Conditional Execution Based on Output

---
- name: Conditional command execution
  hosts: all
  become: true
  tasks:
    - name: Check if service is running
      ansible.builtin.command:
        cmd: systemctl is-active nginx
      register: nginx_status
      changed_when: false
      failed_when: false

- name: Restart nginx if not running ansible.builtin.command: cmd: systemctl restart nginx when: nginx_status.rc != 0

- name: Verify nginx is now running ansible.builtin.command: cmd: systemctl is-active nginx register: nginx_verify changed_when: false

- name: Report status ansible.builtin.debug: msg: "Nginx status: {{ nginx_verify.stdout }}"

Example 6: Handling Command Errors

---
- name: Handle command errors gracefully
  hosts: all
  tasks:
    - name: Try to read a file that might not exist
      ansible.builtin.command:
        cmd: cat /etc/custom-config.conf
      register: config_content
      failed_when: false
      changed_when: false

- name: Use default config if file missing ansible.builtin.debug: msg: "{{ config_content.stdout | default('Using default configuration') }}" when: config_content.rc == 0

- name: Warn about missing config ansible.builtin.debug: msg: "Config file not found, using defaults" when: config_content.rc != 0

Command vs Shell vs Raw: When to Use Each

| Feature | command | shell | raw | |---------|---------|-------|-----| | Shell processing | No | Yes | No | | Pipe support | No | Yes | No | | Redirect support | No | Yes | No | | Environment variables | Limited | Full | No | | Security | Most secure | Less secure | Least secure | | Python required | Yes | Yes | No | | Idempotency helpers | creates/removes | creates/removes | No | | Use when | Simple commands | Need shell features | No Python on target |

Decision Tree

Need pipes, redirects, or shell features?
├── Yes → Use ansible.builtin.shell
└── No
    ├── Python available on target?
    │   ├── Yes → Use ansible.builtin.command ✓
    │   └── No → Use ansible.builtin.raw
    └── Is there a dedicated Ansible module?
        └── Yes → Use the dedicated module instead!

See also: Ansible script Module: Run Local Scripts on Remote Hosts Guide

Making Commands Idempotent

The biggest challenge with the command module is idempotency. Here are proven patterns:

Pattern 1: creates/removes Guards

- name: Extract archive (skip if already extracted)
  ansible.builtin.command:
    cmd: tar xzf /tmp/app-v2.0.tar.gz -C /opt/app/
    creates: /opt/app/version-2.0

- name: Remove old logs (only if log file exists) ansible.builtin.command: cmd: /opt/scripts/cleanup-logs.sh removes: /var/log/app/cleanup-needed.flag

Pattern 2: changed_when with Output Checking

- name: Check current version
  ansible.builtin.command:
    cmd: /opt/app/bin/app --version
  register: app_version
  changed_when: false

- name: Upgrade application ansible.builtin.command: cmd: /opt/app/bin/upgrade.sh when: "'2.0' not in app_version.stdout" register: upgrade_result changed_when: "'upgraded successfully' in upgrade_result.stdout"

Pattern 3: Register and Compare

- name: Get current timezone
  ansible.builtin.command:
    cmd: timedatectl show --property=Timezone --value
  register: current_tz
  changed_when: false

- name: Set timezone if different ansible.builtin.command: cmd: timedatectl set-timezone America/New_York when: current_tz.stdout != 'America/New_York'

Security Best Practices

1. Prefer Dedicated Modules

# BAD: Using command for user creation
- ansible.builtin.command:
    cmd: useradd -m -s /bin/bash deploy

# GOOD: Using dedicated module - ansible.builtin.user: name: deploy shell: /bin/bash create_home: true

2. Avoid Shell When Possible

# RISKY: Shell injection possible
- ansible.builtin.shell:
    cmd: "echo {{ user_input }} > /tmp/output"

# SAFER: No shell processing - ansible.builtin.command: cmd: "echo {{ user_input }}" register: output

3. Use no_log for Sensitive Commands

- name: Set database password
  ansible.builtin.command:
    cmd: "/opt/db/set-password.sh {{ db_password }}"
  no_log: true

Advanced Patterns

Running Commands in a Loop

- name: Check multiple services
  ansible.builtin.command:
    cmd: "systemctl is-active {{ item }}"
  loop:
    - nginx
    - postgresql
    - redis
  register: service_checks
  changed_when: false
  failed_when: false

- name: Report service status ansible.builtin.debug: msg: "{{ item.item }}: {{ item.stdout }}" loop: "{{ service_checks.results }}"

Timeout Handling

- name: Run command with timeout
  ansible.builtin.command:
    cmd: /opt/scripts/long-running-task.sh
  async: 300
  poll: 10
  register: task_result

Using stdin

- name: Send input to command
  ansible.builtin.command:
    cmd: /opt/app/configure.sh
    stdin: |
      yes
      production
      8080

Troubleshooting

Common Errors

"No such file or directory"

# Check path exists first
- name: Verify script exists
  ansible.builtin.stat:
    path: /opt/scripts/deploy.sh
  register: script_check

- name: Run deployment script ansible.builtin.command: cmd: /opt/scripts/deploy.sh when: script_check.stat.exists

"Permission denied"

# Ensure become is set
- name: Run privileged command
  ansible.builtin.command:
    cmd: systemctl restart nginx
  become: true
  become_method: sudo

"changed" always reported

# Use changed_when to suppress false positives
- name: Check something
  ansible.builtin.command:
    cmd: grep -c error /var/log/app.log
  register: error_count
  changed_when: false
  failed_when: error_count.rc > 1

Frequently Asked Questions

What is the difference between ansible.builtin.command and ansible.builtin.shell?

The command module executes commands directly without a shell, making it safer against shell injection attacks. The shell module processes commands through /bin/sh, enabling pipes (|), redirects (>), and environment variable expansion. Use command by default and shell only when you need shell features.

How do I make the command module idempotent?

Use creates (skip if file exists) and removes (skip if file missing) parameters, or combine register with changed_when to control when Ansible reports a change. Always prefer dedicated modules over command when available.

Can I use pipes with ansible.builtin.command?

No. The command module does not process shell operators. Use ansible.builtin.shell if you need pipes, or better yet, use the register and set_fact pattern to chain commands idiomatically in Ansible.

How do I suppress the "changed" status for read-only commands?

Add changed_when: false to any task that only reads data without modifying the system. This is common for status checks, version queries, and information gathering tasks.

Is ansible.builtin.command secure for production use?

Yes, it's the most secure execution module because it doesn't process shell metacharacters. However, always prefer dedicated Ansible modules (like ansible.builtin.copy, ansible.builtin.service, ansible.builtin.user) over command when they exist for your use case.

Related Articles

Ansible builtin file Module Complete GuideAnsible Troubleshooting GuideAnsible Playbook Best PracticesAnsible Error Handling ignore_errors rescue always

Conclusion

The ansible.builtin.command module is a fundamental tool for remote command execution. While it's tempting to use it for everything, always check if a dedicated Ansible module exists first. When you do use command, apply idempotency patterns with creates/removes and changed_when, and prefer it over shell for security. These practices will make your playbooks more reliable, secure, and maintainable in production environments.

Category: troubleshooting

Browse all Ansible tutorials · AnsiblePilot Home