AnsiblePilot — Master Ansible Automation

AnsiblePilot is the leading resource for learning Ansible automation, DevOps, and infrastructure as code. Browse over 1,400 tutorials covering Ansible modules, playbooks, roles, collections, and real-world examples. Whether you are a beginner or an experienced engineer, our step-by-step guides help you automate Linux, Windows, cloud, containers, and network infrastructure.

Popular Topics

About Luca Berton

Luca Berton is an Ansible automation expert, author of 8 Ansible books published by Apress and Leanpub including "Ansible for VMware by Examples" and "Ansible for Kubernetes by Example", and creator of the Ansible Pilot YouTube channel. He shares practical automation knowledge through tutorials, books, and video courses to help IT professionals and DevOps engineers master infrastructure automation.

AAP 2.6 CI/CD Pipeline Integration: GitOps Workflows with Jenkins, GitLab, and GitHub Actions

By Luca Berton · Published 2024-01-01 · Category: installation

Integrate AAP 2.6 into CI/CD pipelines with Jenkins, GitLab CI, and GitHub Actions. GitOps workflows, webhook triggers, Infrastructure as Code testing.

CI/CD with AAP 2.6

AAP 2.6 fits into CI/CD pipelines in two ways: as a target (pipelines trigger AAP jobs) and as an orchestrator (AAP workflows drive the full deployment). Most enterprises use both — CI builds and tests in the pipeline, then hands off to AAP for infrastructure deployment.

See also: Ansible vs GitHub Actions: Key Differences & When to Use Each (2026)

Integration Patterns

PatternHow It WorksBest For
Webhook triggerGit push → AAP launches jobSimple GitOps
API triggerPipeline calls AAP REST APIFull pipeline control
Pipeline pluginNative Jenkins/GitLab pluginTight integration
HybridPipeline builds → AAP deploysSeparation of concerns

GitHub Actions

Trigger AAP Job from GitHub Actions

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

on:
  push:
    branches: [main]
    paths:
      - 'ansible/**'
      - 'inventory/**'

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - name: Trigger AAP Job Template
        uses: actions/github-script@v7
        env:
          AAP_URL: ${{ secrets.AAP_URL }}
          AAP_TOKEN: ${{ secrets.AAP_TOKEN }}
        with:
          script: |
            const response = await fetch(
              `${process.env.AAP_URL}/api/controller/v2/job_templates/42/launch/`,
              {
                method: 'POST',
                headers: {
                  'Authorization': `Bearer ${process.env.AAP_TOKEN}`,
                  'Content-Type': 'application/json'
                },
                body: JSON.stringify({
                  extra_vars: {
                    git_sha: context.sha,
                    git_ref: context.ref,
                    deployer: context.actor,
                    version: `${context.runNumber}`
                  }
                })
              }
            );
            const job = await response.json();
            console.log(`AAP Job launched: ${job.id}`);
            core.setOutput('job_id', job.id);

      - name: Wait for AAP Job
        uses: actions/github-script@v7
        env:
          AAP_URL: ${{ secrets.AAP_URL }}
          AAP_TOKEN: ${{ secrets.AAP_TOKEN }}
        with:
          script: |
            const jobId = '${{ steps.trigger.outputs.job_id }}';
            let status = 'pending';
            while (['pending', 'waiting', 'running'].includes(status)) {
              await new Promise(r => setTimeout(r, 10000));
              const res = await fetch(
                `${process.env.AAP_URL}/api/controller/v2/jobs/${jobId}/`,
                { headers: { 'Authorization': `Bearer ${process.env.AAP_TOKEN}` } }
              );
              const job = await res.json();
              status = job.status;
              console.log(`Job ${jobId}: ${status}`);
            }
            if (status !== 'successful') {
              core.setFailed(`AAP job ${jobId} finished with status: ${status}`);
            }

AAP Webhook Receiver

Configure the job template to accept GitHub webhooks directly:

- name: Configure webhook-triggered template
  ansible.platform.job_template:
    controller_host: "{{ gateway_url }}"
    controller_username: "{{ controller_user }}"
    controller_password: "{{ controller_pass }}"
    name: "Deploy on Push"
    project: "Infrastructure"
    playbook: "deploy.yml"
    inventory: "Production"
    webhook_service: github
    webhook_credential: "GitHub Webhook Secret"
    state: present

GitHub webhook URL: https://gateway.example.org/api/controller/v2/job_templates//github/

GitLab CI

GitLab Pipeline

# .gitlab-ci.yml
stages:
  - lint
  - test
  - deploy

lint:
  stage: lint
  image: quay.io/ansible/ansible-navigator:latest
  script:
    - ansible-navigator lint playbooks/ -m stdout

test:
  stage: test
  image: quay.io/ansible/ansible-navigator:latest
  script:
    - ansible-navigator run playbooks/test.yml -m stdout --check --diff -i inventory/staging

deploy_staging:
  stage: deploy
  script:
    - |
      JOB=$(curl -s -k -X POST \
        -H "Authorization: Bearer $AAP_TOKEN" \
        -H "Content-Type: application/json" \
        -d "{\"extra_vars\": {\"env\": \"staging\", \"version\": \"$CI_COMMIT_SHORT_SHA\"}}" \
        "$AAP_URL/api/controller/v2/job_templates/42/launch/")
      JOB_ID=$(echo $JOB | jq -r '.id')
      echo "Launched AAP job $JOB_ID"
      # Poll for completion
      while true; do
        STATUS=$(curl -s -k \
          -H "Authorization: Bearer $AAP_TOKEN" \
          "$AAP_URL/api/controller/v2/jobs/$JOB_ID/" | jq -r '.status')
        echo "Job $JOB_ID: $STATUS"
        [ "$STATUS" = "successful" ] && break
        [ "$STATUS" = "failed" ] || [ "$STATUS" = "error" ] || [ "$STATUS" = "canceled" ] && exit 1
        sleep 10
      done
  environment:
    name: staging
  only:
    - main

deploy_production:
  stage: deploy
  script:
    - |
      curl -s -k -X POST \
        -H "Authorization: Bearer $AAP_TOKEN" \
        -H "Content-Type: application/json" \
        -d "{\"extra_vars\": {\"env\": \"production\", \"version\": \"$CI_COMMIT_SHORT_SHA\"}}" \
        "$AAP_URL/api/controller/v2/workflow_job_templates/10/launch/"
  environment:
    name: production
  when: manual
  only:
    - main

See also: Automate Ansible Galaxy Roles with GitHub Actions

Jenkins

Jenkins Pipeline

// Jenkinsfile
pipeline {
    agent any

    environment {
        AAP_URL = credentials('aap-url')
        AAP_TOKEN = credentials('aap-token')
    }

    stages {
        stage('Lint') {
            steps {
                sh 'ansible-navigator lint playbooks/ -m stdout --ee false'
            }
        }

        stage('Deploy to Staging') {
            steps {
                script {
                    def response = httpRequest(
                        url: "${AAP_URL}/api/controller/v2/job_templates/42/launch/",
                        httpMode: 'POST',
                        customHeaders: [[name: 'Authorization', value: "Bearer ${AAP_TOKEN}"]],
                        contentType: 'APPLICATION_JSON',
                        requestBody: """{"extra_vars": {
                            "env": "staging",
                            "version": "${env.BUILD_NUMBER}",
                            "git_sha": "${env.GIT_COMMIT}"
                        }}"""
                    )
                    def job = readJSON text: response.content
                    env.AAP_JOB_ID = job.id
                    echo "AAP Job launched: ${job.id}"
                }
            }
        }

        stage('Wait for Staging') {
            steps {
                script {
                    waitForAAPJob(env.AAP_JOB_ID)
                }
            }
        }

        stage('Approve Production') {
            steps {
                input message: 'Deploy to production?', ok: 'Deploy'
            }
        }

        stage('Deploy to Production') {
            steps {
                script {
                    def response = httpRequest(
                        url: "${AAP_URL}/api/controller/v2/workflow_job_templates/10/launch/",
                        httpMode: 'POST',
                        customHeaders: [[name: 'Authorization', value: "Bearer ${AAP_TOKEN}"]],
                        contentType: 'APPLICATION_JSON',
                        requestBody: """{"extra_vars": {"env": "production", "version": "${env.BUILD_NUMBER}"}}"""
                    )
                    def job = readJSON text: response.content
                    waitForAAPJob(job.id)
                }
            }
        }
    }
}

def waitForAAPJob(jobId) {
    timeout(time: 30, unit: 'MINUTES') {
        while (true) {
            def resp = httpRequest(
                url: "${AAP_URL}/api/controller/v2/jobs/${jobId}/",
                customHeaders: [[name: 'Authorization', value: "Bearer ${AAP_TOKEN}"]]
            )
            def job = readJSON text: resp.content
            echo "Job ${jobId}: ${job.status}"
            if (job.status == 'successful') return
            if (job.status in ['failed', 'error', 'canceled']) {
                error "AAP job ${jobId} ${job.status}"
            }
            sleep 10
        }
    }
}

GitOps Workflow Pattern

[Developer pushes to Git]
        ↓
[CI Pipeline: lint + test]
        ↓ pass
[Pipeline triggers AAP webhook]
        ↓
[AAP: Project Sync (git pull)]
        ↓
[AAP: Deploy to Staging]
        ↓ success
[AAP: Run Integration Tests]
        ↓ success
[AAP: Approval Gate (workflow)]
        ↓ approved
[AAP: Deploy to Production (rolling)]
        ↓
[AAP: Post-deploy Verify + Notify]

AAP Workflow for Full GitOps

- name: Create GitOps deployment workflow
  ansible.platform.workflow_job_template:
    controller_host: "{{ gateway_url }}"
    controller_username: "{{ controller_user }}"
    controller_password: "{{ controller_pass }}"
    name: "GitOps Deploy"
    organization: "Operations"
    webhook_service: github
    survey_enabled: true
    survey_spec:
      name: "Deploy Parameters"
      description: ""
      spec:
        - question_name: "Environment"
          variable: target_env
          type: multiplechoice
          choices: ["staging", "production"]
          required: true
        - question_name: "Version/SHA"
          variable: deploy_version
          type: text
          required: true
    state: present

See also: Automate Ansible Collection Testing with GitHub Actions

Best Practices

Credential Management

# Store AAP credentials as CI/CD secrets, never in code
# GitHub: Settings → Secrets → AAP_URL, AAP_TOKEN
# GitLab: Settings → CI/CD → Variables
# Jenkins: Credentials → Secret text

# Use short-lived tokens
- name: Create CI/CD service credential
  ansible.platform.token:
    controller_host: "{{ gateway_url }}"
    controller_username: "{{ controller_user }}"
    controller_password: "{{ controller_pass }}"
    description: "GitLab CI Token"
    scope: "write"
    state: present

Idempotent Deployments

# Playbooks should be safe to re-run
- name: Deploy application (idempotent)
  hosts: webservers
  tasks:
    - name: Deploy version
      ansible.builtin.template:
        src: app-config.j2
        dest: /etc/myapp/config.yml
      notify: restart myapp

    - name: Ensure desired version running
      ansible.builtin.uri:
        url: "http://localhost:8080/health"
        return_content: true
      register: health
      until: health.json.version == deploy_version
      retries: 30
      delay: 10

FAQ

Should the CI pipeline or AAP own the deployment?

Use CI for building, testing, and artifact creation. Use AAP for infrastructure deployment. AAP provides RBAC, credential injection, audit logging, and rollback that CI tools lack.

How do I handle rollbacks?

Configure an AAP workflow with a rollback path. On job failure, the workflow triggers a rollback job template that deploys the previous known-good version. Store version history in AAP survey variables or external CMDB.

Can I use AAP Collections in CI pipelines?

Yes. Install ansible.controller or ansible.platform in your pipeline and use the collection modules to interact with AAP programmatically — launching jobs, creating inventories, managing credentials.

How do I test Ansible playbooks in CI?

Use Molecule for role-level testing, ansible-navigator run --check for syntax/dry-run validation, and ansible-lint for style checks. Run integration tests against ephemeral infrastructure in staging before production.

What about branch-based deployments?

Use AAP project branches — set the SCM branch dynamically via extra_vars or survey. Feature branches deploy to dev environments, main deploys to staging/production.

Conclusion

AAP 2.6 integrates seamlessly into CI/CD pipelines through webhooks, REST API, and native plugins. Whether using GitHub Actions, GitLab CI, or Jenkins, the pattern is consistent: CI handles build and test, AAP handles deployment with enterprise controls. GitOps workflows combine both for fully automated, auditable infrastructure delivery.

Category: installation

Browse all Ansible tutorials · AnsiblePilot Home