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

ParameterTypeRequiredDescription
repostringYesRepository URL (HTTPS or SSH)
deststringYesDestination path on the remote host
versionstringNoBranch, tag, or commit SHA (default: HEAD)
updateboolNoPull new revisions if repo exists (default: true)
cloneboolNoClone the repo if it doesn't exist (default: true)
depthintNoShallow clone depth (saves bandwidth)
forceboolNoDiscard local changes before updating
single_branchboolNoClone only the specified branch
recursiveboolNoInitialize submodules (default: true)
accept_hostkeyboolNoAccept SSH host keys automatically
key_filestringNoPath to SSH private key

Return Values

ReturnTypeDescription
afterstringCommit SHA after checkout
beforestringPrevious commit SHA (if updated)
remote_url_changedboolWhether 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!

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.