ARA
Overview
ARA Records Ansible (ARA) is an open source, self-hosted reporting tool for Ansible. A lightweight callback plugin captures every play, task, host and result as your playbooks run and sends them to a small API server. The server stores the data and shows it in a web interface backed by a REST API.
Point Ansible at the recorder and run your playbooks as you already do. ARA builds a durable, queryable record of what ran, on which hosts, in what order and what changed. The SecureAnyCloud image serves ARA 1.8.0 behind an nginx reverse proxy on a hardened Ubuntu 24.04 LTS base, patched at build time.
What you get:
- ARA Records Ansible 1.8.0 served by gunicorn behind nginx, managed by systemd. The Django API and UI application runs under gunicorn on the loopback interface (127.0.0.1:8000), and nginx serves it on port 80.
- The ARA reporting web UI and the REST API on
:80, both requiring authentication - Secure by default: read and write access are gated, with no open endpoint. Unlike a manual install, the image exposes no unauthenticated read or write path.
- No default administrator and no shared password in the image. The first boot of every instance generates a unique administrator credential.
- A fresh Django application secret key generated on the first boot of every instance
- Ansible (ansible-core) and the ara callback preinstalled, so the appliance records runs out of the box
- The application server bound to
127.0.0.1:8000only, never exposed directly to the network nginx.serviceandara-web.serviceas enabled systemd units- 24/7 SecureAnyCloud support
Prerequisites
You need an active Azure subscription, an SSH key pair, and a VNet plus subnet in the target region. Standard_B2s (2 vCPU / 4 GiB RAM) is a sensible starting point. Size up as your recorded history grows.
In the NSG, allow inbound 22/tcp from your management network and 80/tcp for the reporting UI and API. ARA serves plain HTTP on port 80, so for production, terminate TLS in front of it with your own domain. The application server is never exposed directly. It listens on 127.0.0.1:8000 only, so port 8000 stays off the network.
Step 1 - Deploy from the Azure Marketplace
Sign in to the Azure Portal, choose Create a resource, and search the Marketplace for ARA Records Ansible by SecureAnyCloud. Choose Create.
On the Basics tab, choose your subscription, resource group, region and size. Under Administrator account, choose SSH public key and paste your key. Under Inbound port rules, allow SSH (22) and HTTP (80). Then choose Review + create, and then Create.
Step 2 - Deploy from the Azure CLI
# Accept the Marketplace terms once per subscription (required before the first CLI deploy)
az vm image terms accept --urn sac1647283583153:ara-ubuntu-24-04:default:latest
az vm create \
--resource-group <your-rg> \
--name ara \
--image sac1647283583153:ara-ubuntu-24-04:default:latest \
--size Standard_B2s \
--admin-username azureuser \
--generate-ssh-keys \
--public-ip-sku StandardThen open port 80 for the reporting UI and API:
az vm open-port --resource-group <your-rg> --name ara --port 80Step 3 - Confirm the services are running
SSH in as azureuser and confirm that nginx and the ARA application are active. The application server listens only on 127.0.0.1:8000, while nginx serves the UI and API on port 80.
systemctl is-active nginx ara-webCheck the installed version, and confirm that gunicorn is bound to loopback only:
/opt/ara/venv/bin/python -c "from ara.setup import ara_version as v; print('ARA', v)"Step 4 - Confirm secure by default and retrieve your admin credential
The image is secure by default. The liveness endpoint is open and answers without authentication. The reporting UI and the REST API return 401 until you present the per-instance administrator credential. Confirm this:
curl -s -o /dev/null -w 'GET /healthcheck/ -> HTTP %{http_code}\n' http://127.0.0.1/healthcheck/curl -s -o /dev/null -w 'GET / (no auth) -> HTTP %{http_code}\n' http://127.0.0.1/A 401 on / confirms that there is no open read or write path. The first boot generated your unique administrator credential and wrote it to a root-only file. Read it over SSH:
sudo cat /root/ara-credentials.txtStep 5 - Sign in to the reporting UI
Browse to http://<vm-public-ip>/. Your browser prompts for a username and password. Enter the ara.username and ara.password from the credentials file.
The reporting UI opens on the Playbooks page. It lists every recorded Ansible run with its status, duration, controller, and host, task and result counts.
Step 6 - Record a playbook run
The appliance is preconfigured to record into the local ARA server. The file /etc/ara/ansible.cfg enables the ara callback and points it at http://127.0.0.1. View the file:
cat /etc/ara/ansible.cfgA helper on the appliance runs a small demo playbook through the ara callback. It then reads the record back through the authenticated API to prove the end-to-end round trip. Run the helper:
sudo /usr/local/sbin/ara-record-check.shStep 7 - Explore a recorded run
On the Playbooks page, choose any run to open its detail. The detail shows the execution metadata, the Ansible, ara and Python versions, a summary of hosts, tasks, results, plays and files, and the full task results below.
The Tasks page lists every recorded task across your runs, with its action, name, path and status. You can see at a glance which task failed on which host.
The Hosts page summarizes the hosts that appeared in your runs and the results recorded against each.
Step 8 - Point your own Ansible control node at ARA
To record runs from a control node elsewhere, install ARA's client in that environment and enable the callback. Then set the API client to HTTP and point it at this appliance with your administrator credential:
pip install ara export ANSIBLE_CALLBACK_PLUGINS="$(python3 -m ara.setup.callback_plugins)" export ANSIBLE_CALLBACKS_ENABLED=ara_default export ARA_API_CLIENT=http export ARA_API_SERVER=http://<vm-public-ip> export ARA_API_USERNAME=ara export ARA_API_PASSWORD=<the ara.password from /root/ara-credentials.txt> ansible-playbook your-playbook.ymlARA records each later ansible-playbook run, and the run appears immediately in the reporting UI. To add more users, or to scope read and write access per user, use the Django admin at /admin/ or run sudo /usr/local/sbin/ara-manage createsuperuser.
Security notes
- ARA serves plain HTTP on port 80. For production, put it behind your own TLS-terminating reverse proxy or an Azure Application Gateway with a certificate for your domain, and restrict port 80 in the NSG to trusted networks. A common approach is
certbotwith your-domain on a front-end proxy. - The reporting UI and the REST API both require authentication, so there is no unauthenticated read or write path. Only the
/healthcheck/liveness endpoint, which reports up or down status only, is open. - The application server listens on
127.0.0.1:8000only and is never exposed directly. Only nginx on port 80 faces the network. - The image ships with no administrator and no shared password, and no application secret key is baked into it. The first boot of every instance generates a fresh secret key and a unique administrator credential, and writes them to a root-only file. There are no default credentials to rotate.
- Keep the VM patched. The image is patched at build time and ships with unattended security upgrades enabled.
Support
SecureAnyCloud images come with 24/7 support. If you have questions about this ARA Records Ansible image or need help with your deployment, contact us through the SecureAnyCloud website.
Deploy on Azure
Launch ARA Records Ansible on Ubuntu 24.04 LTS on Azure with 24/7 support from SecureAnyCloud.
View this image
Need Help?
Our support team is available 24/7.
sales@secureanycloud.com
Overview
This guide covers deploying and configuring ARA Records Ansible on AWS, using the SecureAnyCloud AWS Marketplace AMI.
ARA is the popular open source reporting tool for Ansible. You add a callback plugin to your Ansible configuration and run your playbooks as you do today. ARA records every play, task, host and result into a database and shows them in a web interface. Instead of scrolling back through terminal output that nobody kept, you get a durable, searchable record of what ran, where, how long it took, and which task failed on which host.
The SecureAnyCloud image ships ARA unmodified from the upstream release. ARA is licensed under the GNU General Public License v3. Ansible is a registered trademark of Red Hat, Inc. SecureAnyCloud produces this image, and it is not affiliated with, endorsed by, or sponsored by the ARA project or Red Hat.
What you get:
- ARA Records Ansible 1.8.0 in a dedicated Python virtual environment at
/opt/ara/venv ansible-coreand the ARA callback plugin preinstalled and wired up in/etc/ara/ansible.cfg, so the appliance records runs out of the box and any remote control node can be pointed at it- The Django application under gunicorn as an unprivileged service account on loopback
127.0.0.1:8000, behind an nginx reverse proxy on port 80 - A self-contained database at
/opt/ara/server/ansible.sqlite, the upstream default for a single-node deployment - A dedicated 20 GiB gp3 data volume mounted at
/opt/araby filesystem UUID and captured into the AMI. The application, its Python virtual environment and its recording database live on this volume, which you can resize independently of the operating system disk. - Authentication required for both reading and writing the whole interface and the whole API
- A first-boot service that generates a fresh application secret key and a unique administrator password per instance
- 24/7 SecureAnyCloud support
Why the authentication default matters
This is the most important thing to understand about the image. A stock ARA server defaults to unauthenticated read and unauthenticated write (READ_LOGIN_REQUIRED and WRITE_LOGIN_REQUIRED both default to false). On a public network, anyone who can reach port 80 can then read every recorded playbook, every host name and every task result, and can also write records into your history.
This image turns both settings on in /opt/ara/server/settings.yaml, so HTTP authentication gates the reporting interface and the REST API from the first request. The only endpoint deliberately left open is /healthcheck/. It reports up or down and exposes no data, so you can use it as a load balancer target group probe.
Connecting to your instance
Connect over SSH on port 22 with the key pair you chose at launch. The login user depends on the operating system variant you launched:
OS variant SSH login user Example
Ubuntu 24.04 LTS ubuntu ssh -i your-key.pem ubuntu@<public-ip>
Replace <public-ip> with the public IPv4 address shown for your instance in the EC2 console.
The security group you attach needs port 22 for SSH and port 80 for the reporting interface. Also open port 443 if you intend to terminate TLS on the instance, as described at the end of this guide. Port 8000 is deliberately not exposed. gunicorn binds the loopback interface only, so nothing outside the instance can reach it.
What happens on first boot
Every instance launched from this image runs a one-shot service, ara-firstboot.service, before the application starts. It:
- Generates a fresh Django
SECRET_KEYunique to that instance and writes it to/opt/ara/ara.env.
- Applies the database migrations against the empty database shipped in the image, creating a fresh schema with zero user accounts.
- Creates a single administrator,
ara, with a random password unique to that instance.
- Resolves the instance's public address from the EC2 instance metadata service and records it in the credentials file and the login banner.
- Writes its sentinel, at which point systemd starts
ara-web.serviceandnginx.service.
The application services wait for that sentinel, so the appliance never serves traffic without a generated secret key or without an administrator. Confirm that first boot completed:
ls -l /var/lib/sac/data/ara-firstboot.done systemctl is-active ara-web.service nginx.service-rw-r--r-- 1 root root 0 Jul 25 23:37 /var/lib/sac/data/ara-firstboot.done active activeRetrieving your administrator credential
The credential is written to a root-only file, /root/ara-credentials.txt. Read the non-secret fields to confirm it was generated:
sudo grep -E '^ara\.(url|username)=' /root/ara-credentials.txt
sudo grep -c '^ara.password=' /root/ara-credentials.txtara.url=http://35.172.114.84/ ara.username=ara 1To read the password itself, print just that one field:
Purpose Command
Show the administrator password sudo grep '^ara.password=' /root/ara-credentials.txt \| cut -d= -f2-
Show the whole credentials file sudo cat /root/ara-credentials.txt
The username is ara. Treat this password like any production administrator credential. The image ships with zero accounts and each instance generates its own password, so no two instances launched from this AMI share a credential.
Checking the service is healthy
nginx serves an unauthenticated health endpoint. It returns 200 as soon as the stack is serving, so you can use it as a health check for an Application Load Balancer target group:
curl -s -o /dev/null -w 'health: HTTP %{http_code}\n' http://127.0.0.1/healthcheck/health: HTTP 200Confirm the two listeners. nginx is on port 80 on all interfaces, and gunicorn is on port 8000 on loopback only:
sudo ss -tlnp | grep -E ':(80|8000)\b'LISTEN 0 2048 127.0.0.1:8000 0.0.0.0:* users:(("gunicorn",pid=3559,fd=3),("gunicorn",pid=3557,fd=3),("gunicorn",pid=3540,fd=3)) LISTEN 0 511 0.0.0.0:80 0.0.0.0:* users:(("nginx",pid=3549,fd=5),("nginx",pid=3547,fd=5),("nginx",pid=3545,fd=5)) LISTEN 0 511 [::]:80 [::]:* users:(("nginx",pid=3549,fd=6),("nginx",pid=3547,fd=6),("nginx",pid=3545,fd=6))Proving authentication is enforced
Both the reporting interface and the REST API must refuse an unauthenticated request. This block asserts that, and fails if either endpoint is open:
UI=$(curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1/)
API=$(curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1/api/v1/playbooks)
echo "unauthenticated UI: HTTP $UI"
echo "unauthenticated API: HTTP $API" if [ "$UI" = "401" ] && [ "$API" = "401" ]; then
echo "both refused without credentials - correct" else
echo "UNEXPECTED: an endpoint answered without authentication"; exit 1 fiunauthenticated UI: HTTP 401 unauthenticated API: HTTP 401 both refused without credentials - correctNow confirm that the server accepts the generated credential. This block reads the password into a shell variable, so it is never printed:
PW=$(sudo grep '^ara.password=' /root/ara-credentials.txt | cut -d= -f2-)
curl -s -o /dev/null -w 'authenticated UI: HTTP %{http_code}\n' -u "ara:$PW" http://127.0.0.1/authenticated UI: HTTP 200Signing in to the reporting interface
Open http://<public-ip>/ in a browser. Because both read and write require authentication, the browser prompts for HTTP credentials before anything renders. Enter the username ara and the password from the credentials file.
The landing page is the playbook report list. It has one row per recorded run, with its status, start time, duration, the Ansible version and controller that ran it, and counts of hosts, tasks and results. Every column is a link that filters the list.
Choose a run to open its report. The failed run below shows immediately which task failed, on which host, with the full result. The Summary panel gives the play, task, result, host and file counts. Versions records the Ansible, ARA and Python versions that produced the run.
The Tasks view searches and filters task results across every recorded run at once. You can filter by action, task name, path, status, or whether the result carried a deprecation, exception or warning.
Recording your first playbook
The appliance ships ansible-core, the ARA callback and a small demo playbook, so you can prove the whole pipeline end to end without configuring anything. A helper on the image runs the playbook and checks the result:
sudo /usr/local/sbin/ara-record-check.shOK: authenticated record round-trip verified - recorded playbook id=11 (before=5 after=6), unauth=401, read-back=200The helper confirms that an unauthenticated read is refused. It then runs a real playbook through the ARA callback with the per-instance credential, queries the API to confirm that the run was recorded, and reads the run back by id.
The configuration that makes local runs record themselves lives in /etc/ara/ansible.cfg:
sudo grep -vE '^\s*#|^\s*$' /etc/ara/ansible.cfg[defaults] callback_plugins = /opt/ara/venv/lib/python3.12/site-packages/ara/plugins/callback action_plugins = /opt/ara/venv/lib/python3.12/site-packages/ara/plugins/action lookup_plugins = /opt/ara/venv/lib/python3.12/site-packages/ara/plugins/lookup callbacks_enabled = ara_default host_key_checking = False interpreter_python = auto_silent [ara] api_client = http api_server = http://127.0.0.1Querying the REST API
Everything in the interface is also available over the REST API. You can browse the API in a web browser, and it returns JSON to a client that asks for it. List the recorded runs:
PW=$(sudo grep '^ara.password=' /root/ara-credentials.txt | cut -d= -f2-)
curl -s -u "ara:$PW" -H 'Accept: application/json' 'http://127.0.0.1/api/v1/playbooks?limit=5' \
| jq -r '"recorded playbooks: \(.count)", (.results[] | " #\(.id) \(.status) \(.duration) \(.path)")'recorded playbooks: 6 #11 completed 00:00:09.979665 /opt/ara/demo/record-demo.yml #10 completed 00:00:17.995068 /root/playbooks/patch-baseline.yml #9 completed 00:00:19.565372 /root/playbooks/deploy-web-tier.yml #8 failed 00:00:13.630470 /root/playbooks/nightly-backup.yml #7 completed 00:00:20.014845 /root/playbooks/deploy-web-tier.ymlIn a signed-in browser, /api/v1/playbooks gives the same data through the browsable API. This is a fast way to explore the available endpoints and filters:
Useful endpoints: /api/v1/playbooks, /api/v1/plays, /api/v1/tasks, /api/v1/hosts, /api/v1/results and /api/v1/records. Each supports filtering by the fields shown in the interface, plus order and limit.
Pointing your own Ansible at the recorder
Recording runs from the appliance proves that the stack works. In a real deployment, your existing control nodes and CI runners report into it. On each control node, install the ARA client and export four environment variables. Nothing else changes about how you run playbooks.
pip install "ara[server]==1.8.0" export ANSIBLE_CALLBACK_PLUGINS="$(python3 -m ara.setup.callback_plugins)" export ARA_API_CLIENT=http export ARA_API_SERVER=http://<public-ip> export ARA_API_USERNAME=ara export ARA_API_PASSWORD=<set-at-first-boot> ansible-playbook site.ymlA control node needs only the client, so pip install ara is enough if you do not want the server extras there. Set the same four variables in your CI job environment, and every pipeline run reports itself into the appliance automatically.
To make the setting permanent for a control node, put the equivalent in that machine's ansible.cfg instead:
[defaults] callback_plugins = /path/to/ara/plugins/callback callbacks_enabled = ara_default [ara] api_client = http api_server = http://<public-ip> api_username = ara api_password = your-administrator-passwordBecause writing requires authentication, a control node without the credential cannot add records to your history.
Adding more users
Every account is a Django user. Give each person or CI system its own account instead of sharing the bootstrap administrator. Create an account interactively:
sudo /usr/local/sbin/ara-manage createsuperuser --username alice --email alice@example.com
# enter <new-password> when prompted, then confirm itThe same wrapper runs any ARA server management command with the instance environment already loaded. For example, once you have signed in, run sudo /usr/local/sbin/ara-manage changepassword ara to rotate the bootstrap credential.
Where things live
Path Contents
/opt/ara/venv Python virtual environment with ARA, ansible-core and gunicorn
/opt/ara/server/ansible.sqlite The recording database
/opt/ara/server/settings.yaml Server settings, including the two authentication requirements
/opt/ara/ara.env Per-instance environment, including the generated secret key
/etc/ara/ansible.cfg Ansible configuration that records the appliance's own runs
/etc/nginx/sites-available/sac-ara The reverse proxy site
/root/ara-credentials.txt The per-instance administrator credential, mode 0600
Everything under /opt/ara is on the dedicated data volume:
findmnt -no SOURCE,TARGET,FSTYPE,SIZE /opt/ara df -h /opt/ara | tail -1/dev/nvme1n1 /opt/ara ext4 19.5G /dev/nvme1n1 20G 164M 19G 1% /opt/araConfirm the shipped versions:
sudo /opt/ara/venv/bin/python -c 'from ara.setup import ara_version; print("ara", ara_version)'
sudo /opt/ara/venv/bin/ansible --version | head -1ara 1.8.0 ansible [core 2.21.2]Backing up the recorded history
The database is a single file, so a consistent backup takes one command. Use the database's own online backup so that a running server cannot produce a torn copy:
BK=$(mktemp -d) sudo sqlite3 /opt/ara/server/ansible.sqlite ".backup '$BK/ara-backup.sqlite'" ls -lh "$BK/ara-backup.sqlite"
sudo rm -rf "$BK"Copy the resulting file to S3 on a schedule for off-instance retention. Because the whole data tier is on its own EBS volume, an EBS snapshot of that volume is another option, and it captures the application and the database together.
Resizing the data volume
The recording database grows with every playbook run, so plan to grow the data volume rather than the operating system disk. Modify the volume in the EC2 console or with aws ec2 modify-volume, then grow the filesystem in place with no downtime:
lsblk
sudo resize2fs /dev/nvme1n1 df -h /opt/araThe data volume has no partition table, so resize2fs alone is enough. The device name may differ on your instance, so take it from lsblk.
Enabling HTTPS
The image serves plain HTTP on port 80 so that it works as soon as it launches. For anything beyond evaluation, terminate TLS. Two routes are supported:
Application Load Balancer. Put the instance in a target group on port 80 and attach an ACM certificate to an HTTPS listener, so that the load balancer terminates TLS. Use the /healthcheck/ endpoint as the target group health check. This route needs no change on the instance.
Let's Encrypt on the instance. Point a DNS A record at the instance, open port 443 in the security group, then run:
sudo apt-get install -y certbot python3-certbot-nginx
sudo certbot --nginx -d <your-domain> --agree-tos -m you@example.com --redirectcertbot rewrites the nginx site in place and installs a renewal timer. Once TLS is in place, update ARA_API_SERVER on your control nodes to the https:// URL, so that the credential is no longer sent in the clear.
Troubleshooting
Symptom Check
Browser prompts repeatedly for credentials The password is case-sensitive and 24 characters. Re-read it with sudo grep '^ara.password=' /root/ara-credentials.txt \| cut -d= -f2-.
502 Bad Gateway from nginx gunicorn is not running. systemctl status ara-web.service and journalctl -u ara-web.service -n 50.
Nothing recorded from a remote control node Confirm ARA_API_CLIENT=http is set (the default is offline, which writes locally instead), that ARA_API_SERVER is reachable on port 80 from that node, and that the credential is correct. Writing is authenticated, so a wrong password silently produces no records.
First boot appears not to have run systemctl status ara-firstboot.service and journalctl -u ara-firstboot.service. The sentinel is /var/lib/sac/data/ara-firstboot.done.
/healthcheck/ is 200 but the interface is not That endpoint is intentionally unauthenticated and only reports liveness. A 401 on / is the correct, secure response, not a fault.
Support
SecureAnyCloud provides 24/7 technical support for this product with a guaranteed 24-hour response SLA. Our engineers help with deployment, credential rotation and user management, connecting your control nodes and CI runners, callback troubleshooting, DNS and TLS, data volume resizing, backups, retention and upgrades.
Email sales@secureanycloud.com. The same address handles billing inquiries.
Deploy on AWS
Launch ARA Records Ansible with 24/7 support from SecureAnyCloud.
View on Marketplace
Need Help?
Our support team is available 24/7.
sales@secureanycloud.com