Announcing CFEngine Build

November 1, 2021

Earlier this year, we hinted at what we were working on - a place for users to find and share reusable modules for CFEngine. Today, the CFEngine team is pleased to announce the launch of CFEngine Build:

The new website,, allows you to browse for modules, and gives you information about how to use each one of them. When you’ve found the module you were looking for, it can be downloaded and built using the command line tooling.

Downloading and using modules

The new command line tool, cfbs (CFEngine Build System) allows you to download multiple modules, update them, and build them (combine them into a policy set). Once built, you can deploy your policy set to your CFEngine hub (policy server).

First, install the tooling with pip:

$ pip install cfbs

(Depending on which platform and python version you are on, you may have to use pip3 instead of pip, and possibly add sudo to the beginning).

To get started, use the init command in a fresh folder:

$ mkdir my_policy
$ cd my_policy
$ cfbs init
Initialized - edit name and description cfbs.json
To add your first module, type: cfbs add masterfiles

This created a small cfbs.json project file.

A lot of functionality in CFEngine relies on our default policy set, the Masterfiles Policy Framework (MPF). For this reason, you should add it as the first module in your build, as a base:

$ cfbs add masterfiles

(At this point you could build and deploy the policy set, but it doesn’t do much yet, it’s just the default policy you are used to from our packages).

In addition to the website, you can also find modules from the command line:

$ cfbs search
promise-type-ansible (ansible) - Promise type to manage systemd services
autorun - Enable autorun functionality
library-for-promise-types-in-bash (bash-lib) - Library enabling promise types implemented in bash

We can add some of them:

$ cfbs add autorun inventory-sudoers git systemd groups
git is an alias for promise-type-git
systemd is an alias for promise-type-systemd
groups is an alias for promise-type-groups
Added module: library-for-promise-types-in-python (Dependency of promise-type-git)
Added module: autorun
Added module: inventory-sudoers
Added module: promise-type-git
Added module: promise-type-systemd
Added module: promise-type-groups

What modules you add is entirely up to you. In our example above, we have achieved the following:

  1. Enabled autorun functionality
  2. Started tracking which users have sudo access (visible in Mission Portal)
  3. Added 3 new promise types: git, systemd, and groups
    • Each of them is ready to be used in policy

To complete the example, let’s create a simple policy file,

bundle agent clone_lynis
      "tags" slist => { "autorun" };

      version => "master",
      repository => "";

Here, we use the new git promise type, to clone lynis. Let’s add it using cfbs:

$ cfbs add
Added module: ./

Finally, let’s build our new policy set:

$ cfbs build

001 masterfiles                         @ 5c7dc5b43088e259a94de4e5a9f17c0ce9781a0f (Downloaded)
002 library-for-promise-types-in-python @ c3b7329b240cf7ad062a0a64ee8b607af2cb912a (Downloaded)
003 autorun                             @ c3b7329b240cf7ad062a0a64ee8b607af2cb912a (Downloaded)
004 inventory-sudoers                   @ 01fe2d60ecea4e2e0aefbbd9b4bc9ad570425f9f (Downloaded)
005 promise-type-git                    @ c3b7329b240cf7ad062a0a64ee8b607af2cb912a (Downloaded)
006 promise-type-systemd                @ c3b7329b240cf7ad062a0a64ee8b607af2cb912a (Downloaded)
007 promise-type-groups                 @ 087a2fd81e1bbaf241dfa7bf39013efd9d8d348f (Downloaded)
008 ./                    @ local                                    (Copied)

001 masterfiles                         : run './'
001 masterfiles                         : delete './'
001 masterfiles                         : run './cfbs/'
001 masterfiles                         : delete './cfbs/'
001 masterfiles                         : copy './' 'masterfiles/'
002 library-for-promise-types-in-python : copy '' 'masterfiles/modules/promises/'
003 autorun                             : json 'def.json' 'masterfiles/def.json'
004 inventory-sudoers                   : copy './policy/' 'masterfiles/services/inventory-sudoers/'
004 inventory-sudoers                   : json './cfbs/def.json' 'masterfiles/def.json'
005 promise-type-git                    : copy '' 'masterfiles/modules/promises/'
005 promise-type-git                    : append '' 'masterfiles/services/'
006 promise-type-systemd                : copy '' 'masterfiles/modules/promises/'
006 promise-type-systemd                : append '' 'masterfiles/services/'
007 promise-type-groups                 : copy '' 'masterfiles/modules/promises/'
007 promise-type-groups                 : append '' 'masterfiles/services/'
008 ./                    : copy './' 'masterfiles/services/autorun/'

Generating tarball...

Build complete, ready to deploy 🐿
 -> Directory: out/masterfiles
 -> Tarball:   out/masterfiles.tgz

To install on this machine: cfbs install
To deploy on remote hub(s): cf-remote deploy --hub hub out/masterfiles.tgz

As the last 2 lines of output indicate, we now have a policy set we can deploy. I already have a CFEngine hub in cf-remote, named hub, so I will deploy it there:

$ cf-remote deploy --hub hub out/masterfiles.tgz

Deploying to:

OS            : ubuntu (debian)
Architecture  : x86_64
CFEngine      : 3.18.0 (Enterprise)
Policy server : None
Binaries      : dpkg, apt

Copying: '/Users/olehermanse/mypolicy/out/masterfiles.tgz' to 'ubuntu@'
Renaming 'masterfiles.tgz' -> 'masterfiles.tgz' on 'ubuntu@'
Running: 'systemctl stop cfengine3 && rm -rf /var/cfengine/masterfiles && mv masterfiles /var/cfengine/masterfiles && systemctl start cfengine3 && cf-agent -Kf && cf-agent -K'

I can log into the machine to see if my policy worked:

$ ssh ubuntu@
$ sudo ls /opt/lynis/	    FAQ		    README	 db		include	 default.prf	lynis     INSTALL  developer.prf	lynis.8     LICENSE	 extras		plugins

Indeed it worked. I can also see the new inventory attribute in Mission Portal:

The future

CFEngine follows a 6-month release schedule, with 18 months between each new LTS series. This is great for stability and predictability, but it does unfortunately mean that the time until a new feature arrives can be long. With CFEngine Build, we can continuously publish new modules and functionality in the tooling, and users can start using it the same day.

It does not change what is running on your servers - they will still have a policy set, just like before (located in /var/cfengine/masterfiles on the hub). The policy set is built and managed using CFEngine Build, but you choose when to deploy it, and you can test it first. CFEngine Build enables you to use new policy and modules, at ease, when you want to.

Without spoiling everything we have planned, here are some things you can look forward to in the near future:

  • Functionality and tutorials for migrating existing policy sets to CFEngine Build
  • The ability to share Mission Portal reports as modules
  • New promise types for HTTP requests and firewall management

Open source, for all users of CFEngine

Every part of this is open source; the command line tool, the website, the modules, etc. We want everyone to be able to use CFEngine Build and contribute, whether you’re a CFEngine Enterprise customer, a long time Community user, or someone who just got started with the Free 25 Enterprise Licenses.

The website is built with hugo and Less, and served using nginx. This is the same technology stack we use for The command line tool is written in Python, and released on pypi. (That’s very similar to how we develop cf-remote.) It uses git, requests, rsync internally, to clone repos, interact with APIs and copy files around for the build process. We’d like to take this opportunity to thank the authors and maintainers of all the mentioned open source software.


Before starting to write your own modules, we recommend getting familiar with the tooling and how things work. Once you feel like you understand the basics, there is a guide for how to author your own modules:

Getting help

If you have questions, suggestions or need help, please use our GitHub Discussions: