Ansible

Ansible

Certified

Tasks that orchestrate Ansible playbooks from Kestra flows using Ansible Runner or raw CLI.

Use this plugin set when you need to run Ansible automation inside Kestra with containerized runners.

Which task to use?

  • Use AnsibleRunner for complex orchestrations. It uses ansible-runner to provide enterprise-grade execution environments, isolated working directories, structured JSON telemetry (status, job_events, rc), and automatic artifact archiving.
  • Use AnsibleCLI to run raw ansible-playbook commands. It provides maximum flexibility with direct CLI arguments and supports Namespace Files, inline playbooks, and injects a custom callback for structured logs.

How to use the Ansible plugin

Run Ansible playbooks and ad-hoc commands from Kestra flows inside a container with the Ansible CLI pre-installed.

Common properties

containerImage defaults to cytopia/ansible: latest-tools. taskRunner controls where the container runs — defaults to Docker. Pass inventory files, playbooks, and other supporting files via inputFiles (inline content) or pull them from namespace files. Target host credentials (SSH keys, passwords) are supplied through the inventory file using standard Ansible inventory variables rather than plugin-level properties.

Tasks

runner.AnsibleRunner is the enterprise-grade orchestrator that runs playbooks within containerized Ansible Execution Environments (EEs). It enforces a strict tri-layer contract mapping Kestra inputs into isolated /runner/project, /runner/inventory, and /runner/env structures. By extracting execution telemetry and automatically masking multi-line secrets like SSH keys, AnsibleRunner is best suited for robust, traceable CI/CD workflows and multi-node Ansible automation.

cli.AnsibleCLI runs one or more Ansible CLI commands set in commands (e.g. ansible-playbook site.yml -i inventory.ini). Use beforeCommands to run setup steps before the main commands, env to inject environment variables, and outputFiles to capture files produced during execution. Set ansibleConfig to supply a custom ansible.cfg; if omitted, Kestra generates one automatically with its structured output callback enabled.

galaxyDependencies and pythonDependencies install Galaxy collections and pip packages before commands run, alongside the existing requirements.yml/requirements.txt auto-install (autoInstallGalaxyRequirements/autoInstallPythonRequirements, both default true). All of it installs into a working-directory subtree instead of the image's default ~/.ansible and virtual environment, so every command in a multi-command task sees the same collections/packages, and the result is cached across runs (dependencyCacheEnabled, default true) keyed on the task runner, container image, and declared dependencies. Cached installs expire after dependencyCacheTtl (default 7 days): the key does not track the image digest or new releases of unpinned dependencies, so the TTL bounds how stale a floating tag like the default latest can get.

Output capture and custom callbacks

The task captures each playbook's per-host results into outputs.<taskId>.playbooks (a light result per host, with the full results in the file at outputs.<taskId>.resultsUri unless resultsStorage: INLINE) and the values declared through the bundled kestra module into outputs.<taskId>.vars.outputs, through a bundled Ansible callback (kestra_logger). Ansible loads that callback only if it can find it on its callback search path, so the task adds its bundled callback and module directories to ANSIBLE_CALLBACK_PLUGINS and ANSIBLE_LIBRARY before running commands. This keeps outputs working even when the playbook is in a subdirectory (e.g. ansible-playbook ansible/site.yml) or the runner image sets its own callback path (e.g. ARA-instrumented images).

The callback writes its outputs/playbooks payload to a file in the working directory instead of printing it to stdout, so a large ALL-mode run (many hosts/tasks) never depends on the task runner processing one giant stdout line. That file is written once per commands entry, so chaining two ansible-playbook invocations with &&/; in a single entry makes the second silently overwrite the first's payload; list each invocation as its own commands entry for reliable output capture. maxOutputsSize bounds the serialized size of the whole emitted task output (default 10 MB; with resultsStorage: STORE, the stored results file is not counted) and fails the task with an actionable error instead of risking an oversized WorkerTaskResult; raise it or switch to outputsMode: EXPLICIT if you hit it. logsMode (default SUMMARY) keeps per-host task logs to one short line per host, with the failure reason, return code and last 1 KB of stderr kept for failed, unreachable and ignored hosts, plus one extra line per failed loop item (capped at 50 per host and task); a failure tolerated through ignore_errors or ignore_unreachable, or caught by a block's rescue section, is reported as ignored, logged at WARN, and does not fail the task run; set it to FULL to log the entire per-host result payload (it never overrides EXPLICIT-mode redaction, which happens upstream in the callback).

With outputsMode: EXPLICIT, captured results contain only changed and the failure message by default. Set logFailureDetails: true to additionally capture the return code (rc) and the last 1 KB of stderr for failed, unreachable and ignored hosts. For failed loop items, these details are included when available, with at most 50 failed or unreachable items retained per host and task. This option defaults to false; successful and skipped results remain redacted.

**Warning: ** stderr may contain passwords, tokens, or other sensitive information, so enable this option only when you are comfortable exposing those details in captured outputs and failure logs.

Ansible's own per-task output is off by default, so nothing is logged until a command returns. Set streamLogs: true to stream PLAY [...], TASK [...] and per-host ok: /changed: lines as the playbook runs. It adds to the end-of-run per-host logs rather than replacing them, so a task that prints content appears twice and log volume grows with the number of tasks in the playbook. Remove any ANSIBLE_STDOUT_CALLBACK override from the task's env when enabling it, or Ansible's own stdout callback renders each line a third time. EXPLICIT-mode redaction covers the streamed output too: play and task names and per-host statuses are shown, per-host payloads and module tracebacks are not.

Ansible writes its [WARNING]: and [DEPRECATION WARNING]: text to stderr, which Kestra otherwise logs at ERROR regardless of content, so a playbook that exits 0 filled the logs with ERROR rows. Those lines are logged at WARN instead. Anything else on stderr, ERROR! and fatal: included, is untouched. On ansible-core older than 2.21 a long warning is wrapped at 79 columns and arrives as several stderr lines, each logged at WARN on its own rather than reassembled.

Any value already present on those environment variables is preserved, so your own callbacks or modules keep loading alongside the Kestra ones. If you use your own callbacks or modules, set their paths through the task's env:

env: 
  ANSIBLE_CALLBACK_PLUGINS: "/opt/mycallbacks"   # loads alongside Kestra's

Limitation: a callback or module path configured only in a custom ansible.cfg (and not via the environment) is superseded by these variables, because in Ansible an environment variable overrides the config file. If you rely on such a path, set it via the task's env as shown above so it is preserved.