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 thanansible.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 Guide • Ansible Troubleshooting Guide • Ansible Playbook Best Practices • Ansible Error Handling ignore_errors rescue alwaysConclusion
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