Integrating Ansible with Jenkins: Automating Your CI/CD Pipeline

Integrate Ansible with Jenkins to run playbooks from CI/CD pipelines, manage SSH credentials, and deploy consistently.

Integrating Ansible with Jenkins: Automating Your CI/CD Pipeline

Integrating Ansible with Jenkins lets your CI/CD pipeline build an artifact, run tests, and deploy it with the same playbooks you use outside Jenkins. The main challenge is wiring credentials, inventory, and playbook execution without turning the pipeline into a pile of shell commands.

This guide shows a practical setup: Jenkins orchestrates the pipeline, while Ansible handles deployment and configuration on target hosts. The examples assume SSH-based Linux deployments, but the same pattern works with environment-specific inventories and roles.

How Jenkins and Ansible Fit Together

Before you write the pipeline, separate the responsibilities:

  • Jenkins: Checks out code, builds artifacts, runs tests, collects logs, and decides when deployment stages run.
  • Ansible: Connects to target hosts, copies artifacts, writes configuration, manages services, and keeps deployment steps idempotent.

Why Integrate Jenkins with Ansible?

This split gives your team a clean boundary:

  1. Jenkins stores the release flow in a Jenkinsfile.
  2. Ansible stores infrastructure actions in playbooks and roles.
  3. Inventory files decide which hosts belong to staging, production, or other environments.
  4. Jenkins credentials protect SSH keys, Vault passwords, and tokens.

Prerequisites for Integration

Before you begin, make sure you have:

  • A working Jenkins controller and at least one agent that can run deployment jobs.
  • Ansible installed on the Jenkins agent that runs ansible-playbook.
  • Target machines reachable from that agent over SSH.
  • A dedicated SSH key pair for automation.
  • Application code, inventory, playbooks, and roles in version control.

Setting Up Jenkins for Ansible

Jenkins needs two things before it can run Ansible safely: the Ansible runtime on an agent and credentials for the target hosts.

1. Install the Ansible Plugin

You can call ansible-playbook directly from a shell step, but the Jenkins Ansible plugin can make configuration and output easier to manage.

  1. Navigate to Manage Jenkins > Manage Plugins.
  2. Go to the Available tab and search for "Ansible".
  3. Select the "Ansible" plugin and click Install without restart or Download now and install after restart.

2. Configure SSH Credentials

Ansible will use SSH to connect to target servers. Store the private key in Jenkins credentials rather than in the repository.

  1. Go to Manage Jenkins > Manage Credentials.
  2. Select the credentials store used by your jobs.
  3. Click Add Credentials.
  4. Choose SSH Username with private key.
  5. Set a stable ID, such as ansible-ssh-key.
  6. Enter the SSH username used on target machines.
  7. Add the private key and save the credential.

Tip: Key Management Best Practices

  • Never hardcode SSH private keys in a Jenkinsfile, inventory, or playbook.
  • Use dedicated automation keys, not personal keys.
  • Rotate keys on a schedule that matches your team's security policy.

Designing Your Jenkins Pipeline with Ansible

Use a Declarative Pipeline in a Jenkinsfile so deployment logic is reviewed with the application code.

Basic Pipeline Structure

Here is a compact example for a build, staging deploy, and gated production deploy:

// Jenkinsfile
pipeline {
    agent any

    environment {
        // Define environment variables if needed
        ANSIBLE_HOST_KEY_CHECKING = 'False' // Be cautious in production, prefer known_hosts
    }

    stages {
        stage('Checkout Source') {
            steps {
                git 'https://your-scm-url/your-repo.git'
            }
        }

        stage('Build Application') {
            // This stage might build a JAR, WAR, Docker image, etc.
            // Example: build a Spring Boot JAR
            steps {
                sh 'mvn clean package'
            }
        }

        stage('Run Unit Tests') {
            steps {
                sh 'mvn test'
            }
        }

        stage('Deploy to Staging') {
            steps {
                script {
                    // Use the SSH agent to make the private key available to Ansible
                    sshagent(credentials: ['ansible-ssh-key']) {
                        // Execute Ansible playbook
                        sh 'ansible-playbook -i inventory/staging.ini playbooks/deploy_app.yml \n                            -e "app_version=$(cat target/VERSION)"'
                    }
                }
            }
        }

        stage('Deploy to Production') {
            // This stage might require manual approval
            input {
                message "Proceed with deployment to Production?"
                ok "Deploy to Production"
            }
            steps {
                script {
                    sshagent(credentials: ['ansible-ssh-key']) {
                        sh 'ansible-playbook -i inventory/production.ini playbooks/deploy_app.yml \n                            -e "app_version=$(cat target/VERSION)"'
                    }
                }
            }
        }
    }

    post {
        always {
            echo 'Pipeline finished.'
        }
        success {
            echo 'Pipeline succeeded!'
            // slackSend channel: '#deployments', message: "Deployment successful: ${env.BUILD_URL}"
        }
        failure {
            echo 'Pipeline failed!'
            // slackSend channel: '#deployments', message: "Deployment failed: ${env.BUILD_URL}"
        }
    }
}

Key Pipeline Details

  • agent any works for a demo. In production, use an agent label that guarantees Ansible is installed.
  • sshagent(credentials: ['ansible-ssh-key']) exposes the private key only inside that block.
  • ansible-playbook -i inventory/staging.ini playbooks/deploy_app.yml keeps the target environment explicit.
  • The production input block adds a manual gate before a sensitive deployment.
  • Avoid disabling host key checking in production. Manage known_hosts on the Jenkins agent instead.

Example Ansible Playbook: Deploying a Web Application

Here is a simplified inventory/staging.ini and playbooks/deploy_app.yml for a Java web application. Your artifact name, service manager, and paths may differ.

inventory/staging.ini

[web_servers]
web1.example.com
web2.example.com

[database_servers]
db1.example.com

[all:vars]
ansible_user=ubuntu

playbooks/deploy_app.yml

---
- name: Deploy Web Application
  hosts: web_servers
  become: yes
  vars:
    app_name: my-webapp
    app_path: /opt/{{ app_name }}
    app_port: 8080
    app_version: "{{ app_version | default('1.0.0') }}"

  tasks:
    - name: Ensure application directory exists
      ansible.builtin.file:
        path: "{{ app_path }}"
        state: directory
        owner: "{{ ansible_user }}"
        group: "{{ ansible_user }}"
        mode: '0755'

    - name: Copy application JAR to target
      ansible.builtin.copy:
        src: "target/{{ app_name }}-{{ app_version }}.jar"
        dest: "{{ app_path }}/{{ app_name }}.jar"
        owner: "{{ ansible_user }}"
        group: "{{ ansible_user }}"
        mode: '0644'
      notify: restart app service

    - name: Ensure systemd service file exists
      ansible.builtin.template:
        src: templates/my-webapp.service.j2
        dest: /etc/systemd/system/{{ app_name }}.service
        owner: root
        group: root
        mode: '0644'
      notify: restart app service

    - name: Ensure app service is started and enabled
      ansible.builtin.systemd:
        name: "{{ app_name }}"
        state: started
        enabled: yes

  handlers:
    - name: restart app service
      ansible.builtin.systemd:
        name: "{{ app_name }}"
        state: restarted
        daemon_reload: yes

templates/my-webapp.service.j2 (Systemd service template)

[Unit]
Description={{ app_name }} Application
After=network.target

[Service]
User={{ ansible_user }}
ExecStart=/usr/bin/java -jar {{ app_path }}/{{ app_name }}.jar --server.port={{ app_port }}
SuccessExitStatus=143

[Install]
WantedBy=multi-user.target

This playbook creates the app directory, copies the built JAR, writes a systemd unit, and starts the service. The handler restarts the service only when the artifact or unit file changes.

Best Practices and Tips

  • Keep playbooks idempotent so rerunning a failed pipeline does not create extra damage.
  • Store Vault passwords in Jenkins credentials and pass them through a temporary password file or approved credential binding.
  • Organize deployment logic into roles, such as webserver, database, and app_deploy.
  • Use separate inventories or group_vars for staging and production.
  • Run Ansible on dedicated Jenkins agents, not the controller.
  • Use -v or -vv only when you need more detail; avoid leaking secrets into logs.
  • Test playbooks before Jenkins runs them, especially for production roles.

When to See a Professional

Bring in a Jenkins or platform administrator when the pipeline deploys to production, manages shared credentials, writes to many hosts, or needs audit controls. Credential handling, host key verification, and rollback behavior deserve a second review before the first production run.

Takeaway

Use Jenkins for orchestration and Ansible for deployment state. Keep credentials in Jenkins, keep playbooks in version control, make inventory explicit, and test the same Ansible commands outside the pipeline before you automate production deployment.