Ansible Awx Patterns

Production engineering guide for ansible awx patterns covering patterns, implementation strategies, and operational best practices.

Ansible AWX is a web-based interface and orchestration engine for Ansible, built on top of the Ansible control plane. It enables teams to manage, schedule, and monitor Ansible playbooks at scale. This page documents common patterns for using Ansible AWX effectively in production environments—patterns that emerge from real workflows, not theoretical design. These patterns address recurring problems: how to version and deploy playbooks, how to configure environments, how to handle secrets, and how to scale execution across teams and systems. They are grounded in the actual interfaces, APIs, and CLI tools that AWX exposes, using real commands, real configuration keys, and real failure modes.

Managing Playbook Versions with Git and AWX

AWX integrates directly with Git repositories to manage playbook sources. To ensure consistency, use a single Git branch per environment: main for production, staging, development, and release/*.

Each project in AWX must reference a Git repository and specify a branch, tag, or commit. The git source type supports url, branch, timeout, and credential fields. The credential field links to a named credential object, which may be a Machine or Personal Access Token type.

When a project is created via the API, use:

{
  "name": "web-app-deploy",
  "description": "Deploy web application to production",
  "scm_type": "git",
  "scm_url": "https://git.example.com/app.git",
  "scm_branch": "main",
  "scm_credential": 42,
  "scm_update_on_launch": true,
  "scm_update_cache_timeout": 300
}

Key failure mode: scm_update_on_launch is often misunderstood. When enabled, AWX fetches the latest Git revision *before* each job run. But if scm_update_cache_timeout is not set, the cache is never invalidated. Jobs may run on stale code. To ensure freshness, set scm_update_cache_timeout to at least 300 seconds (5 minutes), and use scm_update_on_launch on every job template.

Use awx-cli (a standard command-line tool for AWX) to automate project creation and synchronization:

awx project create \
  --name "api-service" \
  --scm-url "[email protected]:org/api-service.git" \
  --scm-branch "staging" \
  --scm-credential "github-creds" \
  --scm-update-on-launch \
  --scm-update-cache-timeout 600

After changes, trigger a manual update via API:

curl -X POST \
  -H "Authorization: Bearer $AWX_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{}' \
  https://awx.example.com/api/projects/42/update/

Running Jobs with Dynamic Inventory and Credentials

AWX supports dynamic inventory sources, which can be updated via API, cron, or on-demand. The most reliable pattern is to use a Script inventory source that runs a Python script or shell script returning JSON.

Example inventory script (inventory.py):

#!/usr/bin/env python3
import json
import sys

def main():
    inventory = {
        "_meta": {
            "hostvars": {}
        },
        "webservers": {
            "hosts": ["web01.example.com", "web02.example.com"],
            "vars": {
                "ansible_user": "deploy"
            }
        },
        "databases": {
            "hosts": ["db01.example.com"],
            "vars": {
                "db_type": "postgresql"
            }
        }
    }

    json.dump(inventory, sys.stdout)

if __name__ == "__main__":
    main()

Register it as an inventory source:

{
  "name": "production-inventory",
  "inventory_type": "script",
  "script": "#!/bin/bash\npython3 /opt/inventory/inventory.py",
  "update_interval": 300,
  "update_on_launch": true,
  "credential": 17,
  "source": "/opt/inventory/inventory.py"
}

When a job runs, AWX executes the script and caches the result. The update_on_launch flag ensures the inventory is regenerated *before* the job starts. Without it, jobs may use stale inventory, especially in CI/CD pipelines where hosts are added or removed during deployment.

Credentials are tied to the job through the credentials field in the job template. Each credential is a named object with name, type, and inputs. For example, a vault credential has vault_password and vault_file.

To run a job with a specific set of credentials:

{
  "job_template": 12,
  "extra_vars": {
    "deploy_env": "staging",
    "app_version": "v2.1.0"
  },
  "credentials": [
    42,
    55
  ]
}

The credentials array is the key: each ID refers to a credential object, and they are applied in order. If multiple credentials are provided, the later ones override earlier ones in extra_vars.

Using Tower-like Job Templates with Custom Workflow

AWX supports job templates, which are pre-configured executions of playbooks. Each job template has a project, inventory, playbook, credential, limit, extra_vars, and job_type (run, check, survey).

The most common pattern is to use a run job template with limit and extra_vars for targeted deployments.

To create a job template with a custom inventory and limit:

{
  "name": "deploy-api-service",
  "description": "Deploy the API service to a specific environment",
  "job_type": "run",
  "project": 14,
  "inventory": 23,
  "playbook": "deploy.yml",
  "limit": "webservers",
  "extra_vars": {
    "app_version": "{{ app_version }}",
    "deploy_user": "ci-user"
  },
  "survey_enabled": true,
  "survey": [
    {
      "name": "app_version",
      "label": "Application Version",
      "type": "string",
      "required": true,
      "default": "latest"
    },
    {
      "name": "rollback",
      "label": "Rollback Previous Release?",
      "type": "bool",
      "required": false,
      "default": false
    }
  ]
}

The survey_enabled flag enables a user-facing form during job execution. The survey array defines the form fields. When a job is launched, users see the form and can override values. The extra_vars field is populated from the form, and extra_vars are merged in the order: default → form input → job template extra_vars.

A subtle failure: survey fields are not automatically passed to extra_vars. If app_version is set in the survey, it appears in extra_vars, but it is not automatically available in the playbook unless explicitly referenced. To fix, use {{ app_version }} in the playbook, or ensure the survey field name matches the extra_vars key exactly.

Automating Job Execution with Webhooks and Scheduling

AWX provides a webhook endpoint to trigger jobs from external systems. The endpoint is /api/jobs/ with POST method, and expects a JSON payload:

{
  "job_template": 12,
  "extra_vars": {
    "commit_sha": "a1b2c3d4e5f6",
    "branch": "feature/new-ui"
  },
  "inventory": 23,
  "limit": "web01",
  "credentials": [42]
}

The job_template field is required. inventory and limit are optional. credentials is an array of credential IDs. This structure is used by CI/CD systems like GitHub Actions, GitLab CI, and Jenkins.

To trigger a job from GitHub Actions:

- name: Trigger AWX Deployment
  run: |
    curl -X POST \
      -H "Authorization: Bearer $AWX_TOKEN" \
      -H "Content-Type: application/json" \
      -d '{
        "job_template": 12,
        "extra_vars": {
          "commit_sha": "${{ github.sha }}",
          "branch": "${{ github.ref_name }}"
        },
        "inventory": 23,
        "limit": "web01"
      }' \
      https://awx.example.com/api/jobs/

Scheduling jobs uses the schedule resource. A schedule has name, enabled, rrule, extra_vars, and job_template.

Example rrule (Recurrence Rule):

{
  "name": "daily-backup",
  "job_template": 31,
  "enabled": true,
  "rrule": "FREQ=DAILY;INTERVAL=1;BYHOUR=2;BYMINUTE=30",
  "extra_vars": {
    "backup_type": "full"
  }
}

The rrule uses iCalendar standard syntax. FREQ=DAILY means daily recurrence. INTERVAL=1 means every day. BYHOUR=2;BYMINUTE=30 means at 2:30 AM UTC.

A common mistake: rrule is not evaluated at job launch time. The extra_vars are evaluated once at schedule creation, and are not re-evaluated on each run. To make them dynamic, use {{ now }} or {{ job_id }} in extra_vars, but the values are resolved at the time the schedule is created.

To update a schedule with dynamic extra_vars:

{
  "name": "weekly-report",
  "job_template": 44,
  "enabled": true,
  "rrule": "FREQ=WEEKLY;INTERVAL=1;BYDAY=MO;BYHOUR=9;BYMINUTE=0",
  "extra_vars": {
    "report_period": "last-week",
    "send_email": true,
    "email_to": "{{ job_id }}@example.com"
  }
}

Managing Secrets with AWX and Vault

AWX integrates with Ansible Vault for managing secrets. A vault credential type stores the vault password and file path.

To use vaulted secrets, include a vault_password in the job template’s credentials list. The vault file is expected at {{ vault_file }} on the target system.

The extra_vars field can include vault_password, which overrides the credential’s vault_password.

When a playbook uses --ask-vault-pass, AWX prompts the user for the vault password during job execution. To avoid this, set vault_password in the credential or pass it via extra_vars.

To ensure consistent secret management, create a secrets project in AWX that contains group_vars, host_vars, and vault.yml files.

Example group_vars/all/vault.yml:

---
ansible_user: deploy
database_password: "{{ vault_db_password }}"
app_secret_key: "{{ vault_app_secret }}"

To update the vault file, use ansible-vault CLI:

ansible-vault edit --vault-password-file=~/vault-pass.txt group_vars/all/vault.yml

Then commit the updated vault.yml to the Git repository. AWX automatically picks up the change on next job launch.

A sharp edge: AWX does not automatically decrypt the vault file during job execution. The ansible-playbook command must be executed with --vault-password-file or --ask-vault-pass. If the playbook is not configured to load the vault file, vault_password is available but the vault.yml contents are not.

To fix, ensure the job template runs:

ansible-playbook -i inventory.yml \
  --vault-password-file=/tmp/vault-pass \
  --extra-vars @extra_vars.json \
  deploy.yml

Monitoring and Troubleshooting Jobs

AWX exposes job logs via the job resource. Each job has status, started, ended, stdout, and artifacts.

To retrieve job logs:

curl -H "Authorization: Bearer $AWX_TOKEN" \
  https://awx.example.com/api/jobs/123/

The response includes stdout and artifacts as base64-encoded strings.

To extract logs and artifacts:

#!/bin/bash
JOB_ID=123
TOKEN="abc123"

curl -H "Authorization: Bearer $TOKEN" \
  "https://awx.example.com/api/jobs/$JOB_ID/" \
  -o job.json

jq -r '.stdout' job.json > job.log
echo "$JOB_ID: $(jq -r '.status' job.json)"


mkdir -p artifacts
for art in $(jq -r '.artifacts | keys | .[]' job.json); do
  echo "Extracting $art..."
  base64 -d <<< "$(jq -r ".artifacts.\"$art\"" job.json)" > artifacts/$art
done

A common failure: job.status is running, but stdout is empty. This happens when the job runner (e.g., awx_task or awx_worker) is not connected to the AWX web server. Check the awx_task and awx_worker processes with systemctl:

systemctl status awx_task awx_worker
journalctl -u awx_task -f

If logs show ConnectionError or timeout, check the database connection and ensure the awx database is accessible.

Summary

Ansible AWX patterns emerge from real workflows: versioned playbooks, dynamic inventory, credential-driven jobs, scheduled and webhook-triggered executions, and vaulted secrets. The patterns are defined by precise configuration keys, API payloads, and CLI commands. They are not tied to a single product’s UI or internal design. The reader can apply them to any AWX instance, regardless of hosting or configuration. The patterns are robust, but the sharp edges—like scm_update_cache_timeout, survey field mapping, rrule evaluation timing, and vault file loading—are where failures occur. Mastery comes from understanding not just how to use AWX, but how it silently fails, and how to diagnose and fix it.

This page was rewritten on 18 October 2026. It replaced a templated version that was not specific to its own title. How these pages are written and checked. If anything here is wrong, tell us at [email protected] and we will correct it.