Conn3x vs Ansible
This page is for you if Ansible configures your servers and you don’t use Terraform or OpenTofu. Maybe the servers already exist, or someone else creates them. You want to know whether Conn3x is worth a look, and what you’d give up.
Both tools log in over SSH and run what you’ve described, with nothing to install on the host first. Both run tasks in order and fire handlers when something changed. If you write Ansible, you already think the way a circuit works. The differences are in what you see before a run, and what’s remembered after it.
What’s different
Section titled “What’s different”With Ansible, the playbook is the truth and the host is checked against it on every run. There’s no state file, and for a lot of work that’s exactly what you want: nothing to store, lock or back up.
Conn3x keeps state. It’s built on OpenTofu, and it uses OpenTofu’s plan and state for servers the way OpenTofu uses them for cloud resources. That buys you three things:
- A plan on every run. Once a step has been applied,
conn3x planreads it back from the host and shows the diff, andconn3x applyshows it again and asks before it changes anything. Save it with-outand the apply does exactly what you reviewed. - A record of the last apply.
conn3x driftcompares the hosts with what was applied, changes nothing, and exits2when something has moved, so a scheduled job can alert on it. - Removal you can see. Delete a step and the plan lists it. Each module’s
on_removedecides what happens on the host. Most leave things in place by default, and most let you choose to clean up instead.
It costs you something too. State is one more thing to look after: somewhere to keep it, locking when a team shares it, and encryption when it holds a secret.
At a glance
Section titled “At a glance”| Ansible | Conn3x | |
|---|---|---|
| Connection | SSH, agentless; WinRM or SSH for Windows; network connection plugins for devices | SSH, agentless, Linux and Windows. An optional host agent is experimental and off by default |
| Language | YAML, with Jinja for templates and expressions | HCL, with templatefile syntax for templates |
| Before a run | --check --diff, as far as each module supports check mode |
conn3x plan, every time, and a saved plan with -out |
| State | None, by design | A state file, which OpenTofu can encrypt |
| Taking over a running server | Run the playbook against it; with no state, there’s nothing to adopt | import = true on a step adopts what’s there, and the first plan shows only the differences |
| Spotting hand edits | Rerun with --check --diff and read the recap. It exits 0 when it finds changes |
conn3x drift, which exits 2 |
| Removing something | Delete the task and the host keeps it, unrecorded. Write a state: absent task to remove it |
Delete the step and the plan shows it; on_remove decides what the host keeps |
| Cloud resources | Cloud collections such as amazon.aws, azure.azcollection and google.cloud |
Any OpenTofu provider, in the same plan as the servers |
| Ad-hoc commands | ansible all -a "uptime" |
None |
| Modules | A very large ecosystem of collections on Ansible Galaxy | 34 Linux modules, 23 experimental Windows modules, and 127 official circuits |
| Windows | Long established | Preview, with experimental modules |
| Network devices | Collections for many vendors’ switches, routers and firewalls | Three experimental circuits, through community OpenTofu providers |
| UI and access control | AWX, and Red Hat Ansible Automation Platform | None. Conn3x is a CLI that CI or cron runs |
| Maturity | In production use for more than a decade | Before 1.0, at v0.10.0 |
The same playbook, both ways
Section titled “The same playbook, both ways”Here’s a small playbook that installs nginx, writes a site config that answers on port 8080, and starts the service. The config reloads nginx when it changes. Save it as site.yml:
- hosts: web become: true tasks: - name: Install nginx ansible.builtin.package: name: nginx state: present
- name: Write the site config ansible.builtin.copy: dest: /etc/nginx/conf.d/app.conf content: | server { listen 8080; location / { return 200 "Hello from {{ inventory_hostname }}\n"; } } mode: "0644" notify: Reload nginx
- name: Start nginx ansible.builtin.service: name: nginx state: started enabled: true
handlers: - name: Reload nginx ansible.builtin.service: name: nginx state: reloadedAnd an inventory, hosts.ini:
[web]web1 ansible_host=203.0.113.10 ansible_user=deployConvert it
Section titled “Convert it”You don’t have to rewrite anything to see what this looks like as a circuit:
conn3x convert ansible -i hosts.ini -out web.circuit.hcl site.ymlThis is the converter’s real answer, from Conn3x v0.10.0:
Converted 2 tasks. 1 thing needs you; each is marked TODO(convert): line 9, Write the site config: content refers to "inventory_hostname", which isn't a play variable the converter can fill inIt exits with code 3, meaning it wrote the file and something is left. The package, the service and the handler came across. The config didn’t, because inventory_hostname is a magic variable, not one of the play’s vars, and the converter only fills in what it can show means the same thing. It leaves the task as commented YAML instead of guessing. Here’s the middle of web.circuit.hcl, with that comment shortened:
step "install_nginx" { package { name = "nginx" } }
# TODO(convert): Task "Write the site config" (site.yml line 9) content refers to "inventory_hostname", which isn't a play variable the converter can fill in. # - name: Write the site config # ansible.builtin.copy: # dest: /etc/nginx/conf.d/app.conf # ... # notify: Reload nginx
step "start_nginx" { service { name = "nginx" state = "started" enabled = true } }
handler "reload_nginx" { service { name = "nginx" action = "reload" } }It also wrote the circuit’s hosts from the inventory, user = "deploy" from ansible_user, become = true from the play, and a variable for your private key. Finishing the TODO is one step. The host’s name in a circuit is each.key:
step "write_the_site_config" { file { path = "/etc/nginx/conf.d/app.conf" content = <<-EOT server { listen 8080; location / { return 200 "Hello from ${each.key}\n"; } } EOT } notify = [handler.reload_nginx] }Catch a hand edit, both ways
Section titled “Catch a hand edit, both ways”To compare the two fairly, we pointed both at the same throwaway Rocky Linux 10 container with systemd, built from the Containerfile in circuit-nginx’s local example. We applied the converted circuit, with its TODO finished and hosts and ssh pointed at the container, then logged in and added access_log off; to the config by hand. Everything below is real output from those runs, trimmed where it says so.
Ansible 2.21’s check mode, ansible-playbook --check --diff site.yml, with the warnings and recap trimmed:
TASK [Write the site config] ***************************************************--- before: /etc/nginx/conf.d/app.conf+++ after: /etc/nginx/conf.d/app.conf@@ -1,6 +1,5 @@ server { listen 8080;- access_log off; location / { return 200 "Hello from web1\n"; }
changed: [web1]
TASK [Start nginx] *************************************************************ok: [web1]
RUNNING HANDLER [Reload nginx] *************************************************changed: [web1]That’s a good answer, and it’s worth saying plainly: for a file, Ansible’s check mode finds the same edit and shows the same diff. It exited 0, as it does whenever nothing fails.
conn3x plan, with the handler’s entry trimmed:
# conn3x_file.web__write_the_site_config["web1"] will be updated in-place ~ resource "conn3x_file" "web__write_the_site_config" { ~ content = <<-EOT server { listen 8080; - access_log off; location / { return 200 "Hello from web1\n"; } } EOT ~ content_wo = (write-only attribute) id = "127.0.0.1:2299:/etc/nginx/conf.d/app.conf" ~ revision = "f45d03b2e900beb6" -> (known after apply) ~ sha256 = "87e78eda4bda792140b41a833a7557c84224d0a23ce8831272001228d0b52562" -> "3eb5b493ef0a22f537bc4d4f6fa9151b97161ff194ca2565997fb5bca1adfaa4" ~ validate = (write-only attribute) # (3 unchanged attributes hidden)
# (1 unchanged block hidden) }
# conn3x_service_action.web__reload_nginx["web1"] must be replaced
Plan: 1 to add, 1 to change, 1 to destroy.The 1 to add and 1 to destroy are the reload handler being replaced, which is how the plan tells you nginx will reload. And conn3x drift, which exited 2:
Drift found in 1 resource.
Hosts ~ conn3x_file.web__write_the_site_config["web1"] changed
Run conn3x plan to see what Conn3x would change to put them back.So the preview is close. Where the two part ways is everything around it: Conn3x compares against what it last applied, saves the plan you approved, shows a removed step as a removal, and puts a new server from any OpenTofu provider in the same plan when you want one.
Where Ansible is the better choice
Section titled “Where Ansible is the better choice”There are plenty of jobs where Ansible is the right tool, and we’d rather say so:
- Ad-hoc work.
ansible all -m ping, or a one-off command across a group during an incident, is quick and useful. Conn3x has no ad-hoc mode: it only runs what a circuit describes. - Network gear. Ansible’s network collections cover switches, routers and firewalls from many vendors, over their CLIs and APIs. Conn3x has three experimental network circuits, and talking to devices over their own CLI is only planned.
- Windows at scale. Ansible’s Windows support has years of production use behind it. Windows in Conn3x is a preview, and every
windows_*module is experimental. It can build a forest, join servers, add a domain controller and keep the OUs, groups and users in the domain, but a whole lab from one config hasn’t been tested yet. - The ecosystem. Galaxy has roles and collections for almost anything you’d want to configure. Conn3x has 34 Linux modules and 127 circuits, which cover a lot of ground and are still a much smaller set.
- No state to look after. If you don’t want a state file anywhere, Ansible’s stateless model is a real advantage, not a gap.
- A UI, credentials and access control. AWX and Red Hat Ansible Automation Platform give a team job templates, a credential store, RBAC and a web UI. Conn3x has none of that; you run it from CI.
- Team familiarity. Ansible is widely known, widely taught and well documented. A team that’s fluent in it gives up real speed by switching.
- Maturity. Conn3x is at v0.10.0. Stable parts are tested on every change, but before 1.0 a release can still change how you write something. Maturity labels says what each label promises.
You don’t have to choose for every server at once. Converting one playbook and running Conn3x for that group, while Ansible keeps the rest, is a fair way to find out.
What the converter can’t convert
Section titled “What the converter can’t convert”The converter is stable, and it marks what it can’t prove instead of guessing. These come across as TODO(convert) comments for you to finish:
- Roles,
include_tasksandimport_tasks. Add each role’s tasks as steps. - Jinja templates and filters. Rewrite them in
templatefilesyntax for a template step. - Magic variables such as
inventory_hostname, and anything that isn’t one of the play’svars. - Control flow:
when,register,with_*,until,ignore_errors, and anyloopother than a plain list. - Anything under
no_log, with its values left out of the comment. - Commands that ran on every play, until you give them a
creates,removesorunlessguard, ortriggers. - Modules it doesn’t know, and arguments it doesn’t know on modules it does.
Migrate from Ansible lists every module it converts and every case it leaves.
What Conn3x can’t do yet
Section titled “What Conn3x can’t do yet”- Windows Server is in preview, with experimental modules. A domain can be built, joined, given more controllers and filled with OUs, groups and users. Child domains aren’t tested, and a group can’t list members from another domain.
- Network devices are limited to three experimental circuits, with no device CLI connection.
- macOS hosts are planned, not shipped.
- There’s no ad-hoc command, no web UI and no built-in credential store.
- Adopting a running server works for eight Linux modules:
file,template,directory,link,package,service,userandgroup. The rest, and Windows hosts, can’t import yet.
The roadmap has the order we’re building things in.
Next step
Section titled “Next step”Migrate from Ansible: point the converter at one of your own playbooks and see how much carries over. If you’d like to see a plan and a drift check first, Try Conn3x locally does that on a throwaway container in about ten minutes.