Migration Guide

Ansible-to-Parallax Migration Guide

This guide covers migrating existing Ansible playbooks and workflows to Parallax.

Overview

Parallax is a Go-based Ansible-compatible playbook executor. Key differences from Ansible:

  • Single binary — no Python runtime or pip dependencies on the controller or remote hosts
  • Compiled modules — modules compile to Go binaries, shipped and executed on targets
  • gonja templating — Go-based Jinja2 implementation (compatible with most Jinja2 syntax)
  • Lock-free concurrency — channel-based parallelism instead of Python multiprocessing

Most Ansible playbooks run without modification. This guide documents the areas that need attention.

Installation

# From source
git clone https://go.digitalxero.dev/parallax.git
cd parallax
task build

# Or via go install
go install go.digitalxero.dev/parallax@latest

The parallax binary is the only dependency. No Python, pip, or ansible packages are needed on the controller or targets.

Quick Start

# Run a playbook (same as ansible-playbook)
parallax play playbook.yml

# With inventory
parallax play -i inventory.ini playbook.yml

# Check mode (dry run)
parallax play --check playbook.yml

CLI Flag Mapping

Core Flags

Ansible (ansible-playbook)Parallax (parallax play)Notes
-i INVENTORY-i INVENTORYIdentical
-C / --check-C / --checkIdentical
-b / --become-b / --becomeIdentical
--become-user USER--become-user USERIdentical
--become-method METHOD--become-method METHODsudo, su, doas, pfexec
--tags TAGS--tags TAGSComma-separated
--skip-tags TAGS--skip-tags TAGSComma-separated
-e KEY=VALUE-e KEY=VALUESupports key=value, @file, JSON
-l PATTERN-l PATTERNIdentical
-D / --diff-D / --diffIdentical
--start-at-task NAME--start-at-task NAMEIdentical
--forks N--forks NDefault 5

SSH Flags

AnsibleParallaxNotes
--private-key FILE--private-key FILEIdentical
--ask-pass--ask-passIdentical
-u USER--ssh-user USERDifferent flag name
--ssh-common-argsN/AUse individual flags below
N/A--ssh-port PORTDefault 22
N/A--ssh-timeout DURATIONDefault 30s
N/A--ssh-retries NDefault 3
N/A--ssh-retry-interval DURATIONDefault 5s
N/A--ssh-keepaliveDefault true
N/A--ssh-keepalive-interval DURATIONDefault 30s
N/A--strict-host-checkingDefault true
N/A--known-hosts FILEDefault ~/.ssh/known_hosts
N/A--use-agentDefault true

Bastion/Jump Host Flags

AnsibleParallaxNotes
--ssh-common-args "-J user@bastion"--bastion-host HOSTDedicated flag
N/A--bastion-port PORTDefault 22
N/A--bastion-user USERDefaults to –ssh-user
N/A--bastion-key FILEBastion-specific key

Vault Flags

AnsibleParallaxNotes
--vault-id label@source--vault-id label@sourceIdentical
--vault-password-file FILE--vault-password-file FILEIdentical
--ask-vault-pass--ask-vault-passIdentical

Parallax-Only Flags

These flags have no Ansible equivalent:

FlagDefaultDescription
--connection-poolingfalseReuse SSH connections across tasks
--pool-max-connections20Max pooled connections
--precompilefalsePre-build module binaries
--precompile-backgroundfalseBackground precompilation
--cachetrueCache compiled module binaries
--cache-dir~/.parallax/module-cacheCache location
--facts-paralleltrueParallel facts gathering
--facts-concurrency10Max concurrent fact gathers
--facts-cachetrueCache gathered facts
--facts-incrementaltrueOnly re-gather changed facts
--template-cachetrueCache compiled templates
--resource-managementfalseModule resource limits
--debug-enabledfalseEnhanced debug output
--debug-profilingfalseModule execution profiling
--debug-tracingfalseExecution tracing
--callbackdefaultCallback plugin (default, minimal, json, timer, profile_tasks)
--jsonfalseJSON output (shortcut for –callback json)
--validate-featuresfalseCheck Ansible feature compatibility

Playbook Compatibility

What Works Unchanged

The following Ansible playbook features work identically:

  • Play directives: hosts, name, become, become_user, become_method, gather_facts, vars, vars_files, roles, tasks, handlers, pre_tasks, post_tasks, strategy, serial, max_fail_percentage, any_errors_fatal, force_handlers, order, collections, module_defaults, environment, tags
  • Task directives: name, module args, when, register, loop, with_items, with_dict, with_list, with_fileglob, with_indexed_items, with_subelements, with_first_found, notify, tags, become, become_user, ignore_errors, no_log, changed_when, failed_when, until, retries, delay, delegate_to, run_once, check_mode, diff, connection, async, poll, throttle, block, rescue, always
  • Includes/imports: import_tasks, include_tasks, import_role, include_vars
  • Handlers: Standard notify with handler names, listen, meta: flush_handlers
  • Roles: Standard role structure (tasks, handlers, defaults, vars, files, templates, meta)
  • Variables: All precedence levels, set_fact, extra vars (-e), group_vars, host_vars

Known Parser Quirks

  1. notify: must be a YAML list, not a string:
# Works
- name: Restart nginx
  service:
    name: nginx
    state: restarted
  notify:
    - restart nginx

# Does NOT work
- name: Restart nginx
  service:
    name: nginx
    state: restarted
  notify: restart nginx
  1. shell: with cmd: map form can cause issues with multiple handlers. Use the string form:
# Preferred
- name: Run command
  shell: "echo hello"

# May cause issues in some contexts
- name: Run command
  shell:
    cmd: "echo hello"
  1. local_action: runs but does not capture stdout reliably. Use delegate_to: localhost:
# Preferred
- name: Run locally
  command: echo hello
  delegate_to: localhost

# Works but limited stdout capture
- name: Run locally
  local_action: command echo hello
  1. meta: task syntax is fully supported. Valid actions: flush_handlers, clear_facts, clear_host_errors, noop, end_play, end_host, end_batch, refresh_inventory, reset_connection.

Inventory Compatibility

All Ansible inventory formats are supported:

INI Format

[webservers]
web1.example.com ansible_host=10.0.0.1
web2.example.com ansible_port=2222

[dbservers]
db[1:3].example.com

[webservers:vars]
http_port=80

[all:children]
webservers
dbservers

YAML Format

all:
  hosts:
    web1.example.com:
      ansible_host: 10.0.0.1
  children:
    webservers:
      hosts:
        web2.example.com:
          ansible_port: 2222
      vars:
        http_port: 80

Dynamic Inventory

Dynamic inventory scripts (executables that output JSON) work identically.

Host Patterns

All Ansible host patterns are supported:

  • Group names: webservers
  • Intersection: webservers:&dbservers
  • Exclusion: webservers:!staging
  • Ranges: web[0:2].example.com
  • Regex: ~.*\.web\..*
  • Comma-separated: host1,host2,host3

group_vars / host_vars

Standard group_vars/ and host_vars/ directories are loaded from both the inventory directory and the playbook directory, matching Ansible’s behavior. YAML and JSON files are supported. Vault-encrypted files are automatically decrypted when vault passwords are provided.

Module Compatibility

ansible.builtin (71 modules)

All 71 ansible.builtin modules are implemented:

Core: command, shell, copy, file, template, debug, ping, stat, fetch, service, user, group

Package Management: package, apt, yum, dnf, dnf5, pip, rpm_key

System: systemd, systemd_service, sysvinit, gather_facts, setup, service_facts, mount_facts, reboot, hostname

File Operations: lineinfile, replace, blockinfile, assemble

Network & Utilities: get_url, uri, unarchive, cron, mount

Control Flow: set_fact, assert, fail, pause, wait_for, wait_for_connection, meta, add_host, group_by, include_vars, import_tasks, include_tasks, import_role

Advanced: script, raw, tempfile, validate_argument_spec, vars_prompt, subversion, git, slurp, package_facts

Specialized: apt_key, apt_repository, yum_repository, deb822_repository, debconf, dpkg_selections, expect, getent, iptables, known_hosts, async_status, find

ansible.posix (13 modules)

acl, at, authorized_key, firewalld, mount (posix), patch, seboolean, selinux, synchronize, sysctl, rhel_facts, rhel_rpm_ostree, rpm_ostree_pkg

community.general (50 modules)

ini_file, xml, archive, read_csv, json_query, htpasswd, ssh_config, listen_ports_facts, timezone, locale_gen, alternatives, pam_limits, make, modprobe, capabilities, supervisorctl, npm, yarn, gem, composer, cpanm, easy_install, snap, flatpak, homebrew, ufw, nmcli, sefcontext, syslogd, mail, slack, terraform, parted, lvg, lvol, filesystem, lvm_facts, xfs_quota, zfs, postgresql_user, postgresql_privs, mysql_user, redis, rabbitmq_user, jenkins_job, docker_compose, docker_volume, docker_network, fileglob, dconf

Module Naming

Modules can be referenced by short name or FQCN:

# All equivalent
- command: echo hello
- ansible.builtin.command: echo hello
- parallax.builtin.command: echo hello

Templating

Parallax uses gonja, a Go-based Jinja2 implementation. Most Jinja2 syntax works identically.

Supported Features

  • Variable interpolation: {{ variable }}
  • Control structures: {% if %}, {% for %}, {% set %}, {% block %}, {% macro %}
  • Comments: {# comment #}
  • Whitespace control: {%- -%}, {{- -}}
  • Template inheritance: {% extends %}, {% include %}
  • Expressions: math, comparisons, logical operators

Filters

71 custom Ansible-compatible filters plus all gonja built-in filters:

  • String: quote, split, regex_search, regex_replace, regex_findall, regex_escape, comment, ternary
  • Collection: count, flatten, dict2items, items2dict, combine, union, intersect, difference, symmetric_difference, zip, zip_longest, subelements
  • Math: pow, log, root, human_readable, human_to_bytes
  • Type: bool, type_debug, mandatory, to_json, to_nice_json, to_yaml, to_nice_yaml, from_json, from_yaml, to_uuid, to_datetime, strftime
  • Path: basename, dirname, expanduser, expandvars, realpath, relpath, splitext, win_basename, win_dirname, win_splitdrive
  • Crypto: checksum, hash, password_hash, b64encode, b64decode
  • Network: ipaddr, ipv4, ipv6, ip4_hex, ipmath, ipwrap, ipsubnet, nthhost, network_in_network, network_in_usable, hwaddr, macaddr, reduce_on_network, slaac
  • URL: urlsplit
  • Data Query: json_query (JMESPath)

Jinja2 Tests

24 custom tests plus gonja built-ins: truthy, falsy, match, search, regex, contains, startswith, endswith, subset, superset, any, all, version, version_compare, changed, failed, success, succeeded, skipped, unreachable, started, finished

Lookups

16 lookup plugins: file, env, pipe, dict, indexed_items, sequence, password, lines, fileglob, varnames, url, csvfile, ini, first_found, template, config

Known Templating Differences

gonja is highly compatible with Jinja2 but minor edge cases may differ in:

  • Complex nested macro definitions
  • Unusual whitespace handling in edge cases
  • Some Python-specific string methods

If you encounter a template that renders differently, simplify the expression or split it into multiple steps.

Vault

Parallax provides full Ansible Vault compatibility.

Supported Formats

  • Vault 1.1: $ANSIBLE_VAULT;1.1;AES256
  • Vault 1.2: $ANSIBLE_VAULT;1.2;AES256;vault-id

CLI Commands

parallax vault encrypt file.yml
parallax vault decrypt file.yml
parallax vault view file.yml
parallax vault edit file.yml
parallax vault encrypt_string "secret" --name my_var
parallax vault rekey file.yml --new-vault-password-file new_pass.txt

Password Sources

  • --vault-password-file FILE — read password from file
  • --vault-id label@source — labeled vault identity (file or script)
  • --ask-vault-pass — interactive prompt
  • ANSIBLE_VAULT_PASSWORD environment variable

Inline Vault Variables

Vault-encrypted values embedded in YAML files are automatically decrypted during variable loading. This works in playbook vars, group_vars, host_vars, and vars_files.

Connection Types

TypeFlagDescription
SSHconnection: ssh (default)Standard SSH with agent, key, or password auth
Localconnection: localExecute on controller
WinRMconnection: winrmWindows Remote Management (Basic, NTLM, Certificate, Kerberos)
Dockerconnection: dockerExecute in Docker/Podman containers

Features With Behavioral Notes

These features are fully implemented but may have minor behavioral differences:

FeatureAnsibleParallaxNotes
Strategystrategy: free/linearstrategy: free/linear/host_pinnedAll three strategies implemented
Throttlethrottle: Nthrottle: NAlso available via --forks
Host orderorder: sortedorder: sorted/reverse_sorted/shuffle/reverse_inventory/inventoryAll 5 modes
Asyncasync: N / poll: Nasync: N / poll: NFull async support with async_status
Serialserial: N/%/[list]serial: N/%/[list]Integer, percentage, and list forms
Check mode--check--checkPer-task override via check_mode:

Parallax-Only Features

Features available in Parallax with no Ansible equivalent:

Module Caching

Compiled module binaries are cached to avoid recompilation:

parallax play --cache playbook.yml          # enabled by default
parallax play --cache-info stats            # show cache statistics
parallax play --cache-clear                 # clear all cached modules

Connection Pooling

Reuse SSH connections across tasks for improved performance:

parallax play --connection-pooling playbook.yml
parallax play --connection-pooling --pool-max-connections 50 playbook.yml

Parallel Facts Gathering

Gather facts from multiple hosts concurrently:

parallax play --facts-parallel --facts-concurrency 20 playbook.yml
parallax play --facts-incremental playbook.yml  # only re-gather changed facts

Module Precompilation

Pre-build module binaries before execution:

parallax play --precompile playbook.yml
parallax play --precompile-background playbook.yml  # background precompilation

Debug and Profiling

parallax play --debug-enabled --debug-level verbose playbook.yml
parallax play --debug-profiling playbook.yml   # module execution timing
parallax play --debug-report playbook.yml      # comprehensive report at end

Resource Management

parallax play --resource-management --max-memory 512 --max-cpu 80 playbook.yml

Template Caching

parallax play --template-cache --template-cache-size 2000 playbook.yml

Callback Plugins

parallax play --callback minimal playbook.yml
parallax play --callback json playbook.yml
parallax play --callback timer playbook.yml
parallax play --callback profile_tasks playbook.yml

Migration Checklist

  1. Install Parallax — build from source or use go install
  2. Test existing playbooks — run with --check first to verify parsing
  3. Review notify syntax — ensure notify: uses YAML list format
  4. Review local_action usage — switch to delegate_to: localhost if stdout capture is needed
  5. Review shell tasks — prefer shell: "string" form over shell: {cmd: "string"}
  6. Test with real execution — run against a staging environment
  7. Review output — compare task results with Ansible output
  8. Update CI/CD — replace ansible-playbook with parallax play in pipelines
  9. Enable performance features — try --connection-pooling, --precompile, --facts-parallel
  10. Validate compatibility — use --validate-features to check for unsupported patterns