Getting Started with CFEngine: Practical Learnings and DevOps Best Practices

What is CFEngine?

CFEngine is a powerful open-source automation tool for IT infrastructure configuration management. It is lightweight, scalable, agent-based, and focuses on system compliance, security, and drift correction.

Core Concepts I Learned

  • Policy Files: The core configuration, typically stored in /var/cfengine/inputs/.
  • Bodies & Bundles: Bundles are like functions. Bodies define how bundles behave (e.g., body common control for global config).
  • Promises: The basic units of configuration—"promises" are statements of intent (e.g., “this file should exist and look like this”).
  • Classes: Dynamic variables that group hosts or behaviors. classes:: blocks execute conditionally.
  • Variables: Can be scoped globally, to a bundle, or to a class.

CFEngine Directory Structure

/var/cfengine/
├── inputs/      # Policy files (main config lives here)
├── outputs/     # Run-time outputs/logs
├── masterfiles/ # Canonical policy (symlinked in HA setups)
├── bin/         # CFEngine binaries (cf-agent, cf-serverd, etc.)

Basic Example: Hello World Policy

bundle agent hello_world
{
  reports:
      "Hello from CFEngine!";
}

Place this in /var/cfengine/inputs/hello.cf and run:
sudo cf-agent -Kf ./hello.cf

Understanding Classes and Conditionals

bundle agent classify_host
{
  classes:
      "is_prod" expression => regcmp(".*prod.*", "$(sys.fqhost)");
  reports:
    is_prod::
      "This host is a production server.";
    !is_prod::
      "This host is NOT a production server.";
}
  • Classes can be defined inside bundles for conditional execution.
  • Global classes (in body common control) set up defaults across the policy.

The is_prod class example above shows how to classify a server as “production” if its FQDN contains “prod.”

Special Notations & Tagging

  • Comments use #. #@ and @# can be used for special tagging but are not standard comments—mainly used for internal documentation or code generation tooling.
  • Tagging in lines like "tls_enabled" #security tagging is for human readability and does not affect code execution.

CFEngine Execution Flow

  • cf-agent is the main binary that applies policy files on clients.
  • When bootstrapping a new agent/server:
    sudo cf-agent --bootstrap <policy_server_ip>
  • Policy files are fetched and kept in sync from the policy server (HA setups often symlink /var/cfengine/masterfiles).
  • Bundles are called in sequence from bundle sequence in body common control:
    body common control
    {
      bundlesequence => { "hello_world", "classify_host" };
    }
    

Common Gotchas (from my journey so far)

  • Indentation and Quotes: Indentation is not enforced, but proper quoting and matching curly braces is crucial.
  • Variable Scope: Variable leakage does not occur—scopes are strictly enforced by bundle/class.
  • Promises outside bundles: Not allowed. Everything must be inside a bundle.
  • Classes outside bundles in body control: Use classes in body common control for global class definitions, but most class logic should go in bundles.

Sample Policy: File Management

bundle agent manage_ssh_config
{
  files:
    "/etc/ssh/sshd_config"
      perms => mog("600", "root", "root"),
      edit_line => replace_or_add("PermitRootLogin no");
}

This example: Ensures /etc/ssh/sshd_config permissions and disables root login.

Handy Tips

  • Test policies with cf-agent -KIf ./policyfile.cf before rolling out.
  • Use cf-promises --show-classes to debug classes and policy syntax.
  • Keep policy modular; use bundles to break logic into testable chunks.
  • Leverage the official docs — they are detailed, but the community wiki and mailing list help fill in gaps for advanced use.

What I Like About CFEngine

  • Extremely lightweight and efficient, perfect for large fleets
  • Agent-based, so works in offline/disconnected environments
  • Powerful and expressive policy language once you get past the learning curve
  • Good at self-healing (auto-remediation of drift)

Current Challenges / Open Questions

  • Debugging complex policy logic (sometimes cryptic error messages)
  • Less modern documentation and smaller community than Ansible/Puppet
  • Integration with cloud-native workflows (I am still exploring this part)

Next Steps in My Learning

  • Explore using CFEngine with version control and CI/CD pipelines
  • Write custom bodies and templates for reusable logic
  • Experiment with reporting and compliance dashboards

Conclusion

Learning CFEngine has been a great experience for understanding foundational concepts in configuration management. If you want a highly efficient, scalable solution—and are willing to learn its unique policy language—CFEngine is a fantastic tool. I will keep updating this post as I dive deeper, especially around automation, best practices, and troubleshooting real-world scenarios.

Further Reading