Introduction

ansible.builtin.git clones and manages Git repositories on remote hosts. Deploy code, checkout specific branches or tags, pull updates, and handle SSH authentication — all idempotently.

Basic Usage

# Clone a repository
- name: Clone application code
  ansible.builtin.git:
    repo: https://github.com/myorg/myapp.git
    dest: /opt/myapp
    version: main

# Clone with SSH
- name: Clone private repo
  ansible.builtin.git:
    repo: git@github.com:myorg/myapp.git
    dest: /opt/myapp
    version: main
    key_file: /home/deploy/.ssh/deploy_key
    accept_hostkey: true

Parameters

ParameterDefaultDescription
repo(required)Repository URL (HTTPS or SSH)
dest(required)Local path to clone into
versionHEADBranch, tag, or commit hash
clonetrueClone if dest doesn't exist
updatetruePull updates if already cloned
forcefalseDiscard local changes
depth—Shallow clone depth
single_branchfalseOnly fetch the specified branch
key_file—SSH private key path
accept_hostkeyfalseAccept unknown SSH host keys
barefalseCreate bare repository
recursivetrueInitialize submodules
track_submodulesfalseTrack latest submodule commits
refspec—Custom refspec
ssh_opts—Extra SSH options

Checkout Branches and Tags

# Specific branch
- ansible.builtin.git:
    repo: https://github.com/myorg/myapp.git
    dest: /opt/myapp
    version: develop

# Specific tag
- ansible.builtin.git:
    repo: https://github.com/myorg/myapp.git
    dest: /opt/myapp
    version: v2.1.0

# Specific commit
- ansible.builtin.git:
    repo: https://github.com/myorg/myapp.git
    dest: /opt/myapp
    version: a1b2c3d4

Practical Patterns

Code Deployment

---
- name: Deploy application
  hosts: webservers
  become: true
  vars:
    app_version: "v2.1.0"
    app_repo: "git@github.com:myorg/myapp.git"
    app_dest: /opt/myapp
  tasks:
    - name: Pull latest code
      ansible.builtin.git:
        repo: "{{ app_repo }}"
        dest: "{{ app_dest }}"
        version: "{{ app_version }}"
        key_file: /home/deploy/.ssh/deploy_key
        accept_hostkey: true
        force: true
      register: git_result
      notify: Restart application

    - name: Show deployed version
      ansible.builtin.debug:
        msg: "Deployed {{ git_result.after[:8] }} (was {{ git_result.before[:8] }})"
      when: git_result.changed

    - name: Install dependencies
      ansible.builtin.pip:
        requirements: "{{ app_dest }}/requirements.txt"
      when: git_result.changed

  handlers:
    - name: Restart application
      ansible.builtin.service:
        name: myapp
        state: restarted

Shallow Clone (Faster)

# Only clone recent history
- name: Shallow clone for deployment
  ansible.builtin.git:
    repo: https://github.com/myorg/myapp.git
    dest: /opt/myapp
    version: main
    depth: 1
    single_branch: true

Release-Based Deployment

- name: Clone to release directory
  ansible.builtin.git:
    repo: "{{ app_repo }}"
    dest: "/opt/releases/{{ app_version }}"
    version: "{{ app_version }}"
    depth: 1
  register: deploy

- name: Install dependencies
  ansible.builtin.command:
    cmd: pip install -r requirements.txt
    chdir: "/opt/releases/{{ app_version }}"
  when: deploy.changed

- name: Switch symlink
  ansible.builtin.file:
    src: "/opt/releases/{{ app_version }}"
    dest: /opt/myapp/current
    state: link
    force: true
  notify: Restart app

SSH Agent Forwarding

# In ansible.cfg
# [ssh_connection]
# ssh_args = -o ForwardAgent=yes

- name: Clone with forwarded SSH agent
  ansible.builtin.git:
    repo: git@github.com:myorg/private-repo.git
    dest: /opt/myapp
    accept_hostkey: true

Deploy Key Setup

- name: Copy deploy key
  ansible.builtin.copy:
    src: deploy_key
    dest: /home/deploy/.ssh/deploy_key
    owner: deploy
    mode: "0600"

- name: Add GitHub to known hosts
  ansible.builtin.known_hosts:
    name: github.com
    key: "{{ lookup('pipe', 'ssh-keyscan github.com 2>/dev/null') }}"
    state: present

- name: Clone repo
  ansible.builtin.git:
    repo: git@github.com:myorg/myapp.git
    dest: /opt/myapp
    key_file: /home/deploy/.ssh/deploy_key
  become_user: deploy

Configuration Repository

# Clone config repo and deploy specific files
- name: Clone config repo
  ansible.builtin.git:
    repo: git@github.com:myorg/server-configs.git
    dest: /opt/configs
    version: main

- name: Deploy nginx config
  ansible.builtin.copy:
    src: "/opt/configs/nginx/{{ inventory_hostname }}.conf"
    dest: /etc/nginx/nginx.conf
    remote_src: true
  notify: Reload nginx

Register Output

- ansible.builtin.git:
    repo: https://github.com/myorg/myapp.git
    dest: /opt/myapp
    version: main
  register: git_result

# Available fields:
# git_result.changed    — true if repo was updated
# git_result.before     — previous commit hash
# git_result.after      — current commit hash
# git_result.remote_url_changed — URL changed

- debug:
    msg: "Updated from {{ git_result.before[:8] }} to {{ git_result.after[:8] }}"
  when: git_result.changed

Troubleshooting

"Permission denied (publickey)"

SSH key issues:

# Use key_file
- git:
    repo: git@github.com:myorg/app.git
    dest: /opt/app
    key_file: /home/deploy/.ssh/id_ed25519
    accept_hostkey: true

# Or use HTTPS with token
- git:
    repo: "https://{{ github_token }}@github.com/myorg/app.git"
    dest: /opt/app

"Local modifications exist"

Use force: true to discard local changes:

- git:
    repo: https://github.com/myorg/app.git
    dest: /opt/app
    force: true    # Discard local changes

"Git not installed"

- name: Install git
  ansible.builtin.package:
    name: git
    state: present

- name: Clone repo
  ansible.builtin.git:
    repo: https://github.com/myorg/app.git
    dest: /opt/app

Conclusion

ansible.builtin.git deploys code from Git repositories idempotently. Use version for branches, tags, or commits. Use depth: 1 for faster shallow clones in deployment. Use key_file or SSH agent forwarding for private repos. The module tracks changes — register gives you before/after commit hashes for conditional logic. Combine with symlinks for zero-downtime release-based deployments.