Introduction
Deploying code from Git repositories is a fundamental automation task. The ansible.builtin.git module handles cloning, updating, and managing Git checkouts on remote hosts — supporting HTTPS, SSH, version pinning, and advanced features like sparse checkout and submodules.
For SSH-based checkout, see: Checkout Git Repository via SSH
Module Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
repo | string | Yes | Repository URL (HTTPS or SSH) |
dest | string | Yes | Destination path on the remote host |
version | string | No | Branch, tag, or commit SHA (default: HEAD) |
update | bool | No | Pull new revisions if repo exists (default: true) |
clone | bool | No | Clone the repo if it doesn't exist (default: true) |
depth | int | No | Shallow clone depth (saves bandwidth) |
force | bool | No | Discard local changes before updating |
single_branch | bool | No | Clone only the specified branch |
recursive | bool | No | Initialize submodules (default: true) |
accept_hostkey | bool | No | Accept SSH host keys automatically |
key_file | string | No | Path to SSH private key |
Return Values
| Return | Type | Description |
|---|---|---|
after | string | Commit SHA after checkout |
before | string | Previous commit SHA (if updated) |
remote_url_changed | bool | Whether the remote URL was modified |
Basic Checkout
---
- name: Deploy application from Git
hosts: all
become: true
tasks:
- name: Ensure git is installed
ansible.builtin.package:
name: git
state: present
- name: Clone repository
ansible.builtin.git:
repo: https://github.com/myorg/myapp.git
dest: /opt/myapp
Practical Examples
Pin to a Specific Version
Always pin deployments to a tag or commit for reproducibility:
- name: Deploy tagged release
ansible.builtin.git:
repo: https://github.com/myorg/myapp.git
dest: /opt/myapp
version: "v2.3.1"
- name: Deploy specific commit
ansible.builtin.git:
repo: https://github.com/myorg/myapp.git
dest: /opt/myapp
version: "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2"
Shallow Clone (Faster Deploys)
- name: Shallow clone (latest commit only)
ansible.builtin.git:
repo: https://github.com/myorg/myapp.git
dest: /opt/myapp
depth: 1
version: main
This is significantly faster for large repositories — downloads only the latest commit instead of full history.
Authenticated HTTPS Checkout
For private repositories, use token-based authentication:
- name: Clone private repo with token
ansible.builtin.git:
repo: "https://{{ git_token }}@github.com/myorg/private-app.git"
dest: /opt/private-app
version: main
no_log: true # Hide token from logs
Store the token in Ansible Vault:
# group_vars/all/vault.yml (encrypted)
git_token: "ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
Force Update (Discard Local Changes)
- name: Force-update to latest
ansible.builtin.git:
repo: https://github.com/myorg/myapp.git
dest: /opt/myapp
version: main
force: true # Discard any local modifications
Clone Without Updating
- name: Clone only if not present
ansible.builtin.git:
repo: https://github.com/myorg/myapp.git
dest: /opt/myapp
update: false # Don't pull if already cloned
With Submodules
- name: Clone with submodules
ansible.builtin.git:
repo: https://github.com/myorg/myapp.git
dest: /opt/myapp
recursive: true # Initialize and update submodules
version: main
Full Deployment Playbook
---
- name: Deploy application
hosts: web_servers
become: true
vars:
app_repo: https://github.com/myorg/myapp.git
app_version: "v2.3.1"
app_dest: /opt/myapp
tasks:
- name: Install git
ansible.builtin.package:
name: git
state: present
- name: Clone/update application
ansible.builtin.git:
repo: "{{ app_repo }}"
dest: "{{ app_dest }}"
version: "{{ app_version }}"
force: true
register: git_result
notify: restart application
- name: Show deployed version
ansible.builtin.debug:
msg: "Deployed commit: {{ git_result.after }}"
when: git_result.changed
- name: Install dependencies
ansible.builtin.pip:
requirements: "{{ app_dest }}/requirements.txt"
virtualenv: "{{ app_dest }}/venv"
when: git_result.changed
- name: Run database migrations
ansible.builtin.command:
cmd: "{{ app_dest }}/venv/bin/python manage.py migrate"
chdir: "{{ app_dest }}"
when: git_result.changed
handlers:
- name: restart application
ansible.builtin.systemd:
name: myapp
state: restarted
daemon_reload: true
CI/CD Integration
Use the git module in CI/CD pipelines to deploy on push:
- name: CI/CD deploy
hosts: staging
become: true
vars:
deploy_branch: "{{ lookup('env', 'CI_COMMIT_BRANCH') | default('main') }}"
deploy_sha: "{{ lookup('env', 'CI_COMMIT_SHA') | default('HEAD') }}"
tasks:
- name: Deploy exact commit
ansible.builtin.git:
repo: https://github.com/myorg/myapp.git
dest: /opt/myapp
version: "{{ deploy_sha }}"
depth: 1
register: deploy
- name: Post-deploy tasks
ansible.builtin.include_tasks: post-deploy.yml
when: deploy.changed
Troubleshooting
Permission Denied
fatal: could not read Username for 'https://github.com': No such device or address
The repo is private. Use a token:
repo: "https://{{ git_token }}@github.com/myorg/private-app.git"
Destination Exists and Is Not a Git Repository
- name: Remove non-git directory first
ansible.builtin.file:
path: /opt/myapp
state: absent
when: git_clone_result is failed
- name: Retry clone
ansible.builtin.git:
repo: https://github.com/myorg/myapp.git
dest: /opt/myapp
SSL Certificate Errors
- name: Clone with custom SSL
ansible.builtin.git:
repo: https://git.internal.example.com/myapp.git
dest: /opt/myapp
environment:
GIT_SSL_NO_VERIFY: "true" # Only for testing!
Related Articles
- Checkout Git Repository via SSH
- Ansible Lint Rule 401: latest[git]
- Copy Files to Remote Hosts
- Ansible Vault: Encrypt Sensitive Data
- Ansible Best Practices Guide
- Install Software on RHEL: dnf Module
Conclusion
The ansible.builtin.git module makes Git deployments reproducible and automated. Always pin to a tag or commit SHA for production deployments, use depth: 1 for faster clones, and store credentials in Ansible Vault with no_log: true. Combine with handlers for automatic service restarts on code changes, and use the after return value to log exactly which commit is deployed on each host.