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

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

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

Module Parameters

ParameterRequiredDefaultDescription
cmdYes (or free form)The command to execute
chdirNoChange directory before executing
createsNoSkip if this file exists
removesNoOnly run if this file exists
stdinNoSet stdin for the command
stdin_add_newlineNotrueAdd newline to stdin data
strip_empty_endsNotrueStrip empty lines from output
argvNoPass command as a list (safer)

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

Featurecommandshellraw
Shell processingNoYesNo
Pipe supportNoYesNo
Redirect supportNoYesNo
Environment variablesLimitedFullNo
SecurityMost secureLess secureLeast secure
Python requiredYesYesNo
Idempotency helperscreates/removescreates/removesNo
Use whenSimple commandsNeed shell featuresNo 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: Automate Text Capitalization with Ansible Playbooks

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

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

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.

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