How to Run an Ansible Playbook Locally Without SSH: 5 Proven Methods
Use the connection: local keyword in your play, target the pre-configured localhost host, or pass -c local on the CLI to execute Ansible tasks directly on the control node without SSH overhead.
Running an Ansible playbook locally is essential for controller provisioning, CI/CD pipelines, and testing roles without remote infrastructure. While Ansible is architected for remote execution via SSH, the ansible/ansible repository provides a robust local connection plugin that bypasses network transport entirely. This article explores the five most reliable methods to run ansible playbook locally, backed by specific source file references from the codebase.
Understanding the Local Connection Plugin Architecture
The core mechanism for local execution resides in lib/ansible/plugins/connection/local.py. This plugin implements the ConnectionBase API and executes commands using subprocess.Popen directly on the controller node rather than opening an SSH session.
Key implementation details from the source:
- User context: The plugin sets
self._play_context.remote_userto the current controller user viagetpass.getuser()because no remote authentication is required. - File transfers: Instead of SCP or SFTP,
put_fileandfetch_fileuseshutil.copyfilesince both source and destination exist on the same filesystem. - Privilege escalation: The
exec_commandmethod allocates a pseudo-TTY viapty.openpty()whenbecomeis enabled, ensuring sudo password prompts work correctly.
Method 1: Configure the Play with connection: local
Setting connection: local at the play level forces all tasks in that play to execute on the controller. This is the most explicit method and documents intent clearly within your playbook.
---
- name: Configure the Ansible controller
hosts: localhost
connection: local
become: true
tasks:
- name: Install required packages
ansible.builtin.apt:
name: git
state: present
- name: Create application directory
ansible.builtin.file:
path: /opt/myapp
state: directory
mode: '0755'
This approach is ideal for controller provisioning, bootstrap scripts, or any workflow where the target host is the machine running Ansible.
Method 2: Target the Implicit localhost Host
Ansible automatically defines a localhost host in the default inventory located at lib/ansible/inventory/source/local.py. This host entry pre-configures ansible_connection=local, meaning you can target localhost without any additional connection parameters.
ansible-playbook -i localhost, site.yml
Note: The trailing comma after localhost is required to indicate a static inventory entry rather than a file path.
This method leverages the built-in inventory logic, making it perfect for CI/CD pipelines where you want minimal configuration syntax.
Method 3: Override with the -c local CLI Flag
For ad-hoc execution or testing scenarios where you cannot modify the playbook, the -c (or --connection) CLI flag forces a specific connection plugin. This option is parsed in lib/ansible/cli/playbook.py and injected into the play context.
ansible-playbook -i inventory.ini -c local site.yml
This approach applies the local connection to all hosts in the run, overriding any connection keywords defined in the playbook. Use this for rapid testing or when executing playbooks designed for remote hosts against the controller temporarily.
Method 4: Delegate Specific Tasks with delegate_to: localhost
When most tasks should run on remote hosts but specific operations (like generating reports or rendering templates) must execute on the controller, use delegate_to: localhost. This task-level directive forces the local connection plugin for that specific task while maintaining the remote context for others.
- name: Deploy application to remote servers
hosts: webservers
become: true
tasks:
- name: Generate configuration on controller
ansible.builtin.template:
src: web.conf.j2
dest: /tmp/web.conf
delegate_to: localhost
- name: Copy configuration to remote host
ansible.builtin.copy:
src: /tmp/web.conf
dest: /etc/web.conf
The delegate_to mechanism is implemented in the task executor, which looks up the delegated host's connection settings—identical to how it handles the implicit localhost entry.
Method 5: Set ansible_connection=local in Inventory
For environments where multiple hosts or groups require local execution, define the connection type in your inventory file or group variables. Setting ansible_connection=local as a host or group variable ensures the local plugin is used without modifying playbooks or CLI commands.
[local]
mycontroller ansible_connection=local ansible_user=admin
[local:vars]
ansible_connection=local
This declarative approach centralizes connection logic in your inventory, making it ideal for mixed environments where some managed nodes are actually the controller itself.
How Local Execution Handles Privilege Escalation and File Transfers
Understanding the internal mechanics helps troubleshoot edge cases. According to the source code in lib/ansible/plugins/connection/local.py:
-
Privilege escalation: When
become: trueis set, the plugin allocates a pseudo-terminal usingpty.openpty()to handle interactive sudo password prompts. Theexec_commandmethod manages this allocation before spawning the subprocess. -
File transfers: Unlike remote connections that use SCP/SFTP, the local plugin implements
put_fileandfetch_fileusingshutil.copyfile. This is possible because both source and destination paths reside on the same filesystem, eliminating network overhead entirely.
These implementations ensure that running ansible playbook locally provides the same functional capabilities as remote execution, including proper permission handling and idempotent file operations.
Summary
- The local connection plugin (
lib/ansible/plugins/connection/local.py) is the core mechanism that enables running ansible playbook locally without SSH. - Five reliable methods exist:
connection: localin plays, targeting implicitlocalhost, using-c localCLI flag,delegate_to: localhostfor specific tasks, and settingansible_connection=localin inventory. - Built-in support: The default inventory automatically configures
localhostwithansible_connection=local, making local execution seamless for CI/CD pipelines. - Full feature parity: Local execution supports privilege escalation (
become) and file transfers usingshutil.copyfile, ensuring identical behavior to remote connections.
Frequently Asked Questions
What is the difference between connection: local and delegate_to: localhost?
Use connection: local when you want the entire play to execute on the controller node, typically set at the play level alongside hosts: localhost. Use delegate_to: localhost when you need only a specific task to run locally while the rest of the play executes on remote hosts. Both methods invoke the same local connection plugin, but they operate at different scopes.
Does running Ansible locally support sudo and privilege escalation?
Yes. The local connection plugin fully supports privilege escalation via become. When become: true is set, the plugin allocates a pseudo-terminal using pty.openpty() to handle interactive password prompts, then executes the command with elevated privileges via subprocess.Popen. This behavior is implemented in lib/ansible/plugins/connection/local.py.
Why does ansible-playbook -i localhost, site.yml work without specifying -c local?
The default inventory provided by Ansible (defined in lib/ansible/inventory/source/local.py) automatically creates a localhost host entry with ansible_connection=local pre-configured. When you target localhost or 127.0.0.1, Ansible automatically selects the local connection plugin without requiring explicit CLI flags or playbook keywords.
Are file transfers slower when running Ansible locally?
No. Local file transfers are actually faster because they bypass network protocols entirely. The local connection plugin implements put_file and fetch_file using shutil.copyfile, which performs direct filesystem copies since both source and destination reside on the same controller node. This eliminates the overhead of SCP or SFTP encryption and network latency.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →