blog

Automated DEV/TEST environments for Acumatica with Ansible

· updated · acumatica · ansible · erp-devops · infrastructure-as-code · kvm · zfs

TLDR

Most Acumatica customers run production on Acumatica’s SaaS cloud, but customization development, upgrade rehearsal, and configuration testing need self-hosted instances you can afford to break. kborovik/acumatica-devops builds those instances with Ansible on one Ubuntu Linux KVM host: one make site goes from golden image to a running Acumatica login page, unattended. Rebuilding an instance costs one command instead of a day of clicking, so instances get rebuilt instead of drifting.

Where DEV/TEST lives when production is on Acumatica Cloud

Production on Acumatica’s SaaS cloud cannot host development; a self-hosted DEV/TEST lab costs compute plus build time, and the build time is the part this repo automates.

Most Acumatica customers run production on Acumatica’s SaaS cloud. Acumatica itself puts it at a “vast majority”. Acumatica hosts it, patches it, and backs it up — and that instance is not where you develop.

You cannot install a debugger on the SaaS instance, rehearse an upgrade on it, or reset it to a known state to test a customization. Customization development, upgrade rehearsal, and configuration testing all need instances you fully control and can afford to break. Those instances you host yourself. Acumatica’s own developer blog walks through standing up local instances for customization and integration work.

Tenant snapshots do not remove this need. A snapshot moves configuration and data between tenants, but it still needs a running instance to restore into. This repo builds that instance.

Hosting your own instances is cheaper than it sounds. Acumatica publishes downloadable builds with no registration, and SQL Server Developer Edition is a free dev/test license. A DEV/TEST lab costs compute, not license fees. What it costs instead is the time to build and maintain the instances, and that is the part kborovik/acumatica-devops automates.

Hand-built instances take a day and drift

A hand-built instance takes most of a day, nothing records how it was built, and when a rebuild costs a day people stop rebuilding — a test on a drifted instance proves nothing about the system that will go live.

A developer who needs an Acumatica DEV/TEST instance usually stands it up by hand: install Windows Server, then SQL Server, then the Acumatica MSI, then click through IIS and the instance deployment. It takes most of a day.

Nothing records what was done. The next instance is built the same way, from memory, and drifts from the first. DEV/TEST instances should be torn down and rebuilt often so they stay close to production. When a rebuild costs a day, people stop rebuilding, and the instance drifts further. A test that runs on a drifted instance proves nothing about the system that will actually go live.

One make site from golden image to login

One make site run clones a golden image, installs SQL Server unattended, and deploys Acumatica with the vendor’s own command-line tool — one command from inventory line to login page.

The host is one Ubuntu Linux machine running KVM, with VM disks on ZFS. Ansible configures the host and builds the guests. The lab currently runs Windows Server 2025 guests, SQL Server 2025 Developer, and Acumatica 26.101; versions are pinned in the Ansible configuration.

The build is inventory-driven. Add a host under the acu group in ansible/inventory/hosts.yml with a reserved IP, then apply the stack.

acu:
  hosts:
    acu-dev1: { vm_ip: 192.168.122.11 }
    acu-tst1: { vm_ip: 192.168.122.12 }
make site                # clone -> rename -> mssql -> acumatica

make is a standard Unix build tool. A Makefile gives each step a name and lists the commands behind it, like a batch file with named targets. Here make site is a wrapper around ansible-playbook site.yml, nothing more. Nothing depends on make itself: a GitHub Actions or GitLab CI runner can run the same playbook, on a schedule or on every change.

One make site clones a golden image, renames the guest, installs SQL Server unattended, and deploys Acumatica. The Acumatica step runs ac.exe, Acumatica’s own command-line deployment tool, so the automation follows the vendor-supported path. The run is unattended from the first command to the login page; rebuilding an instance costs one command, not a day of clicking. The result is a running instance at http://acu-dev1.vm.internal/, first login admin/setup. Every instance is built the same way, so two hosts in inventory produce two interchangeable guests. make vm LIMIT=acu-tst1 builds a single instance, and sudo win-vm-rm acu-tst1 --yes tears it down. The full lifecycle, from golden image to teardown, is documented in docs/windows-vms.md.

Ubuntu Linux host KVM + ZFSzfs clonezfs cloneSSHControl machinelaptop or CI runnermake siteGolden imagews2025-base, kept shut offacu-dev1Windows Server 2025SQL Server 2025 · Acumatica 26.101acu-tst1Windows Server 2025SQL Server 2025 · Acumatica 26.101http://acu-dev1.vm.internalhttp://acu-tst1.vm.internal

Disposable by design

Each instance is a ZFS clone of a golden image, so another developer’s instance costs one line of inventory, not another day of installs.

A golden image is built once per Windows Server version and kept shut off. Each instance is a zfs clone of that image — instant, with no data copied. A second developer, or a second branch under test, is another line in the inventory and another clone that shares the golden image’s blocks until it writes its own. Acumatica’s customization guide recommends a separate application instance for each developer. With clones, following that recommendation costs one line of inventory, not another day of installs.

The MAC address is derived from the last octet of vm_ip. The static DHCP lease and the <name>.vm.internal DNS record therefore exist before the VM first boots — no manual network step after the inventory line is in place.

The guest is renamed to its inventory name before SQL Server is installed. SQL Server records the machine name at install time, so the order matters, and the mssql role asserts it.

Each instance gets a separate data disk (a second zvol, formatted D:). The OS disk and the database disk are kept apart, so a storage rollback of the OS never drags the databases back with it.

The reset button

A ZFS snapshot is a whole-VM undo and a SQL .bak is a per-database restore; Ansible provisions both, and both run nightly.

A test environment is only useful if you can return it to a known state. Two mechanisms do that, both provisioned by Ansible.

A ZFS snapshot is a whole-VM undo. Snapshot before a risky change — an upgrade, a customization import — and roll back if it breaks:

sudo zfs snapshot -r upool/vms/acu-dev1@before-upgrade
sudo virsh shutdown acu-dev1
sudo zfs rollback upool/vms/acu-dev1@before-upgrade

A SQL-native .bak is per-database and portable. Restore one database without touching the rest of the VM, or move a database between instances to load a copy of real data into a test instance:

RESTORE DATABASE AcumaticaDB
  FROM DISK = '\\<host>\mssql-backups\acu-dev1\AcumaticaDB.bak'
  WITH REPLACE;

The commands above are the on-demand form. Both layers also run nightly on a schedule, with no manual routine. The full design, including retention and restore commands, is in docs/backup-strategy.md. The next post covers how they are scheduled and why neither replaces the other.

DEV/TEST as a pipeline build artifact

The DEV/TEST instance becomes a build artifact — the disposable, reproducible substrate the ERP GitOps pipeline deploys to and resets between test runs.

The environment the tenant configuration runs on becomes a build artifact, not a hand-assembled one-off. The mssql and acumatica roles configure the guest, not the hypervisor, so the same roles can build an instance on any Windows Server VM, including one on a public cloud.

This is the substrate for configuration-as-code. The ERP GitOps series (kborovik/acumatica-gitops) builds an Acumatica tenant from YAML, tests a change, and applies it through a pipeline. That pipeline needs a disposable, reproducible instance to deploy to and reset between test runs. This repo builds that instance. Read the operator manual and the vm_clone, mssql, and acumatica roles in the repo.