Newbie questions

45 views
Skip to first unread message

Jakub Wardyn

unread,
Aug 3, 2025, 11:51:16 AMAug 3
to help-cfengine
Hello,
I'm a total newbie in CFEngine, I'm interested in learning but I have trouble understanding how to even start, and I'm beginning to think maybe CFEngine isn't what I need.

Right now I have a couple of servers, on which I want to deploy some services (some in Docker, some as regular processes), I want a solution where I can describe that declaratively, and have a e.g. git repository with "current state of everything that's running on my servers", I also want to automate stuff like setting up backups, updating the system, etc. It's all Linux for now, and I expect the number of servers to grow in the future.

I previously used Ansible and Salt but I found those to be very opaque, complex and fragile, I wanted a low-level solution and CFEngine really looks nice here. With ChatGPT's help I even got some code that I can run to deploy Docker service and it all makes sense to me.

What doesn't make sense though, is the massive number of stuff I get in my "masterfiles" directory (I think it's called MPF), and contradicting documentation.

I have Alpine Linux and Debian Linux, as hub and agent respectively. They both use different directories for CFEngine's files (/var/lib/cfengine vs /var/lib/cfengine3), and this causes errors during bootstrap, unless I symlink, which seems hacky. In addition documentation everywhere says that /var/cfengine should be used, which is not the case.

In addition - the number of stuff in MPF adds, what I think is, unnecessary complexity to my use case, ideally I'd probably copy some stuff like stdlib etc. but delete the rest. I tried to start with a small and simple promises.cf but I failed miserably due to some issues with relative paths access, the failsafe.cf requested relative paths, while the hub didn't allow me to define ACL for relative paths...

Anyway, I think what I really want to know is:
- Is there any "practical" guide for CFEngine which is reasonably recent, and can guide me through building small-scale stuff from the grounds up. I'd appreciate a link.
- Do I need MPF? If I have a git repo do I just copy MPF into it? What does MPF do for me? The very reason that I wanted to learn CFEngine is the simplicity and transparency of what it does, MPF seems to contradict that.
- How to deal with the differences between paths in different distros? Should I just roll out CFEngine manually? I thought I can just install CFEngine in anyway, and then bootstrap and it will figure everything out from there, seems like that's not the case. I hope the "hub" can tell the agents where to look for the policies? Or is the files structure just coupled together everywhere?
- And honestly, is CFEngine even the right tool for use cases like mine? I don't mind tinkering a bit and working in low-level stuff, but I hate fighting with the complexity of my tools. I want a simple low-level tool with which I build my own automation, not an "out-of-the-box" corporate solution for thousands of servers.

Richard Jones

unread,
Aug 3, 2025, 1:16:41 PMAug 3
to Jakub Wardyn, help-cfengine
(I'm by no means any kind of expert, but I've been using CFE for a
number of years now)

On Aug 03, Jakub Wardyn wrote
> Hello,
> What doesn't make sense though, is the massive number of stuff I get in my
> "masterfiles" directory (I think it's called MPF), and contradicting
> documentation.

I tried briefly to write my own, a cut-down version, and failed
miserably. I suspect you'll receive similar advice from others, but
let's see.

> I have Alpine Linux and Debian Linux, as hub and agent respectively.
> They both use different directories for CFEngine's files
> (/var/lib/cfengine vs /var/lib/cfengine3), and this causes errors
> during bootstrap, unless I symlink, which seems hacky. In addition
> documentation everywhere says that /var/cfengine should be used, which
> is not the case.

If you use the community packages provided at cfengine.com, then
everything lives in /var/cfengine, much easier. Otherwise you can use
the $(sys.workdir) variable.

> Anyway, I think what I really want to know is:
> - Is there any "practical" guide for CFEngine which is reasonably recent,
> and can guide me through building small-scale stuff from the grounds up.
> I'd appreciate a link.

Diego Zamboni's "Learning CFEngine" might be a good place to start,
though it's probably a little out of date by now. I think he keeps his
errata and addendum up to date though.

> - Do I need MPF? If I have a git repo do I just copy MPF into it? What does
> MPF do for me? The very reason that I wanted to learn CFEngine is the
> simplicity and transparency of what it does, MPF seems to contradict that.
> - How to deal with the differences between paths in different distros?
> Should I just roll out CFEngine manually? I thought I can just install
> CFEngine in anyway, and then bootstrap and it will figure everything out
> from there, seems like that's not the case. I hope the "hub" can tell the
> agents where to look for the policies? Or is the files structure just
> coupled together everywhere?

I had, probably through my own fault, lots of problems getting clients
bootstrapped to the hub. In the end I just used a git repo and
bootstrapped to localhost "cf-agent -B 127.0.0.1"

I have my code in a separate directory and copy an autorun.cf file into
/var/cfengine/masterfiles/services/autorun/. This means, bar this one
file, you don't need to keep all of masterfiles in your repo and can
download the stock one. Something along the lines of the following,
which separates out machines by their DNS domain:

bundle common inputs {

# Despite its location, the bundles in services/autorun need the
# "autorun" tag.
meta:
"tags" slist => { "autorun" }

vars:
"inputs" slist => { findfiles("$(sys.workdir)/$(sys.domain)/cf/*.cf") };

reports:
"inputs are @(inputs)";
}

body file control {

inputs => { @(inputs.inputs) };

}

and then in /var/cfengine/cf/mydomain.com/mydomain.cf we can separate out machines roles:

bundle agent mydomain {

# Also needs to be tagged as autorun, the methods below then take
# care of running the other bundles per machine/role/whatever.
meta:
"tags" slist => { "autorun" };

methods:
webserver_host::
"webserver" usebundle => "webserver";

}

It's a steep initial learning curve, but in my opinion, once you've got
your bundles and files in place it's a breeze to automate systems.

The above was taken in part from:
https://cfengine.com/blog/2015/dynamic-bundlesequence-with-autorun-meta-tags-and-hard-classes/
which my also help.

Thanks,

Richard

--
junix.systems/privacy
+44 7843 588 599

Marco Marongiu

unread,
Aug 3, 2025, 1:30:39 PMAug 3
to help-c...@googlegroups.com
Hi Jakub

You need this book: https://cf-learn.info/

I had the same struggles you have. This book made all the difference.
Worth every cent.

Ciao
-- bronto

PS: I am in the list of testimonials, but it doesn't mean I earn
anything if you buy the book or you don't. I do think that Diego's book
is the best CFEngine book ever written.

Jakub Wardyn

unread,
Aug 4, 2025, 12:28:06 PMAug 4
to help-cfengine
Thank you for your quick responses and suggestions!

I managed to move forward a bit, but there's a lot of "friction".
Quick try of compiling CFEngine on Alpine Linux failed, so I just settled on the "symlink hack" for now.
I also know more or less how to hook up to the MPF so it runs my policies.

Got quick two questions:
1. Can strings in CFEngine be split up into multiline without defining additional variables, and without adding unintended whitespaces?
E.g. In C++ I'd do something like this:
```
const char* string = "This is my first line,"
" this is another line";
```
Which evaluates to "This is my first line, this is another line"
2. Can I build my own bundles/functions which return values? I've seen that bundles can modify variables and define class in the parent scope, but that seems quite horrible. I mean something like this:
```
vars:
    "my_value" string => do_http_request("some arguments here");
```
where "do_http_request" is some wrapper around Curl that I can write on my own,
can't use url_get, because the binary on Alpine is compiled without curl support, and I would rather not bother with that,
and instead do what I would do in other languages: abstract the mundane task of calling curl into something less repetitive.

craig.c...@northern.tech

unread,
Aug 4, 2025, 2:58:57 PMAug 4
to help-cfengine
Hello Jakub,

Thanks for trying out CFEngine!

I feel your pain when it comes to different, as we call it, WORKDIR values e.g. /var/cfengine as we use in our packages versus distribution packages which use all kinds of things. Ideally things would "just work" if policy and code user the configured WORKDIR value always but this is likely not the case.

Yes, multiline strings are possible, typically using the concat function for comments, strings, etc. For example:

bundle agent main

{

  reports:

"${with}" with => concat(

"hello, ",

"world!");

}


If you find yourself wanting to create "methods bundles" which are "true functions" you might be sort of "doing it wrong" in some ways.


I would say it is not so often used. Another option I would consider is to write a shell script as a wrapper and call that instead.

You had: "my_value" string => do_http_request("some arguments here");

I might suggest: "my_value" string => execresult("wrapper ${arg1} ${arg2}",  useshell);


That way you could use that wrapper in scripts also if need be. Obviously you might get into trouble with quotes and spaces going that route but it might work for you.

---

A general note at least from my perspective as a member of the CFEngine team and fairly frequent policy writer is if you find yourself trying to write procedural "code", take a minute to refactor things and write policy as declarative "what" you want and not so much "how" to make it happen. I would even suggest using POSIX sh compatible scripting for some of that "how". Writing procedural style CFEngine policy is possible but in my opinion often leads to policy which is hard to read and debug.

The books suggested so far are great resources.

You might try https://github.com/nickanderson/CFEngine-zero-to-hero-primer as well. Nick is a master at policy writing but is out for a few days. He will likely have much to say to help you along as well.

Also, there are folks on IRC at libera.chat and Matrix if you like those platforms: https://cfengine.com/developers/ has some more resources.

Keep sending questions, we'll do our best to help you along!

-Craig Comstock
CFEngineer/Digger/Software Engineer

Nick Anderson

unread,
Aug 8, 2025, 6:54:59 PMAug 8
to Jakub Wardyn, help-cfengine

Hi Jakub,

Welcome to CFEngine. I will jump around a bit in my response.

  • Is there any "practical" guide for CFEngine which is reasonably recent, and can guide me through building small-scale stuff from the grounds up. I'd appreciate a link.

I don't think there is a single guide that works for everyone. If I had to provide but a single link it would have to be https://docs.cfengine.com.

Right now I have a couple of servers, on which I want to deploy some services (some in Docker, some as regular processes), I want a solution where I can describe that declaratively, and have a e.g. git repository with "current state of everything that's running on my servers", I also want to automate stuff like setting up backups, updating the system, etc. It's all Linux for now, and I expect the number of servers to grow in the future.

This sounds like stuff that CFEngine can help you with. You might find promise-type-docker-compose useful or a source of inspirational (https://build.cfengine.com/modules/promise-type-docker-compose/).

  • And honestly, is CFEngine even the right tool for use cases like mine? I don't mind tinkering a bit and working in low-level stuff, but I hate fighting with the complexity of my tools. I want a simple low-level tool with which I build my own automation, not an "out-of-the-box" corporate solution for thousands of servers.

So, you have some servers running Linux and you want a tool that can help you manage configuration files, processes and help you adjust and maintain the state of the running systems over time. You want something simple and low level to build your own system with and not an out of the box solution.

I am bias, but I think CFEngine sounds like a natural fit.

I think that CFEngine is simple, but I guess it's subjective. I am at least quite familiar with how it operates. If you find yourself "fighting" with CFEngine maybe take a step back and see if you can approach from a different perspective. I remember when starting with CFEngine that it changed the way I thought about things.

  • How to deal with the differences between paths in different distros? Should I just roll out CFEngine manually? I thought I can just install CFEngine in anyway, and then bootstrap and it will figure everything out from there, seems like that's not the case. I hope the "hub" can tell the agents where to look for the policies? Or is the files structure just coupled together everywhere?

I think it's important to remember the agency of the agents, no one tells an agent anything. Perhaps imagine yourself as the agent rather than hollering at the hub in the sky ask where you should look for your policy think what in my environment can I clue in on to figure out where to get policy.

Installing and bootstrapping to a hub and then figuring things out is totally possible. That's I think what most people do. By default the MPF doesn't really do much of anything.

I am not sure what you mean by "is the file structure just coupled together everywhere".

How to deal with the difference in paths between different distros? There are fundamental lower level answers to this but perhaps that leads well into your question if you need the MPF.

  • Do I need MPF? If I have a git repo do I just copy MPF into it? What does MPF do for me? The very reason that I wanted to learn CFEngine is the simplicity and transparency of what it does, MPF seems to contradict that.

Categorically, I can tell you that you do not need the MPF. However, it does offer something to build off of and the policy is instrumented in the most common places so that you can tweak behavior without having to modify the vendored policy. Plus, there is often an assumption of use so if you are not using it it's good to highlight that fact. You can build a much leaner policy set, that covers just what you need. This goes back to dealing with differences in paths between repos and binaries.

The MPF didn't always exist. In the early versions of 3.x up to maybe 3.5 there wasn't much to graft on to (https://github.com/cfengine/core/tree/3.0.0/inputs). So, the questions from that period of time led to the structure of the Masterfiles Policy Framework MPF forming.

  • Is there any "practical" guide for CFEngine which is reasonably recent, and can guide me through building small-scale stuff from the grounds up. I'd appreciate a link.

I think the all time best way of learning CFEngine is live in person training.

The best book is Diegos Learning CFEngine (https://cf-learn.info/). It's not current, but all the basics are in there and I recall when the book came out wishing I had that book when I started learning CFEngine. I referred to things in it for many years.

Aleksey Tslalokhin published the content that he used for CFEngine training. You can use it as a self guided tutorial. https://www.softcover.io/read/72be7d4f/cf3/_single-page

I think that modern practical guides focus on leveraging the newer higher level tools that are available (https://docs.cfengine.com/docs/3.24/getting-started.html).

If you do choose to write your own policy, I will say that you might find cf-locate (https://github.com/cfengine/core/tree/master/contrib/cf-locate) useful. You could use it to lift out specific body and bundles from the standard library to keep your own policy set extremely minimal.

The follow-up from [2025-08-04 Mon] :

Got quick two questions: Can strings in CFEngine be split up into multiline without defining additional variables, and without adding unintended whitespaces? E.g. In C++ I'd do something like this: ``` const char* string = "This is my first line," " this is another line"; ``` Which evaluates to "This is my first line, this is another line"

Yep, you can.

bundle agent __main__
{
  vars:
      "s1" string => "Here
is
a
multiline string \
where I \
have escaped some \
but not other newlines.
";

      # Sometimes there is preference to use concat()
      "s2" string => concat("Here we",
                            " bring some strings",
                            ' together and show',
                            ` various quoting of`,
                            ` strings which contain quoteing chars "'"'`,
                            " handy tricks indeed to avoid escapes" );


  reports:
      "$(s1)";
      "$(s2)";
}
R: Here
is
a
multiline string where I have escaped some but not other newlines.

R: Here we bring some strings together and show various quoting of strings which contain quoteing chars "'"' handy tricks indeed to avoid escapes

Can I build my own bundles/functions which return values? I've seen that bundles can modify variables and define class in the parent scope, but that seems quite horrible. I mean something like this: ``` vars: "my_value" string => do_http_request("some arguments here"); ``` where "do_http_request" is some wrapper around Curl that I can write on my own, can't use url_get, because the binary on Alpine is compiled without curl support, and I would rather not bother with that, and instead do what I would do in other languages: abstract the mundane task of calling curl into something less repetitive.

Yes, it's possible to define your own bundles and bodies (it's quite expected that you will).

I recommend reading through the language concepts section of the reference manual (https://docs.cfengine.com/docs/3.24/reference-language-concepts.html). You can even define your own promise types (https://cfengine.com/blog/2020/introducing-cfengine-custom-promise-types/) but there is not currently any ability to define custom functions.

I think it's important to remember that bundles are not functions (https://docs.cfengine.com/docs/3.24/reference-language-concepts-bundles.html). Though you can use them in that way and they have the ability to return data into the calling bundle via reports promises using bundle_return_value_index (https://docs.cfengine.com/docs/3.26/reference-promise-types-reports.html#bundle_return_value_index) and useresult (https://docs.cfengine.com/docs/3.26/reference-promise-types-methods.html#useresult)

Here is an example:

bundle agent __main__
{
  methods:
      "Call my bundle with params and define a variable with returned data"
        usebundle => myBundle("KEY", "VALUE"),
        useresult => "returned_from_myBundle";

      "More ...."
        usebundle => myBundle("CFEngine", "Awesome"),
        useresult => "returned_from_myBundle";

      "Calculator"
        usebundle => add("5266", "42", "value"),
        useresult => "sum";

  reports:
      "CFEngine version: $(sys.cf_version)";
      "$(with)"
        with => storejson( returned_from_myBundle );

      "Calculated $(sum[value])";
}
bundle agent myBundle(key, value)
{
  reports:
      "$(value)"
        bundle_return_value_index => "$(key)";
}
bundle agent add(int1, int2, key)
{
  reports:
      "$(with)"
        with => int( eval( "$(int1)+$(int2)", "math", "infix" ) ),
        bundle_return_value_index => "$(key)";
}
R: CFEngine version: 3.27.0a.5553c81fe
R: {
  "CFEngine": "Awesome",
  "KEY": "VALUE"
}
R: Calculated 5308

Modules are another thing to know about (https://docs.cfengine.com/docs/3.26/reference-language-concepts-modules.html#top). There are a lot of different kinds of modules in CFEngine. There are the traditional variables and classes modules which allow you to define variables and classes from external programs via a simple line based protocol. You can use modules in different ways but primarily via usemodule() (https://docs.cfengine.com/docs/3.26/reference-functions-usemodule.html#top) or via commands with module => true (https://docs.cfengine.com/docs/3.26/reference-promise-types-commands.html#module). Then there are package modules which are external programs that communicate with CFEngine to implement packages promises, promise modules which are for defining custom promise types and CFEngine Build Modules (https://build.cfengine.com/) which are collections of policy or other CFEngine related content.

Nick Anderson

unread,
Aug 8, 2025, 9:54:12 PMAug 8
to Jakub Wardyn, help-cfengine

Hey Jakub,

I meant to ask but apparently forgot, when you say:

I've seen that bundles can modify variables and define class in the parent scope, but that seems quite horrible.

Can you point me to where you see this?

Modules have the power to define variables in any bundle scope of any namespace and can define namespace scoped classes in any namespace.

In policy there is a narrow case where you can define a variable in a remote bundle, but only if that bundle is not defined.

E.g:

bundle agent __main__
{
  vars:
      "remoteBundle.myvar" string => "Hello World";

  reports:
      "$(remoteBundle.myvar)";
}
R: Hello World

But, it's protected when there is actual policy defining the bundle:

bundle agent __main__
{
  vars:
      "remoteBundle.myvar" string => "Hello World";

  reports:
      "$(remoteBundle.myvar)";
}
bundle agent remoteBundle
{
      reports:
        "$(myvar)";
}
   error: Ignoring remotely-injected variable 'myvar'
   error: Remote bundle variable injection detected!
   error: Variable identifier 'remoteBundle.myvar' is not legal
   error: Promise belongs to bundle 'main' in file '/tmp/example.cf' near line 5
   error: Ignoring remotely-injected variable 'myvar'
   error: Remote bundle variable injection detected!
   error: Variable identifier 'remoteBundle.myvar' is not legal
   error: Promise belongs to bundle 'main' in file '/tmp/example.cf' near line 5
   error: Remote bundle variable injection detected!
   error: Variable identifier 'remoteBundle.myvar' is not legal
   error: Promise belongs to bundle 'main' in file '/tmp/example.cf' near line 5
   error: Remote bundle variable injection detected!
   error: Variable identifier 'remoteBundle.myvar' is not legal
   error: Promise belongs to bundle 'main' in file '/tmp/example.cf' near line 5
   error: Remote bundle variable injection detected!
   error: Variable identifier 'remoteBundle.myvar' is not legal
   error: Promise belongs to bundle 'main' in file '/tmp/example.cf' near line 5
   error: Remote bundle variable injection detected!
   error: Variable identifier 'remoteBundle.myvar' is not legal
   error: Promise belongs to bundle 'main' in file '/tmp/example.cf' near line 5
   error: Remote bundle variable injection detected!
   error: Variable identifier 'remoteBundle.myvar' is not legal
   error: Promise belongs to bundle 'main' in file '/tmp/example.cf' near line 5
   error: Remote bundle variable injection detected!
   error: Variable identifier 'remoteBundle.myvar' is not legal
   error: Promise belongs to bundle 'main' in file '/tmp/example.cf' near line 5
   error: Remote bundle variable injection detected!
   error: Variable identifier 'remoteBundle.myvar' is not legal
   error: Promise belongs to bundle 'main' in file '/tmp/example.cf' near line 5
R: $(remoteBundle.myvar)

Jakub Wardyn

unread,
Aug 13, 2025, 6:16:22 PMAug 13
to Nick Anderson, help-cfengine
Hello everyone,

Thank you for the clarifications, I think I also understand CFEngine a
bit better.

> Installing and bootstrapping to a hub and then figuring things out
> is totally possible. That's I think what most people do. By default
> the MPF doesn't really do much of anything.
Yeah well, I find it quite puzzling that CFEngine doesn't have this kind
of behavior out of the box, I feel like everything rests on this brittle
assumption that CFEngine is installed in /var/cfengine .

So from what I understand - I will have to modify the default MPF update
scripts, so that, on the agent, it somehow "calculates" the paths?

> I am not sure what you mean by "is the file structure just coupled
> together everywhere".
What I mean is that, with MPF by default, the installation path of
CFEngine needs to be the same in every agent.

> I think it's important to remember that bundles are not functions
Yes, I think what I really needed to see are the custom policies, that's
what I was looking for - a way to extend CFEngine.
For now I've gone with a more pragmatic approach of using relevant tools
that handle idempotency, like docker-compose and Terraform, we'll see
how it goes.

> I meant to ask but apparently forgot, when you say:
>> I've seen that bundles can modify variables and define class in
>> the parent scope, but that seems quite horrible.
> Can you point me to where you see this?
I think this is it:
https://docs.cfengine.com/docs/3.26/reference-promise-types-methods.html#usebundle

I've said it's "quite horrible" - that might be an exaggeration, it's
just weird coming from a programming background.

Regards,
Jakub Wardyn

Nick Anderson

unread,
Aug 13, 2025, 8:24:37 PMAug 13
to Jakub Wardyn, help-cfengine

Thank you for the clarifications, I think I also understand CFEngine a bit better.

Great.

Installing and bootstrapping to a hub and then figuring things out is totally possible. That's I think what most people do. By default the MPF doesn't really do much of anything.
Yeah well, I find it quite puzzling that CFEngine doesn't have this kind of behavior out of the box, I feel like everything rests on this brittle assumption that CFEngine is installed in /var/cfengine .

CFEngine being installed in /var/cfengine has been the standard for CFEngine since it's inception (way back in 1993) as far as I know, it's that way in the oldest docs at least (https://docs.cfengine.com/docs/archive/manuals/cf2-Reference.html#Work-directory). Interestingly enough, the beginnings of developing the file system hierarchy standard (FHS) was around the same time (https://refspecs.linuxfoundation.org/FHS_3.0/fhs/ch07s02.html)

So from what I understand - I will have to modify the default MPF update scripts, so that, on the agent, it somehow "calculates" the paths?

It's still not clear to me what paths exactly you are talking about.

There are paths that are involved in building CFEngine, for example between packages built by the maintainers of CFEngine (where everything is installed in /var/cfengine). Distributions will often build with FHS enabled. It's generally a-typical to run environments with a mix of FHS and non-FHS options. the MPF generally uses variables when referring to CFEngine's paths.

A more concrete example of what you mean might help me.

I am not sure what you mean by "is the file structure just coupled together everywhere".
What I mean is that, with MPF by default, the installation path of CFEngine needs to be the same in every agent.

It should not have to need to be. We recommend keeping things the same for simplicity.

Since that is not a configuration we test, you might indeed run into some issues. What are the specific errors?

I meant to ask but apparently forgot, when you say:
I've seen that bundles can modify variables and define class in the parent scope, but that seems quite horrible.
Can you point me to where you see this?
I think this is it: https://docs.cfengine.com/docs/3.26/reference-promise-types-methods.html#usebundle

I've said it's "quite horrible" - that might be an exaggeration, it's just weird coming from a programming background.

OK, yeah. CFEngine is certainly different, there is no doubt about that.

I remember being annoyed because when I started with CFEngine I really wanted the promiser to store the returned data something like:

"DEFINE_THIS_VAR" usebundle => child;

Anyway, I got over it I guess. I don't want that anymore, but I still see how it would be a desire.

Reply all
Reply to author
Forward
0 new messages