Handle the state of containers in a Docker compose configuration

Posted by Craig Comstock
December 2, 2024

Recently we had a Fireside Chat with long-time contributor and CFEngine Champion Bas Van der Vlies. During that talk he mentioned a Build module he developed: promise-type-docker-compose.

For this month’s Module Monday post I thought I would take this promise type for a spin alongside the Docker Compose Quickstart tutorial.

Setup

For this blog I brought up a libvirt vagrant VM with Debian 12 and installed the latest LTS (3.24.0) with cf-remote. To install docker I follow the instructions at Install Docker Engine. I was using Debian 12 “bookworm” and found that the default packages docker.io and docker-compose are rather old and were not compatible with the promise-type-docker-compose module. Additionally, the module relies on the jq utility, so I install that as well. I may write policy some time to take care of these dependencies but especially the Docker Engine install involves quite a few steps so will leave that for another time.

Build project

After installing those dependencies I create a blank CFBS project.

$ pipx install cfbs
$ mkdir play-cfbs
$ cd play-cfbs
$ cfbs init
# then answer some questions...

Then add the promise type module:

$ cfbs add promise-type-docker-compose
Added module: library-for-promise-types-in-bash (Dependency of promise-type-docker-compose)
Added module: promise-type-docker-compose
The default commit message is:

        Added 2 modules

         - Added module 'promise-type-docker-compose'
         - Added module 'library-for-promise-types-in-bash'

Edit it? [yes/y/NO/n] 
Committing using git:

[promise-type-docker-compose bcbffee] Added 2 modules
 1 file changed, 28 insertions(+)

I copy/paste the various files from the tutorial.

$ mkdir composetest
$ cat > composetest/app.py
$ cat > composetest/requirements.txt
$ cat > composetest/Dockerfile
$ cat > composetest/compose.yaml

Policy

I use the example policy from the module README to bring up the app.

main.cf
bundle agent composetest
{
  docker_compose:
    "${this.promise_dirname}/compose.yaml"
      state => "start";
}

I add this composetest directory to my cfbs project with the cfbs add <directory> command. Adding a directory with cfbs causes all of the files in that directory to be copied into the built policy as well as specific files in the directory being handled in a reasonable way:

  • Copy any .cf policy files recursively and add their paths to def.json’s inputs.
  • Enable services_autorun_bundles class in def.json.
  • Merge any def.json recursively into out/masterfiles/def.json.
  • Copy any other files with their existing directory structure to destination.
$ cfbs add ./composetest
Which bundle should be evaluated (added to bundle sequence)?
 1. ./composetest/main.cf:composetest (default)
 2. (None)
 [1/2]: 
Added module: ./composetest/
The default commit message is 'Added module './composetest/'' - edit it? [yes/y/NO/n] 
Committing using git:

[promise-type-docker-compose f439e31] Added module './composetest/'
 1 file changed, 11 insertions(+)

Notice that cfbs add detects that my main.cf policy file has a bundle named composetest which I agree to add as a bundle that should be included in my policy evaluation.

One little snag we hit here is in the way that policy is copied and then where it is used by the default Masterfiles Policy Framework update policy. The files in our composetest are indeed part of the built policy but only certain files are copied into /var/cfengine/inputs where cf-execd and cf-agent periodically run the policy from. Because of this we need to add an extra pattern of what files to copy to inputs. Luckily there is an MPF extension available for just this purpose: Extend files considered for copy during policy updates. As mentioned before we can add a def.json to our composetest directory and the cfbs directory command will add any augments in that file to the main def.json in the resulting policy.

./composetest/def.json
{
  "variables": {
    "default:update_def.input_name_patterns_extra": {
      "value": [ ".*\\Dockerfile" ],
      "comment": "Dockerfile or *.Dockerfile should be copied so that promise-type-docker-compose works when referencing this.promise_dirname/*.yaml files."
    }
  }
}

I push my changes up to a branch on a git repo. Then I visit Mission Portal and configure a Build project to use this repository.

After clicking on “Push and Deploy” I wait a few agent cycles (5-15 minutes) and check that the app is running. And indeed it is!

browser showing docker compose tutorial app working

The complete Build project for this blog is available in the promise-type-docker-module branch of our example projects repo: play-cfbs. Take a look at all the branches for other example projects.

Questions?

If you have questions or need help, reach out on the mailing list or GitHub discussions. If you have a support contract, feel free to open a ticket in our support system.