ARA Records Ansible 1.8.0 on AWS User Guide
Overview
This guide covers launching and using the ARA Records Ansible 1.8.0 on Ubuntu 24.04 AMI from cloudimg on Amazon EC2. It is one version of the cloudimg Infrastructure as Code and Config Management listing on AWS Marketplace; you pick it as the software version when you subscribe and launch.
ARA Records Ansible (ARA) records your Ansible playbook runs. Its callback plugin sends every playbook, play, task, host and result to an API server as the run happens, and ARA's web interface lets you browse, search and compare those runs afterwards: which playbook ran, from which controller and as which user, on which hosts, what changed and what failed, and how long each task took. You keep running ansible-playbook as you do today; ARA builds up the history.
A fresh upstream ARA server lets anyone who can reach it read every recorded run and write new ones, ships Django's admin site and serves plain HTTP. The cloudimg image changes that:
- HTTPS from the first boot. nginx serves ARA on port 443 with a certificate generated on your instance's first boot for its public address, public DNS name and private address. Port 80 redirects to HTTPS. The certificate's SHA-256 fingerprint is written to the credentials file so you can check it the first time you connect.
- A login for everything. Every page of the reporting interface and every read and write through the REST API needs an ARA login (HTTP basic authentication against ARA's own user table). Without one, ARA answers
401and shows nothing. - One administrator, unique to your instance. First boot creates a single user,
admin, with a random 24 character password, a fresh Django secret key and an empty database. The image ships no password, no password hash, no secret key and no data. The password is written to/root/ara-credentials.txt(root only). - Closed by default. Django's admin site and the health check app are not published. Users are managed with a root command on the instance (
ara-user, Step 10). Requests are rate limited per client address. - Ready to record. ansible-core 2.21.6 and ARA's callback are installed in the same environment, and
/etc/ara/ansible.cfgalready points the callback at this instance over HTTPS, verified against its own certificate. Ansible controllers elsewhere record to it with the same login (Step 8). - Hardened. The ARA application server (gunicorn) listens on 127.0.0.1 only, and only root, the
araservice account and nginx may connect to it. ARA runs as an unprivileged account that can write only to its database directory. ARA and nginx are blocked from the instance metadata service and from private addresses in your VPC, nginx drops requests for host names the instance does not serve, and the AMI requires IMDSv2. - Verified packages. ARA, ansible-core and every Python dependency are installed from a lock file that pins each package to a SHA-256 hash, as binary wheels only. The image is fully patched Ubuntu 24.04 LTS with unattended security upgrades on.
What is on the image: ARA Records Ansible 1.8.0 with ansible-core 2.21.6 in /opt/ara/venv (Python 3.12), SQLite database in /var/lib/ara/server, nginx 1.24 from the Ubuntu archive, Ubuntu 24.04 LTS, cloud-init and the AWS Systems Manager agent.
Default user: ubuntu · Architecture: x86_64 · Recommended instance type: m5.large
Prerequisites
- An AWS account subscribed to the listing, an EC2 key pair and a VPC subnet with a route to the internet (or a private route you use for management).
- A security group that allows TCP 22 (SSH), TCP 443 (ARA) and TCP 80 (redirects to HTTPS). Open 22 from your own networks only. Open 443 to the people who read the reports and to the Ansible controllers that record to ARA. Port 8000 is ARA's internal application server and must never be opened.
Step 1: Launch the instance
In the AWS Marketplace listing choose Continue to Subscribe, then Continue to Configuration, pick ARA Records Ansible 1.8.0 on Ubuntu 24.04 as the software version and your Region, then Launch through EC2. Choose m5.large, your key pair and a security group as above.
With the AWS CLI (replace the values in angle brackets):
aws ec2 run-instances --region <region> --image-id <ami-id-from-the-listing> \
--instance-type m5.large --key-name <your-key-pair> \
--security-group-ids <your-security-group> --subnet-id <your-subnet> \
--metadata-options HttpTokens=required,HttpEndpoint=enabled \
--tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=ara}]'
Step 2: Connect to your instance
| Version | SSH user |
|---|---|
| ARA Records Ansible 1.8.0 on Ubuntu 24.04 | ubuntu |
ssh -i <your-key.pem> ubuntu@<public-ip>
The login message shows the ARA address, the user name and where to find the password:
ARA Records Ansible 1.8.0 (cloudimg): https://<public-ip>/ user admin, password: sudo cat /root/ara-credentials.txt
Record a playbook here: ANSIBLE_CONFIG=/etc/ara/ansible.cfg (see the guide) Self-test: sudo ara-selftest
Guide: https://www.cloudimg.co.uk/guides/ara-records-ansible-1-8-0-on-ubuntu-24-04-aws/
Step 3: Check the services
First boot creates the database, the administrator, the secret key and the certificate, then ARA and nginx start. ara-firstboot runs once and then shows as inactive:
systemctl is-active nginx ara-web ara-firstboot
sudo test -f /var/lib/cloudimg/ara-firstboot.done && echo FIRSTBOOT_DONE
active
active
inactive
FIRSTBOOT_DONE
Only SSH and nginx listen on the network. ARA's application server (gunicorn) listens on 127.0.0.1:8000:
sudo ss -lntp | awk 'NR>1 {print $4, $6}' | sed -E 's/pid=[0-9]+,fd=[0-9]+/.../g' | sort
0.0.0.0:22 users:(("sshd",...),("systemd",...))
0.0.0.0:443 users:(("nginx",...),("nginx",...),("nginx",...))
0.0.0.0:80 users:(("nginx",...),("nginx",...),("nginx",...))
127.0.0.1:8000 users:(("gunicorn",...),("gunicorn",...),("gunicorn",...),("gunicorn",...))
127.0.0.53%lo:53 users:(("systemd-resolve",...))
127.0.0.54:53 users:(("systemd-resolve",...))
[::]:22 users:(("sshd",...),("systemd",...))
[::]:443 users:(("nginx",...),("nginx",...),("nginx",...))
[::]:80 users:(("nginx",...),("nginx",...),("nginx",...))
Step 4: Get your URL and password
The credentials file is readable by root only. This prints everything in it except the password line:
sudo grep -v '^ARA_PASSWORD=' /root/ara-credentials.txt
# ARA Records Ansible 1.8.0 (cloudimg) on this instance. Open ARA_URL in a browser and sign in as ARA_USERNAME / ARA_PASSWORD.
# Give the same login to Ansible (api_username / api_password in the [ara] section of ansible.cfg) to record playbooks.
# The password is unique to this instance. Change it: sudo ara-set-password (or --stdin to choose one). More users: sudo ara-user.
# The certificate is self-signed for this instance (/etc/ara/ara-instance.crt): compare its SHA-256 fingerprint with ARA_TLS_SHA256.
ARA_URL=https://<public-ip>/
ARA_HOST=<public-ip>
ARA_USERNAME=admin
ARA_TLS_SHA256=<ARA_TLS_SHA256>
ARA_ADDRESS_SOURCE=imdsv2-public-ipv4
GUIDE=https://www.cloudimg.co.uk/guides/ara-records-ansible-1-8-0-on-ubuntu-24-04-aws/
Print the password itself only when you are ready to sign in: sudo sed -n 's/^ARA_PASSWORD=//p' /root/ara-credentials.txt
ARA_URL uses the instance's public IPv4 address, read from instance metadata on first boot (an instance without one uses its private address). To serve ARA under your own DNS name, see Step 11.
Step 5: Check the certificate and sign in
The certificate is self signed, so your browser asks you to confirm it the first time. Before you accept it, compare its SHA-256 fingerprint (in the browser's certificate viewer) with ARA_TLS_SHA256 from the credentials file. From your workstation you can read it with OpenSSL:
echo | openssl s_client -connect <public-ip>:443 2>/dev/null | openssl x509 -noout -fingerprint -sha256
On the instance itself the same check reads the fingerprint nginx serves:
H=$(sudo sed -n 's/^ARA_HOST=//p' /root/ara-credentials.txt)
echo | openssl s_client -connect 127.0.0.1:443 -servername "$H" 2>/dev/null | openssl x509 -noout -fingerprint -sha256
sudo grep '^ARA_TLS_SHA256=' /root/ara-credentials.txt
The two fingerprints are the same:
sha256 Fingerprint=<ARA_TLS_SHA256>
ARA_TLS_SHA256=<ARA_TLS_SHA256>
Open ARA_URL in your browser. It asks for a user name and password: enter admin and the password from the credentials file. ARA opens on the Playbooks page, which lists every recorded run with its status, date, duration, name or path, Ansible version, controller, user, and its host, task and result counts. A new instance shows the run that sudo ara-selftest records (Step 6) once you have run it:

Step 6: Record a playbook run on the instance
The image ships a self test that proves the whole path end to end over HTTPS: without a login, and with a wrong password, every page and the API answer 401; Django's admin and the health check app are not served; then Ansible runs a small demo playbook, /opt/ara/demo/record-demo.yml, through ARA's callback with this instance's password, and the new run is read back through the API and the reporting interface. It never prints the password:
sudo ara-selftest
ara-selftest: 10 paths x (no credential, wrong password) -> 401, an anonymous write -> 401, /admin and /healthcheck -> 404; Ansible recorded playbook <n> (5 results) and it reads back through the API and the UI
ARA_SELFTEST_OK
To record your own playbooks on the instance, use the image's Ansible with ANSIBLE_CONFIG=/etc/ara/ansible.cfg, and give the callback the ARA login. This records the same demo playbook:
export ANSIBLE_CONFIG=/etc/ara/ansible.cfg ARA_API_USERNAME=admin
ARA_API_PASSWORD="$(sudo sed -n 's/^ARA_PASSWORD=//p' /root/ara-credentials.txt)"; export ARA_API_PASSWORD
/opt/ara/venv/bin/ansible-playbook /opt/ara/demo/record-demo.yml
...
PLAY RECAP *********************************************************************
localhost : ok=5 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Replace the demo with your own playbook and inventory, for example /opt/ara/venv/bin/ansible-playbook -i inventory.ini site.yml. If you prefer not to keep the password in your shell, copy /etc/ara/ansible.cfg to a private file (mode 0600) and add api_username = admin and api_password = ... to its [ara] section, then point ANSIBLE_CONFIG at that copy.
Two playbook variables label a run in ARA: ara_playbook_name gives it a readable name instead of its path, and ara_playbook_labels adds labels you can filter on:
- name: Configure web tier
hosts: web
vars:
ara_playbook_name: Deploy web and database tiers
ara_playbook_labels: [production, deploy]
tasks:
...
ARA's command line client reads the same records through the API:
export ARA_API_CLIENT=http ARA_API_SERVER=https://localhost ARA_API_CA=/etc/ara/ara-instance.crt ARA_API_USERNAME=admin
ARA_API_PASSWORD="$(sudo sed -n 's/^ARA_PASSWORD=//p' /root/ara-credentials.txt)"; export ARA_API_PASSWORD
/opt/ara/venv/bin/ara playbook list --limit 3 -c id -c status -c path -c duration
+----+-----------+-------------------------------+-----------------+
| id | status | path | duration |
+----+-----------+-------------------------------+-----------------+
| 11 | completed | /opt/ara/demo/record-demo.yml | 00:00:20.545874 |
| 10 | completed | /opt/ara/demo/record-demo.yml | 00:00:21.491273 |
| 9 | completed | /home/ubuntu/infra/patch.yml | 00:00:25.895798 |
+----+-----------+-------------------------------+-----------------+
Step 7: Explore a recorded run
Select a playbook to open its report: when and where it ran, the Ansible, ARA and Python versions, a summary of hosts, tasks, results, plays and files, each host with its ok, changed and failed counts, the playbook files, and every task result below:

Hosts lists each host with the result counts from its latest run, and links to its facts and history:

Tasks lists every task across your runs with its action, name, file and line, so you can find which task changed or failed and in which playbook:

Each page has Search and filter for narrowing by name, status, controller, user, label or date. API opens ARA's browsable REST API (/api/v1/), which uses the same login.
Step 8: Record from your own Ansible controllers
Any Ansible controller that can reach port 443 can record to this instance. On the controller, install the same ARA version and copy the instance's certificate so the callback can verify it:
python3 -m pip install --user "ara==1.8.0"
scp -i <your-key.pem> ubuntu@<public-ip>:/etc/ara/ara-instance.crt ~/ara-instance.crt
Then enable the callback and point it at the instance with the ARA login:
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=https://<public-ip>
export ARA_API_CA=~/ara-instance.crt
export ARA_API_USERNAME=admin
export ARA_API_PASSWORD='<the ARA_PASSWORD from /root/ara-credentials.txt>'
ansible-playbook -i inventory.ini site.yml
Each run appears on the Playbooks page as soon as it starts. For CI pipelines and shared controllers, give each controller its own ARA user (Step 10) instead of the administrator's password. The certificate names the instance's public and private addresses and its public DNS name; if you use your own DNS name (Step 11), copy the new certificate to your controllers again.
Step 9: Check the security posture from the command line
Port 80 redirects to HTTPS, the reporting interface and the API refuse requests without a valid login, Django's admin and the health check app are not served, and a request for a host name the instance does not serve is dropped without an answer. /healthz is answered by nginx itself for load balancer health checks and reveals nothing about ARA:
H=$(sudo sed -n 's/^ARA_HOST=//p' /root/ara-credentials.txt); C=(--cacert /etc/ara/ara-instance.crt --connect-to ::127.0.0.1:)
curl -s -o /dev/null -w 'http:// -> %{http_code} %{redirect_url}\n' "${C[@]}" "http://$H/"
curl -s -o /dev/null -w 'UI, no login -> %{http_code}\n' "${C[@]}" "https://$H/"
curl -s -o /dev/null -w 'API, no login -> %{http_code}\n' "${C[@]}" "https://$H/api/v1/playbooks"
curl -s -o /dev/null -w 'API, bad password -> %{http_code}\n' "${C[@]}" -K <(printf 'user = "admin:%s"\n' "$(openssl rand -hex 12)") "https://$H/api/v1/playbooks"
curl -s -o /dev/null -w 'API write, no login-> %{http_code}\n' "${C[@]}" -X POST -H 'Content-Type: application/json' -d '{}' "https://$H/api/v1/playbooks"
curl -s -o /dev/null -w 'Django admin -> %{http_code}\n' "${C[@]}" "https://$H/admin/"
curl -s -o /dev/null -w 'health check app -> %{http_code}\n' "${C[@]}" "https://$H/healthcheck/"
curl -s -o /dev/null -w 'nginx /healthz -> %{http_code}\n' "${C[@]}" "https://$H/healthz"
curl -sk -o /dev/null -w 'unknown host -> %{http_code}\n' --resolve evil.example:443:127.0.0.1 https://evil.example/ || true
http:// -> 301 https://<public-ip>/
UI, no login -> 401
API, no login -> 401
API, bad password -> 401
API write, no login-> 401
Django admin -> 404
health check app -> 404
nginx /healthz -> 200
unknown host -> 000
000 means nginx closed the connection without answering, which is how it treats any host name it does not serve.
Step 10: Manage ARA users
ARA's users are managed with ara-user as root. Names and passwords are passed on standard input or generated, never on a command line. To list the users:
sudo ara-user list
admin active last-login=never
To add a user (for example one per Ansible controller or CI pipeline), with a generated password that is shown once, or with a password of your own of 16 characters or more, and to remove one:
sudo ara-user set ci-pipeline
printf '%s\n' '<new-password>' | sudo ara-user set alice --stdin
sudo ara-user delete alice
Every ARA user can read every recorded run and record new ones; ARA has no per user permissions.
To change the administrator's password, either generate a new random one (written to /root/ara-credentials.txt) or set your own (not stored). It takes effect at once, so update ARA_API_PASSWORD or api_password on every controller that records as admin:
# a new random password
sudo ara-set-password
# or your own
printf '%s\n' '<new-password>' | sudo ara-set-password --stdin
Step 11: Your own DNS name and certificate
Point a DNS record at the instance (an Elastic IP keeps the address stable across stop and start), then run ara-set-domain with the name. It sets nginx's server names and ARA's allowed hosts, and issues a new self signed certificate for that name, plus any extra names or IPv4 addresses you list, localhost and 127.0.0.1:
sudo ara-set-domain <your-domain>
sudo grep -E '^ARA_(URL|HOST|TLS_SHA256)=' /root/ara-credentials.txt
Copy the new /etc/ara/ara-instance.crt to your Ansible controllers (Step 8). Without your own name, run sudo ara-set-domain with the new address after a stop and start changes the instance's public IP.
To serve ARA with a certificate your browsers and controllers already trust, put an Application Load Balancer with an AWS Certificate Manager certificate in front of the instance: forward HTTPS to the instance on port 443 and use /healthz as the health check path. The load balancer passes your name on to the instance, and its health checks use the instance's private address, so serve both: sudo ara-set-domain <your-domain> <private-ip>. nginx drops any other host name, health checks included.
Step 12: Reach a private network from ARA
ARA only receives data, so it never needs to connect to anything inside your VPC, and the ara and nginx accounts are blocked from private addresses, carrier grade NAT addresses, your VPC's own address ranges and the instance metadata service. If something you run as the ara account must reach a private host, add its network to the allow list and reload the rule:
echo '<private-ip>/32' | sudo tee -a /etc/cloudimg/ara-egress-allow
sudo systemctl reload cloudimg-ara-egress
The metadata service and link local addresses can never be allowed.
Step 13: Data, backups and server components
The versions on the image:
/opt/ara/venv/bin/python -c "from ara.setup import ara_version as v; print('ARA', v)"
/opt/ara/venv/bin/ansible --version 2>/dev/null | head -1
/opt/ara/venv/bin/python -V
nginx -v 2>&1
ARA 1.8.0
ansible [core 2.21.6]
Python 3.12.3
nginx version: nginx/1.24.0 (Ubuntu)
Everything ARA records is in one SQLite database, /var/lib/ara/server/ansible.sqlite. To back it up, stop ARA for a moment, copy the file, and start ARA again (or take a snapshot of the root volume):
sudo systemctl stop ara-web
sudo install -m 0600 /var/lib/ara/server/ansible.sqlite <backup-dir>/ara-$(date +%F).sqlite
sudo systemctl start ara-web
To restore into a new instance from this image, stop ARA, copy the backup over the database with the same owner and mode, and start ARA:
sudo systemctl stop ara-web
sudo install -o ara -g ara -m 0600 <backup-dir>/ara-<date>.sqlite /var/lib/ara/server/ansible.sqlite
sudo systemctl start ara-web
The restored database keeps its users and passwords, so the administrator from the old instance signs in, not the one in the new credentials file.
| Component | Path |
|---|---|
| ARA, ansible-core and their Python packages (root owned) | /opt/ara/venv |
| Database (ARA's only writable path) | /var/lib/ara/server/ansible.sqlite (ara:ara 0600) |
ARA server settings (rendered by ara-render-settings; do not edit) |
/etc/ara/server/settings.yaml |
| Ansible configuration for recording on the instance | /etc/ara/ansible.cfg |
| Instance certificate (public copy) | /etc/ara/ara-instance.crt |
| Demo playbook | /opt/ara/demo/record-demo.yml |
| nginx site | /etc/nginx/sites-available/ara |
| Licences, third party notices, source note, lock file | /usr/share/doc/ara/ |
| Helpers | /usr/local/sbin/ara-user, ara-set-password, ara-set-domain, ara-selftest |
| Services | ara-web (gunicorn), nginx, ara-firstboot (first boot only), cloudimg-ara-egress |
| Credentials | /root/ara-credentials.txt (mode 0600) |
Logs. ARA and gunicorn log to the journal (sudo journalctl -u ara-web). nginx's access log records request paths without query strings.
Recorded data. ARA stores what Ansible reports: task arguments, results and, unless you turn it off, host facts. Mark tasks that handle secrets with no_log: true, and see ARA's documentation for the callback options that ignore facts or arguments.
Step 14: Security recommendations
- Allow port 22 in the security group from your own networks only, and port 443 only to the people and controllers that use ARA. Never open port 8000.
- Check the certificate fingerprint the first time you connect (Step 5), then use your own DNS name (Step 11).
- Give each controller and pipeline its own ARA user (Step 10), and change the administrator's password if you shared it (Step 10).
- Use
no_log: trueon tasks that handle secrets, so their values are never sent to ARA. - Keep the operating system patched; unattended security upgrades are on, and
sudo apt-get update && sudo apt-get upgradeapplies everything else.
Support and licensing
ARA Records Ansible is open source under the GNU General Public License v3.0 or later, and ansible-core under the GNU General Public License v3.0 or later; there is no per user or per CPU licence fee. The licences, the third party notices, a note on where the source comes from and the lock file are in /usr/share/doc/ara/. cloudimg is not affiliated with or endorsed by the ARA project or Red Hat. Ansible is a registered trademark of Red Hat, Inc. The AMI price covers cloudimg's hardening, first boot setup and support.
- Email: support@cloudimg.co.uk
- Website: https://www.cloudimg.co.uk
- Support: 24/7, with a 24 hour response SLA