Skip to main content
Ansible advanced Lesson 5 of 5

Ansible in CI/CD Pipelines

Integrate Ansible with GitHub Actions and Jenkins to automate configuration management and application deployments as part of your CI/CD pipeline.

Running Ansible from CI/CD pipelines automates configuration management so infrastructure changes are version-controlled, reviewed, and applied consistently.

Learning outcomes

By the end you can:

  • run an Ansible playbook from GitHub Actions
  • handle Vault secrets securely in CI
  • integrate Ansible into a Jenkins deploy stage
  • structure a CI-friendly project layout

1) Project layout for CI

project/
├── .github/
│   └── workflows/
│       └── deploy.yml
├── ansible/
│   ├── ansible.cfg
│   ├── inventory/
│   │   ├── production.ini
│   │   └── staging.ini
│   ├── group_vars/
│   │   ├── all/
│   │   │   ├── vars.yml
│   │   │   └── vault.yml    # encrypted
│   ├── roles/
│   │   └── app/
│   ├── requirements.yml
│   └── site.yml
└── Makefile

2) GitHub Actions: deploy with Ansible

# .github/workflows/deploy.yml
name: Deploy

on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    environment: production

    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: "3.12"

      - name: Install Ansible and dependencies
        run: |
          pip install ansible boto3 botocore
          ansible-galaxy install -r ansible/requirements.yml

      - name: Write SSH private key
        run: |
          install -m 600 -D /dev/null ~/.ssh/deploy_key
          echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/deploy_key

      - name: Write Vault password
        run: echo "${{ secrets.VAULT_PASSWORD }}" > /tmp/vault_pass

      - name: Run Ansible playbook
        run: |
          ansible-playbook \
            -i ansible/inventory/production.ini \
            --private-key ~/.ssh/deploy_key \
            --vault-password-file /tmp/vault_pass \
            ansible/site.yml

      - name: Cleanup secrets
        if: always()
        run: |
          rm -f ~/.ssh/deploy_key /tmp/vault_pass

Required GitHub Secrets

  • SSH_PRIVATE_KEY — private key for SSH access to target hosts
  • VAULT_PASSWORD — Ansible Vault decryption password

3) ansible.cfg for CI

# ansible/ansible.cfg
[defaults]
inventory        = inventory/
roles_path       = roles/
host_key_checking = False   # avoid interactive SSH host verification in CI
forks            = 10
pipelining       = True

[ssh_connection]
ssh_args = -o ControlMaster=auto -o ControlPersist=60s

4) Staging vs production gates

Use GitHub Environments to add manual approval before production:

jobs:
  deploy-staging:
    runs-on: ubuntu-latest
    environment: staging
    steps:
      - name: Deploy to staging
        run: ansible-playbook -i ansible/inventory/staging.ini ansible/site.yml ...

  deploy-production:
    runs-on: ubuntu-latest
    needs: deploy-staging
    environment: production    # requires manual approval in GitHub
    steps:
      - name: Deploy to production
        run: ansible-playbook -i ansible/inventory/production.ini ansible/site.yml ...

5) Jenkins: Ansible deploy stage

pipeline {
  agent any

  environment {
    VAULT_PASSWORD = credentials('ansible-vault-password')
    SSH_KEY = credentials('deploy-ssh-key')
  }

  stages {
    stage('Checkout') {
      steps { checkout scm }
    }

    stage('Install Ansible') {
      steps {
        sh 'pip install ansible'
        sh 'ansible-galaxy install -r ansible/requirements.yml'
      }
    }

    stage('Deploy Staging') {
      steps {
        sh '''
          echo "$VAULT_PASSWORD" > /tmp/vault_pass
          ansible-playbook \
            -i ansible/inventory/staging.ini \
            --private-key "$SSH_KEY" \
            --vault-password-file /tmp/vault_pass \
            ansible/site.yml
          rm -f /tmp/vault_pass
        '''
      }
    }

    stage('Deploy Production') {
      when {
        branch 'main'
      }
      input {
        message "Deploy to production?"
        ok "Deploy"
      }
      steps {
        sh '''
          echo "$VAULT_PASSWORD" > /tmp/vault_pass
          ansible-playbook \
            -i ansible/inventory/production.ini \
            --private-key "$SSH_KEY" \
            --vault-password-file /tmp/vault_pass \
            ansible/site.yml
          rm -f /tmp/vault_pass
        '''
      }
    }
  }

  post {
    always {
      sh 'rm -f /tmp/vault_pass'
    }
  }
}

6) Linting and testing before deploy

Always lint and validate before running against real servers:

# In GitHub Actions, before the deploy step:
- name: Lint playbooks
  run: ansible-lint ansible/site.yml

- name: Syntax check
  run: ansible-playbook ansible/site.yml --syntax-check

- name: Dry run (check mode)
  run: |
    ansible-playbook \
      -i ansible/inventory/staging.ini \
      --check \
      --diff \
      --vault-password-file /tmp/vault_pass \
      ansible/site.yml

Install ansible-lint:

pip install ansible-lint

Next steps

  • Ansible Tower / AWX: web UI and RBAC for team-scale Ansible
  • Molecule: local testing of Ansible roles with Docker
  • Combine with Terraform: Terraform creates infrastructure, Ansible configures it

Frequently Asked Questions

How do I pass secrets to Ansible from CI without hardcoding them?
Store the Vault password as a CI secret (e.g., VAULT_PASSWORD in GitHub Secrets). In your workflow, write it to a temporary file, pass it to ansible-playbook with --vault-password-file, and delete the file after the run.
Should Ansible run in push or pull mode from CI?
Typically push: the CI runner SSHes into target hosts and applies configuration. For large fleets, consider pull mode with ansible-pull where hosts periodically fetch and apply their own configuration.