<!-- category: modern-perl -->
Rex: Infrastructure Automation in Perl
You manage servers. Multiple servers. You SSH into each one, run the same commands, check the same things, fix the same problems. Over and over.What if you could write that in Perl?
task 'check_disk', group => 'webservers', sub { say run 'df -h'; };
That runs$ rex check_disk
df -h on every server in the webservers group. Over SSH.
In parallel. With one command.
This is Rex. Remote Execution. Infrastructure automation written in Perl, configured in Perl, extended in Perl. Think Ansible, but your playbooks are real code, not YAML pretending to be code.
Part 1: WHAT REX IS
Rex is a CPAN module. Install it:You write a Rexfile (like a Makefile, but Perl). Define your servers. Define your tasks. Run them from the command line.cpanm Rex
That's the whole workflow. No agents on remote servers. No daemon process. Just SSH. Rex connects, runs your commands, reports back.rex <task_name>
┌─────────┐ │ Your │ │ machine │ rex check_disk │ (Rexfile)│ └────┬─────┘ │ SSH ┌────┼────────────────┐ │ │ │ v v v ┌────┐ ┌────┐ ┌────┐ │web1│ │web2│ ... │webN│ └────┘ └────┘ └────┘ No agents. No daemons. Just SSH.
Part 2: THE REXFILE
A Rexfile is just Perl with Rex functions imported. Here's a minimal one:Save it asuse Rex -base; user 'deploy'; private_key '~/.ssh/id_ed25519'; group webservers => ( '10.0.1.10', '10.0.1.11', '10.0.1.12', ); desc 'Check disk usage on all web servers'; task 'check_disk', group => 'webservers', sub { my $out = run 'df -h'; say $out; }; 1;
Rexfile in your project directory. Run it:
Rex SSHs into each server in the webservers group and runs$ rex check_disk
df -h.
Output comes back to your terminal. Done.
Part 3: SERVER GROUPS
Group your servers by function:Each task targets a group:group webservers => ('10.0.1.10', '10.0.1.11'); group databases => ('10.0.2.10', '10.0.2.11'); group monitoring => ('10.0.3.10'); group all_prod => ('10.0.1.10', '10.0.1.11', '10.0.2.10', '10.0.2.11', '10.0.3.10');
Or override on the command line:task 'check_web', group => 'webservers', sub { ... }; task 'check_db', group => 'databases', sub { ... }; task 'check_all', group => 'all_prod', sub { ... };
Run a task on a specific host, regardless of its group.$ rex -H 10.0.1.10 check_disk
List all available tasks:
The$ rex -T Tasks: check_disk Check disk usage on all web servers check_nginx Check if nginx is running update Apply security updates
desc line before each task becomes its description in the task
list. Document your tasks. Future you will thank present you.
Part 4: RUNNING COMMANDS
Therun function is your workhorse. It executes a command on the
remote server and returns the output:
Check exit status:my $output = run 'uptime'; say $output;
The callbacks give you clean error handling. No checkingrun 'systemctl restart nginx', on_success => sub { say "nginx restarted OK"; }, on_error => sub { say "FAILED to restart nginx"; die "Critical failure"; };
$?
manually. No parsing exit codes.
Run with sudo:
Or set it globally:my $output = run 'sudo apt-get update -q';
Now everysudo TRUE;
run command uses sudo automatically.
Part 5: PACKAGE MANAGEMENT
Rex has built-in functions for managing packages. No need to shell out to apt or yum:Rex detects the OS and uses the right package manager. Debian getsuse Rex::Commands::Pkg; task 'install_essentials', group => 'webservers', sub { pkg 'nginx', ensure => 'present'; pkg 'curl', ensure => 'present'; pkg 'htop', ensure => 'present'; };
apt. Red Hat gets yum or dnf. You write it once.
Update all packages:
task 'update_all', group => 'all_prod', sub { update_package_db; # apt update / yum check-update run 'sudo apt-get upgrade -y'; # or use Rex's update functions };
Part 6: SERVICE MANAGEMENT
Start, stop, restart, check status:Check if a service is running:use Rex::Commands::Service; task 'restart_nginx', group => 'webservers', sub { service nginx => 'restart'; say "nginx restarted on " . connection->server; };
Nothing magical here. It's just Perl.task 'check_services', group => 'webservers', sub { my $hostname = run 'hostname'; say "> $hostname"; my $status = run 'systemctl is-active nginx'; if ($status eq 'active') { say " nginx: OK"; } else { say " nginx: $status (PROBLEM)"; } };
if statements, string
comparison, say. All the language features you already know, applied
to remote servers.
.--. |o_o | "It's just Perl. That's the whole point." |:_/ | // \ \ (| | ) /'\_ _/`\ \___)=(___/
Part 7: FILE OPERATIONS
Push files to remote servers:Upload. Set permissions. Reload. Three lines of intention, zero lines of SSH and SCP plumbing.use Rex::Commands::Upload; use Rex::Commands::File; task 'deploy_config', group => 'webservers', sub { upload 'files/nginx.conf', '/etc/nginx/nginx.conf'; file '/etc/nginx/nginx.conf', owner => 'root', group => 'root', mode => '644'; service nginx => 'reload'; };
Template files with embedded Perl:
Your template uses Rex's template syntax (or plain Perl string interpolation). Config management without learning a template DSL.file '/etc/nginx/conf.d/myapp.conf', content => template('templates/myapp.conf.tpl', server_name => 'app.example.com', upstream => '127.0.0.1:8080', );
Part 8: A REAL REXFILE
Here's a practical Rexfile for managing a small web fleet:That's your entire fleet management in one file. Run any task:use Rex -base; use Rex::Commands::Pkg; use Rex::Commands::Service; use Rex::Commands::Upload; use Rex::Commands::File; user 'deploy'; private_key '~/.ssh/id_ed25519'; group webservers => ('10.0.1.10', '10.0.1.11', '10.0.1.12'); desc 'Show hostname and uptime'; task 'hello', group => 'webservers', sub { say run 'hostname'; say run 'uptime'; }; desc 'Check disk usage, warn if above 80%'; task 'check_disk', group => 'webservers', sub { my $host = run 'hostname'; my @lines = split /\n/, run 'df -h'; for my $line (@lines) { if ($line =~ m~(\d+)%~ && $1 > 80) { say "WARNING [$host]: $line"; } } }; desc 'Apply security updates'; task 'security_updates', group => 'webservers', sub { my $host = run 'hostname'; say "Updating $host..."; run 'sudo apt-get update -q', on_error => sub { die "apt update failed on $host" }; run 'sudo apt-get upgrade -y -q', on_error => sub { die "upgrade failed on $host" }; say "$host updated OK"; }; desc 'Deploy nginx config and reload'; task 'deploy_nginx', group => 'webservers', sub { upload 'files/nginx.conf', '/etc/nginx/nginx.conf'; file '/etc/nginx/nginx.conf', owner => 'root', group => 'root', mode => '644'; run 'sudo nginx -t', on_error => sub { die "nginx config test FAILED" }; service nginx => 'reload'; say "nginx deployed and reloaded on " . run('hostname'); }; 1;
$ rex hello $ rex check_disk $ rex security_updates $ rex deploy_nginx
Part 9: WHY NOT ANSIBLE?
Fair question. Ansible is the 800-pound gorilla of config management. Why would you pick Rex?Your Rexfile is real code. Not YAML with Jinja2 templates and weird indentation rules. Perl. With variables, loops, conditionals, error handling, CPAN modules, and everything else you already know.
No new language to learn. If you know Perl, you know Rex. Ansible requires learning YAML structure, Jinja2 templating, module arguments, playbook organization, roles, galaxy. Rex requires learning about ten functions.
Lightweight. No control server. No inventory database. No tower UI. Just a Rexfile and SSH keys.
Perfect for small fleets. If you have 3 to 50 servers, Rex is exactly the right size tool. Ansible works too, but it's like driving a semi truck to the grocery store.
Ansible: YAML → Jinja2 → Python → SSH → Remote Rex: Perl → SSH → Remote Fewer layers. Fewer surprises.
Part 10: BEYOND BASICS
Rex can do more than run commands. It has modules for:Gather system info:CATEGORY WHAT IT DOES ---------------- ------------------------------------------ Rex::Commands::Fs File system operations (mkdir, symlink, etc.) Rex::Commands::Cron Manage cron jobs Rex::Commands::User Create/manage users and groups Rex::Commands::Iptables Firewall rules Rex::Commands::Gather System info (OS, memory, CPU) Rex::Commands::Cloud AWS, OpenStack integration
Runuse Rex::Commands::Gather; task 'inventory', group => 'all_prod', sub { my $os = operating_system(); my $ver = operating_system_version(); my $mem = memory(); my $host = run 'hostname'; say "$host: $os $ver, ${\ int($mem->{total}/1024) }MB RAM"; };
rex inventory and get a fleet-wide hardware report. No agents,
no monitoring tool, no dashboard. Just Perl asking questions over SSH.
Part 11: TESTING LOCALLY
Rex can run tasks on localhost too:Thetask 'local_check', sub { LOCAL { say run 'df -h /'; }; };
LOCAL block runs commands on your machine, not remote. Great for
testing task logic before deploying to the fleet.
Or use the -H localhost flag:
$ rex -H localhost check_disk
Part 12: GETTING STARTED
Install Rex:Create a Rexfile in your project directory. Start with one server, one task:cpanm Rex
Run it:use Rex -base; user 'youruser'; private_key '~/.ssh/id_ed25519'; group myserver => ('10.0.0.50'); desc 'Hello from Rex'; task 'hello', group => 'myserver', sub { say run 'hostname'; say run 'uptime'; }; 1;
If that works, you have infrastructure automation. Add more servers, add more tasks, and grow from there. Your Rexfile is version controlled, peer reviewable, and testable. It's just Perl.$ rex hello
The Rex documentation lives at https://www.rexify.org/. The API reference covers every built-in command. The cookbook has patterns for common tasks.
But honestly, the best way to learn Rex is to take that thing you SSH into three servers to do every Tuesday morning and put it in a Rexfile. You'll never go back.
perl.ggRex ┌───────────┐ │ │ │ Rexfile │ "It's just Perl." │ │ └─────┬──────┘ │ ┌───────┼───────┐ │ │ │ v v v [web1] [web2] [web3] Infrastructure as code. Real code.