How it works
The parts of an installation, what happens at a launch, and where data is kept
Architecture
AnsibleForms is a Node.js server that serves the web application and a REST API, backed by a MySQL database. It reads forms from YAML files and runs nothing itself: it hands each job to a runner. A playbook goes to one of your RTE (runtime environment) containers, which runs ansible-playbook, and a template or job goes to AWX, AAP, Ascender, Semaphore, Rundeck or Jenkins:
The components in the diagram each have a role of their own:
| Component | Role |
|---|---|
| Browser | The Vue web application: the forms, the Designer, the job history and the settings |
| AnsibleForms server | Evaluates the forms, records the jobs, runs the workflows and the schedules, hands each job to its runner and follows it, and serves the REST API |
| Runners | Where jobs run, added under Connections > Runners: a form names one with runner: <name>, or uses the default runner of its type (see Runners) |
| RTE containers | One or more runtime environment containers, the same server started with AF_ROLE=rte: each runs ansible-playbook for ansible forms, with the python libraries and collections of its image, and sends the output and the final status back to the server over one WebSocket, without database credentials (see How the RTE runs) |
| AWX / AAP / Ascender, Semaphore, Rundeck, Jenkins | Runners for templates and jobs: AWX forms launch a job or workflow template, Semaphore, Rundeck and Jenkins forms launch one of their jobs, and the server follows its output |
| MySQL / MariaDB | Holds the jobs and their output, the users, the credentials and the connections; only the server reads and writes it |
| Persistent folder | Holds config.yaml, the form files, the playbooks, the logs and the backups |
| Data sources | Fill the fields: database queries, REST APIs, files and other functions |
| Secret stores | Supply the user and password of a credential at use time, see Secret stores |
| Git repositories | Keep the forms, the playbooks and config.yaml under version control |
| Login providers | Sign the users in: LDAP, Entra ID and OIDC, next to the local users |
| Notification channels | Carry the notifications and the approval requests: mail, Slack, Microsoft Teams, Telegram or a webhook |
| Syslog servers | Receive the log and the audit trail as they are written, see Syslog |
| MCP client, chat | Optional, both off by default: the MCP server and the chat assistant, which needs a model provider |
By default one container runs the server, web app and background work together, next to one RTE. For several app nodes behind a load balancer, a worker that runs the background work, and more RTEs, see Scaling.
Built with
The server and the web interface are built on these components:
| Part | Technology |
|---|---|
| Server | Node.js and Express |
| Database | MySQL or MariaDB |
| Web interface | Vue 3, with Bootstrap 5 and Font Awesome |
What happens when you launch a form
A launch goes through the same steps whether it comes from the browser or from the REST API:
- Load : the server sends only the forms the user’s roles allow, and evaluates server expressions
- Submit : the browser turns the field values into extravars and posts them to
POST /api/v2/job - Check : the server reloads the form for the user’s roles and, with launch validation on, checks the values
- Record : the job is stored with status
running, and its id is added to the extravars as__jobid__ - Approve : a form with an
approvalpoint waits until an approver accepts the job - Credentials : for a playbook, the app (never the RTE) reads the form’s credentials from the database or a secret store
- Run : the job goes to its runner, the one the form names with
runner:or the default of its type. For a playbook, the app sends the job to its RTE over the RTE’s WebSocket, with its credentials sealed for it, and the RTE runsansible-playbook(see How the RTE runs), and AWX, AAP, Ascender, Semaphore, Rundeck or Jenkins runs a template or a job. A workflow runs its nodes, each as a job of its own on its own runner - Follow : the server saves the output in the database as it arrives, sent by the RTE over its socket for a playbook, or read from the other runners. The form page re-reads the job every two seconds, and the Jobs page follows it live as the server announces each change
- End : the job gets its final status, and its
notificationsgo out by mail and to its notification channels when the form asks for them
Where things are stored
AnsibleForms keeps its state in two places: the MySQL database and the persistent folder. Back up both.
In the database
The database, AnsibleForms, is created at the first start (unless ALLOW_SCHEMA_CREATION is 0) and holds:
- the jobs, their extravars and their output, the schedules and the stored jobs
- the local users, the groups and the API tokens
- the credentials, encrypted with
ENCRYPTION_SECRET, and the secret store connections - the runners, the notification channels (mail included), the syslog servers, and the LDAP, SSO, repository and chat assistant settings
- the audit log
In the persistent folder
It is /app/dist/persistent in the image and server/persistent from source; each path has its own variable:
| Default path | Variable | Contents |
|---|---|---|
config.yaml | CONFIG_PATH | The categories, the roles and the constants |
forms/ | FORMS_FOLDER_PATH | The form files, .yaml or .yml, in subfolders if you like |
playbooks/ | ANSIBLE_PATH | The playbooks of ansible forms, and their working directory |
repositories/ | REPO_PATH | The clones of the git repositories |
forms_backups/ | FORMS_BACKUP_PATH | The copies the Designer makes before it saves |
backups/ | BACKUP_PATH | The database backups |
uploads/ | UPLOAD_PATH | The files uploaded through file fields |
logs/ | LOG_PATH | ansibleforms.log and ansibleforms.errors.log |
certificates/ | HTTPS_CERT, HTTPS_KEY | The certificate and key, when HTTPS is 1 |
.env | The environment variables saved from the settings pages (unless ALLOW_ENV_EDIT is 0) |
The generated SSH key lives in HOME_PATH, outside the persistent folder by default.