Openvox
Overview
OpenVox is the community-maintained, fully open-source continuation of Puppet, published under the Apache 2.0 license by Vox Pupuli. OpenVox Server is its server-side component. It compiles catalogues from your manifests and serves them to agents, runs the certificate authority that gives every managed node a verifiable identity, and collects the facts and run reports that those nodes send back.
This guide covers deploying and configuring OpenVox Server on Ubuntu 24.04 on Azure, using the SecureAnyCloud image from the Azure Marketplace. The image installs OpenVox Server 8.15.2 and OpenVox Agent 8.28.1 from the official OpenVox apt repository (apt.voxpupuli.org).
The repository is wired up with the project's own release package, pinned by SHA-256. The build asserts that the signing key it installs is the Vox Pupuli key 0E26 4299 8700 2418 F951 3F2A 5FB9 99C2 D62F F3D9 before it trusts a single package. Both packages are version-pinned and held. Unattended security upgrades keep the operating system patched on your running VM.
What configuration management gives you. You describe the state a machine should be in, in code, and the agent makes it so on every run, correcting anything that has drifted. The same manifest that builds a machine also keeps it built. Rebuilds become routine, audits become factual rather than aspirational, and you detect configuration drift instead of discovering it. OpenVox speaks the Puppet language, so the manifests, modules and Puppet Forge content you already have continue to work, and existing Puppet agents can report to this server.
The agent is installed alongside the server, on purpose. openvox-server declares a dependency on openvox-agent, and this image installs both on the same VM. The appliance is therefore complete and self-demonstrating. On its first boot it enrols itself, compiles a real catalogue and applies it, so you can see the whole loop working before you point another machine at it.
No certificate authority ships in this image. OpenVox Server has no administrator password. Its credential is an X.509 identity, so a prebuilt certificate authority baked into the image would hand every deployment the same CA private key. This image ships no certificate authority, no certificate and no private key at all.
On the first boot of every VM, a one-shot service creates that instance's own certificate authority and host certificate, bound to its own hostname and addresses. Only then does it open the listener. Until that happens, OpenVox Server is bound to loopback only and is held stopped by a bootstrap guard. A start-time identity check runs before every start and refuses to serve unless the instance has its own authority and a certificate that verifies against it.
What you get:
- OpenVox Server 8.15.2 from the official, signature-verified OpenVox repository, running under systemd as the unprivileged
puppetuser (puppetserver.service) and serving its HTTPS API on port 8140 - OpenVox Agent 8.28.1 on the same VM, enabled as
puppet.service, so the node keeps itself in the state its catalogue declares - A per-instance certificate authority and host certificate generated on first boot, with the CA fingerprint recorded in
/root/openvox-credentials.txt(mode 0600, root only) - Mutual TLS between server and agents: client certificates authenticate every request, and anonymous requests to protected endpoints are refused
- A JVM and JRuby pool sized for
Standard_B2s: a fixed 1536 MB heap and a single JRuby instance, rather than upstream's 2 GB default, which does not fit 4 GB of RAM. No swap is used to make it fit - A small, deliberately idempotent site manifest in the
productionenvironment, so your first agent run compiles and applies a real catalogue instead of an empty one
Prerequisites
Before you deploy, make sure you have:
- An active Azure subscription, an SSH public key, and a VNet with a subnet in the target region
- A subscription to the OpenVox listing on Azure Marketplace
- Network Security Group rules that allow TCP 22 (admin) and TCP 8140 from the machines you intend to manage. Do not open port 8140 to the whole internet. It is the control plane for your estate, and only your managed nodes need to reach it
- A way for your agents to resolve this server by name (private DNS, or a
hostsentry). Agents verify the server's certificate, and the certificate is issued for this VM's name
Recommended virtual machine size: Standard_B2s (2 vCPU, 4 GB RAM). This image is tuned specifically for it and compiles and applies catalogues at that size. For larger estates, see the scaling section in Step 10 and raise the JVM heap and the JRuby pool together.
Deploy from the Azure Portal
Search the Marketplace for OpenVox, select the SecureAnyCloud publisher and choose Create.
Configure the Network Security Group to allow TCP 8140 from the subnets that contain the machines you will manage, and TCP 22 for administration. Keep port 8140 closed to the public internet.
Deploy from the Azure CLI
Run the following commands to deploy the VM:
RG="openvox-prod"; LOCATION="eastus"; VM_NAME="openvox-01" IMAGE_URN="sac1647283583153:openvox-ubuntu-24-04:default:latest" SSH_KEY="$(cat ~/.ssh/id_rsa.pub)" az group create --name "$RG" --location "$LOCATION"
# Accept the Marketplace terms once per subscription (required before the first CLI deploy)
az vm image terms accept --urn sac1647283583153:openvox-ubuntu-24-04:default:latest
az vm create \
--resource-group "$RG" --name "$VM_NAME" \
--image "$IMAGE_URN" \
--size Standard_B2s \
--storage-sku StandardSSD_LRS \
--admin-username azureuser \
--ssh-key-values "$SSH_KEY" \
--public-ip-sku Standard --location "$LOCATION" \
--os-disk-delete-option Delete --nic-delete-option Delete
az vm open-port --resource-group "$RG" --name "$VM_NAME" --port 8140 --priority 1010The VM name you choose becomes this server's certname. It is the identity its certificate is issued for and the name your agents point at. Choose something stable and meaningful, such as openvox-01.
First boot
On the first boot of the VM, openvox-firstboot.service runs once. It performs these actions:
- Resolves this VM's own hostname and addresses
- Renders
/etc/puppetlabs/puppet/puppet.conffor that certname, with the hostname, the Azure private FQDN and the VM's addresses as subject alternative names - Creates this instance's own certificate authority and host certificate with
puppetserver ca setup - Moves the listener from loopback to all interfaces and starts OpenVox Server
- Runs the local agent against the server, which compiles and applies a real catalogue
- Writes
/root/openvox-credentials.txt(mode 0600, root only), which records this instance's certname, server URL and CA fingerprint
First boot takes roughly 30 to 60 seconds on Standard_B2s, most of which is the JVM starting. Wait for the sentinel before you continue:
sudo test -f /var/lib/sac/data/openvox-firstboot.done && echo "first boot complete"Confirm the services are running
Check the services and the status endpoint:
systemctl is-active puppetserver.service puppet.service
sudo ss -tlnp | grep 8140 curl -sk https://127.0.0.1:8140/status/v1/simple; echoExpected output:
active active LISTEN 0 50 *:8140 *:* users:(("java",pid=8468,fd=27)) runningpuppetserver.service is OpenVox Server. puppet.service is the OpenVox Agent daemon, which re-applies this node's catalogue on a schedule. The status endpoint returns the single word running when the server is ready to compile catalogues.
Read this instance's identity
OpenVox Server has no password. Its identity is its certificate authority, which was created on this VM's first boot and exists nowhere else. Read the credentials file:
sudo cat /root/openvox-credentials.txtThe file records the certname, the server URL, the paths to the CA and SSL directories, and the SHA-256 fingerprint of this instance's certificate authority. Confirm that the recorded fingerprint matches the authority on disk:
CADIR=$(sudo grep '^openvox.ca.dir=' /root/openvox-credentials.txt | cut -d= -f2-) sudo grep '^openvox.ca.fingerprint.sha256=' /root/openvox-credentials.txt
sudo openssl x509 -in "$CADIR/ca_crt.pem" -noout -fingerprint -sha256Both commands print the same fingerprint, for example:
openvox.ca.fingerprint.sha256=2C:CA:9C:8A:91:4D:36:77:28:E5:D0:46:28:38:B3:2B:40:08:9B:80:00:36:BB:A2:28:B3:9A:A0:A8:B8:8E:EC sha256 Fingerprint=2C:CA:9C:8A:91:4D:36:77:28:E5:D0:46:28:38:B3:2B:40:08:9B:80:00:36:BB:A2:28:B3:9A:A0:A8:B8:8E:ECThis fingerprint is unique to your instance. Note it down. It proves to an agent that it is talking to your server and not an impostor. Two VMs launched from this image have two completely different authorities, because neither authority shipped with the image.
Run this node against its own server
One command shows the whole product at work. The agent asks the server for a catalogue, the server compiles it from your manifests, and the agent applies it. Run:
CERTNAME=$(sudo grep '^openvox.certname=' /root/openvox-credentials.txt | cut -d= -f2-) sudo /opt/puppetlabs/bin/puppet agent --test --server "$CERTNAME"Expected output:
Info: Using environment 'production' Info: Retrieving pluginfacts Info: Retrieving plugin Notice: Requesting catalog from openvox-01:8140 (10.0.0.10) Notice: Catalog compiled by openvox-01 Info: Caching catalog for openvox-01 Info: Applying configuration version '1786073592' Notice: /Stage[main]/Main/Node[default]/File[/var/lib/sac/data/openvox-catalog-applied.txt]/ensure: defined content as '{sha256}2e1de2053488f2b94b421084633d7a9ef0736533c9f25aeac78d260b177698ed' (corrective) Notice: Applied catalog in 0.04 secondsCatalog compiled by <your server> shows the server doing its job, and Applied catalog shows the agent doing its job. The shipped manifest writes a small receipt that describes what was applied:
cat /var/lib/sac/data/openvox-catalog-applied.txt# Written by OpenVox when this node applied its catalogue. managed-by=openvox certname=openvox-01 environment=production os=Ubuntu 24.04Run the agent a second time:
CERTNAME=$(sudo grep '^openvox.certname=' /root/openvox-credentials.txt | cut -d= -f2-) sudo /opt/puppetlabs/bin/puppet agent --test --server "$CERTNAME"Notice: Catalog compiled by openvox-01 Info: Caching catalog for openvox-01 Info: Applying configuration version '1786073594' Notice: Applied catalog in 0.03 secondsNotice what is missing. There is no File[...]/ensure: line, because nothing needed changing. This property is idempotence, and it is what makes configuration management safe to run continuously. puppet.service runs exactly this loop for you on a schedule.
To assert idempotence mechanically, use --detailed-exitcodes. It returns 2 when changes were made and 0 when the node was already in the declared state:
CERTNAME=$(sudo grep '^openvox.certname=' /root/openvox-credentials.txt | cut -d= -f2-) sudo /opt/puppetlabs/bin/puppet agent --test --detailed-exitcodes --server "$CERTNAME" >/dev/null 2>&1 echo "exit code: $? (0 = already in the declared state, 2 = changes applied)"Verify the mutual TLS boundary
Client certificates authenticate every OpenVox request. Prove it in both directions.
An authenticated request that uses this VM's own certificate succeeds:
CERTNAME=$(sudo grep '^openvox.certname=' /root/openvox-credentials.txt | cut -d= -f2-)
SSLDIR=$(sudo grep '^openvox.ssl.dir=' /root/openvox-credentials.txt | cut -d= -f2-) sudo curl -s --cacert "$SSLDIR/certs/ca.pem" \
--cert "$SSLDIR/certs/$CERTNAME.pem" --key "$SSLDIR/private_keys/$CERTNAME.pem" \
"https://$CERTNAME:8140/status/v1/services?level=critical" | head -c 200; echo{"puppet-profiler":{"service_version":"8.15.2","service_status_version":1,"detail_level":"critical","state":"running","status":{},"active_alerts":[]},"jruby-metrics":{"service_version":"8.15.2",...An anonymous request for a catalogue is refused:
CERTNAME=$(sudo grep '^openvox.certname=' /root/openvox-credentials.txt | cut -d= -f2-)
curl -sk -o /dev/null -w 'anonymous catalog request: HTTP %{http_code}\n' \
"https://127.0.0.1:8140/puppet/v3/catalog/$CERTNAME?environment=production"anonymous catalog request: HTTP 403A node with no signed certificate cannot obtain a catalogue, so it cannot learn anything about how your estate is configured. Confirm the file permissions that protect the identity:
CERTNAME=$(sudo grep '^openvox.certname=' /root/openvox-credentials.txt | cut -d= -f2-)
CADIR=$(sudo grep '^openvox.ca.dir=' /root/openvox-credentials.txt | cut -d= -f2-)
SSLDIR=$(sudo grep '^openvox.ssl.dir=' /root/openvox-credentials.txt | cut -d= -f2-) sudo stat -c '%a %U:%G %n' /root/openvox-credentials.txt "$CADIR/ca_key.pem" "$SSLDIR/private_keys/$CERTNAME.pem"600 root:root /root/openvox-credentials.txt 640 puppet:puppet /etc/puppetlabs/puppetserver/ca/ca_key.pem 640 puppet:puppet /etc/puppetlabs/puppet/ssl/private_keys/openvox-01.pemWrite your own manifest
The shipped manifest lives in the production environment and is ordinary OpenVox code. Read it first:
sudo cat /etc/puppetlabs/code/environments/production/manifests/site.ppAdd your own resources to the same node default block, or create per-node blocks. This example ensures that a package is installed, a directory exists and a service is running:
node default { package { 'htop': ensure => installed, } file { '/opt/company': ensure => directory, owner => 'root', mode => '0755', } service { 'chrony': ensure => running, enable => true, } }Check that a manifest parses before an agent run needs it:
sudo /opt/puppetlabs/bin/puppet parser validate /etc/puppetlabs/code/environments/production/manifests/site.pp && echo "manifest parses"Install modules from the Puppet Forge into the environment's module path. OpenVox is compatible with Puppet language modules:
sudo /opt/puppetlabs/bin/puppet module list --modulepath /etc/puppetlabs/code/environments/production/modulesTo install a module, use sudo /opt/puppetlabs/bin/puppet module install <author>-<module> --modulepath /etc/puppetlabs/code/environments/production/modules from a VM with outbound internet access.
Enrol another machine as an agent
Run the commands in this step on the agent machine, not on this server. Replace <vm-ip> with this server's private address and <server-certname> with the certname you read in Step 5.
The agent must be able to resolve the server by the name on its certificate, because that is the name the agent verifies the server's certificate against. If you have no private DNS, add a hosts entry.
# RUN THESE ON THE AGENT MACHINE, NOT ON THE OPENVOX SERVER.
echo "<vm-ip> <server-certname>" | sudo tee -a /etc/hosts curl -fsSLO https://apt.voxpupuli.org/openvox8-release-ubuntu24.04.deb
sudo dpkg -i openvox8-release-ubuntu24.04.deb
sudo apt-get update && sudo apt-get install -y openvox-agent
sudo /opt/puppetlabs/bin/puppet config set server <server-certname> --section main
sudo /opt/puppetlabs/bin/puppet agent --test --waitforcert 30Back on this server, list the pending request:
sudo /opt/puppetlabs/bin/puppetserver ca list --allSigned Certificates: openvox-01 (SHA256) 0C:3F:B6:C6:FD:7A:A2:2E:66:66:61:64:F0:63:BD:2F:21:0D:2A:81:B7:90:5D:CE:D8:44:A0:F7:20:A5:48:47 alt names: ["DNS:openvox-01", "DNS:localhost", "DNS:openvox", "DNS:puppet"]Sign a waiting request with sudo /opt/puppetlabs/bin/puppetserver ca sign --certname <agent-certname>. Once the request is signed, the agent's --waitforcert run continues automatically and applies its first catalogue.
Autosigning is deliberately disabled in this image (autosign = false in puppet.conf). Signing is the moment you decide that a machine may receive your configuration, so make that decision explicitly.
Scale the server for your estate
This image is tuned for Standard_B2s. The two settings that matter are the JVM heap and the JRuby pool. Raise them together, because each JRuby instance needs roughly 512 MB of heap to compile catalogues comfortably. Check the current values:
grep -E '^JAVA_ARGS=' /etc/default/puppetserver
grep -E '^\s*max-active-instances' /etc/puppetlabs/puppetserver/conf.d/puppetserver.confJAVA_ARGS="-Xms1536m -Xmx1536m -XX:ReservedCodeCacheSize=192m -XX:+UseG1GC -XX:MaxMetaspaceSize=256m -Djruby.logger.class=com.puppetlabs.jruby_utils.jruby.Slf4jLogger" max-active-instances: 1A single JRuby instance compiles catalogues one at a time, which is right for a small estate on two vCPUs. To grow, move to a larger VM size and raise both values. For example, on a 4 vCPU / 16 GB machine, use a 6 GB heap with 4 JRuby instances.
Edit JAVA_ARGS in /etc/default/puppetserver and max-active-instances in /etc/puppetlabs/puppetserver/conf.d/puppetserver.conf, then run sudo systemctl restart puppetserver.service.
Check the current memory headroom at any time:
free -m | head -2 ps -eo rss,comm --no-headers | awk '$2=="java"{printf "OpenVox Server JVM RSS: %d MB\n", $1/1024}'Do not add swap to squeeze in a larger heap. Move to a larger VM size instead.
Where things are
List the key directories:
ls /etc/puppetlabs/code/environments/production
sudo tail -3 /var/log/puppetlabs/puppetserver/puppetserver.logPath What it is
/etc/puppetlabs/code/environments/production/manifests/ Your manifests. site.pp is the entry point
/etc/puppetlabs/code/environments/production/modules/ Modules for this environment, including Puppet Forge modules
/etc/puppetlabs/puppet/puppet.conf Server and agent settings, rendered for this VM on first boot
/etc/puppetlabs/puppetserver/ca/ This instance's certificate authority. Back it up; losing it means re-enrolling every agent
/etc/puppetlabs/puppet/ssl/ This node's own certificate and private key
/etc/default/puppetserver JVM arguments, including the heap size
/var/log/puppetlabs/puppetserver/puppetserver.log Server log, including every catalogue compilation
/root/openvox-credentials.txt This instance's identity summary and CA fingerprint (0600)
The server log records every compilation, so it is the first place to look when an agent run fails:
2026-08-07T03:33:14.621Z INFO [qtp2073451292-60] [puppetserver] Puppet Compiled catalog for openvox-01 in environment production in 0.22 secondsHarden the deployment
- Keep port 8140 private. It is the control plane for your estate. Scope the Network Security Group to the subnets that hold your managed nodes, never to the internet.
- Back up the certificate authority.
/etc/puppetlabs/puppetserver/ca/is unique to this instance and cannot be regenerated. If you lose it, you must re-enrol every agent. Back it up encrypted, and treat it with the same care as any private key. - Leave autosigning off. Signing a certificate is the decision to trust a machine with your configuration.
- Revoke decommissioned nodes so that a retired machine's certificate cannot be reused:
sudo /opt/puppetlabs/bin/puppetserver ca clean --certname <agent-certname>. - Keep secrets out of manifests. Manifests are compiled into catalogues and cached on the agent. Use an external lookup for secrets rather than literals in code.
- Version-control your manifests. The value of infrastructure as code is that the code is reviewable and revertible.
- Keep the OS patched. Unattended security upgrades are enabled in this image. The OpenVox packages themselves are pinned and held, so upgrade them deliberately after testing.
Support and licensing
OpenVox is licensed under the Apache License 2.0 and is maintained by the Vox Pupuli community. The upstream project is at github.com/OpenVoxProject.
OpenVox is an independent open-source continuation of Puppet and is compatible with it. PUPPET is a trademark of Perforce Software, Inc. SecureAnyCloud and Vox Pupuli are not affiliated with, endorsed by, or sponsored by Perforce Software. References to Puppet in this guide describe compatibility only.
SecureAnyCloud provides 24/7 technical support for this image by email (sales@secureanycloud.com) and live chat, with a one-hour average response time for critical issues. Support covers enrolling and signing agents, migrating existing manifests and modules, structuring environments, sizing the JVM and JRuby pool as your node count grows, certificate lifecycle, and troubleshooting failed compilations and agent runs.
Deploy on Azure
Find OpenVox Server on Ubuntu 24.04 by SecureAnyCloud on the Azure Marketplace.
Need Help?
Contact sales@secureanycloud.com or visit secureanycloud.com/support.
Deploy on Azure
Launch OpenVox Server 8.15.2 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