Shipping_containers_at_ClydeNot too soon after people start using Atomic images, the question of customization soon follows. It is a natural progression for most people when they use Atomic. There are a number of different ways to accomplish using custom images not withstanding using docker and containers. The Atomic tool called 'rpm-ostree-toolbox' is emerging as the best tool for customizing Atomic.

The 'rpm-ostree-toolbox' main command is actually a wrapper (much like virsh) for three subcommands: treecompose, imagefactory, and installer.  With these three subcommands, you can create:

  • a custom Atomic tree
  • customized disk images (qcow2 and the like) based on your tree
  • install media (ISO) that installs your tree

In this first of two blogs, I will discuss how to set up the rpm-ostree-toolbox tooling, create a standard (default) tree, and then create a custom tree.  Then I will share some common commands that can be used to inspect your tree.  And finally, we will boot a generic CentOS7 Atomic image and consume our custom tree. I will be using a standard Fedora 21 image to perform all of these actions.

Setting up the required tools

To install the required packages, we simply need to install 'rpm-ostree-toolbox' and 'pykickstart'.

[baude@localhost ~]$ sudo yum install pykickstart rpm-ostree-toolbox

Obtaining the CentOS Atomic git repository

The rpm-ostree-toolbox tooling uses a variety of configuration files to drive its output.  These files are kept in a git repository which provides almost all the default values you will need to create the outputs you need.  The git repository is at:

To obtain these, simply issue a:

[baude@localhost ~]$ git clone

Reviewing the configuration files for CentOS

There are several key files in the buildscripts git repository that warrant a brief review and explanation.

  • config.ini
  • JSON files
  • centos-atomic-7.tdl
  • REPO files


The config.ini file is the key control file for rpm-ostree-toolbox itself.  For example, it defines the OS, version, where input and output files are, and which JSON files are to be used. The rpm-ostree-toolbox commands are designed to be run against an unchanged git repository like the one one we cloned earlier.  Therefore, to build, a standard or default tree, you should not have to change the config.ini file at all.  Once you begin to build customized trees, you will need to either edit the config.ini or create a new one.

To familiarize yourself with the config.ini file and its values, it is best to open and review it.  It is heavily commented and self-explanatory.

JSON files

The two important JSON files in the git repository, for our purposes, is centos-atomic-base.json and centos-atomic-host.json.  In fact, the latter includes the first one.  These two files describe the parts that go into making trees, images, and installer media.  For example, it lists which yum repositories should be used, which RPMs will make up the package manifest, and the name of the OS among other things.


This file is used by the imagefactory subcommand and is a required piece of input. The key value in the tdl file points to a yum repository which includes the required packages for an Atomic install tree.

REPO files

In the CentOS repository, you will also find several .repo files.  These repo files can be and are consumed in each of the rpm-ostree-tool subcommands.  They direct the tooling where to look for packages that are needed throughout the entire process.  The repo files are also listed in the JSON files so that they are included.  If you need to add a custom repository, add it to the JSON file and make a respective .repo file.  One note, each repo file may only list one repository.


Composing a tree

As mentioned earlier, rpm-ostree-toolbox consists of three subcommands.  The first is called treecompose.  You can see these subcommands with the --help switch.

[bbaude@localhost ]$ sudo rpm-ostree-toolbox --help
  treecompose - Use rpm-ostree to compose RPMs to a tree
  imagefactory - Use ImageFactory to create a disk image
  installer - Use Lorax to create an installable ISO and PXE boot loader

For our purposes, this is the first command you should run when customizing Atomic.  In turn, this tree content can be consumed by an existing CentOS Atomic image, used to create a new CentOS Atomic image, or used to be embedded into a CentOS Atomic installer.

We can also get help on the subcommand by passing the subcommand and the --help switch.

[bbaude@localhost ]$ sudo rpm-ostree-toolbox treecompose --help
  usage: rpmostreecompose-main [-h] -c CONFIG [--ostreerepo OSTREEREPO]
                               [-p PROFILE] [-V VERSIONING] [-v]
  Compose OSTree tree
  optional arguments:
  -h, --help show this help message and exit
  -c CONFIG, --config CONFIG
      Path to config file
  --ostreerepo OSTREEREPO
      Path to OSTree repository (default: ${pwd}/repo)
  -p PROFILE, --profile PROFILE
      Profile to compose (references a stanza in the config file)
      Version to mark compose
  -v, --verbose verbose output

The minimum input for a treecompose is to point to the config.ini it should use and provide a --ostreerepo argument.  Putting ostree content under /srv is somewhat or a standard so I will define the --ostreerepo argument as /srv/rpm-ostree/centos-atomic-host/7/.  With these two pieces of information, we are ready to create our first tree:

 [bbaude@localhost ]$ sudo rpm-ostree-toolbox treecompose -c /home/baude/sig-atomic-buildscripts/config.ini --ostreerepo /srv/rpm-ostree/centos-atomic-host/7/

The treecompose process will now begin.  First it parses all the input files and then downloads the required RPMs that will end up in the tree.  It will then end up installing those RPMs into the tree and running a SELinux relabel on it.

Customizing the tree

One of the motivations for making your own tree is so that you can customize it.  And in following through with this example, I am going to add vim to my tree. There is no defined, concrete process on how you should customize your trees.  You could simply edit the centos-atomic-host.json file and add 'vim' to the packages list.  But I have found that my best practice for this actually is an opportunity for Atomic to shine.  A single Atomic repository can house more than one tree.  The trees are referred to by their ref.  A ref is simply the name of a tree and is somewhat akin to a branch in git.

My recommendation is to leave the default ref and create a new ref that refers to your tree in the same repo.  This can be done by creating a new treefile for our custom tree.

[bbaude@localhost ]$ cat sig-atomic-buildscripts/centos-atomic-host-vim.json
 "include": "centos-atomic-host.json",
 "packages": ["vim"]

Note in the new json file above (centos-atomic-host-vim.json), we have added two simple keys and values.  Because our new treefile is considered to be complimentary to the tree we have already made, we include centos-atomic-host.json, This means all the packages and values in that and subsequent include files will be honoured.  And rpm-ostree-toolbox will also amend the package list, which here only includes vim, to the other packages described in the included json files.

The best way to implement calling this new tree file is to create a new profile in our config.ini. As an example, consider this new vim profile I have added to the config.ini:

tree_name = vim
ref = %(os_name)s/%(release)s/%(arch)s/%(tree_name)s
tree_file = %(os_name)s-vim.json

Note here how I define the tree_name as vim.  It was standard in the our default tree compose.  As a result, the ref will end in /vim.  And finally, the tree_file definition points to our new tree file we defined earlier.

Be sure to watch the format of the json file and adhere to it very closely.  Once you have completed your change, you can recompose the tree with the same command we used earlier but this time we add -p vim to build for the vim profile:

[baude@localhost]$ sudo rpm-ostree-toolbox treecompose -c /home/baude/sig-atomic-buildscripts/config.ini --ostreerepo /srv/rpm-ostree/centos-atomic-host/7/ -p vim

Inspecting a composed tree

Once your second tree compose completes, there a couple of handy commands you can use to probe and inspect them.   Most of these commands need two pieces of information about your tree:

  • repo -- where your composed tree is (/srv/rpm-ostree/centos-atomic-host/7/repo/)
  • ref -- what is the ref name of the tree.  Refs are similar to a name of a tree and point to a generated file system tree.

We can determine the ref from a composed tree using the ostree command like so:

[baude@localhost ]$ ostree refs --repo=/srv/rpm-ostree/centos-atomic-host/7/

Note how we see two refs in our repo.  The standard ref is the default (unchanged) tree we created, whereas the vim ref is our custom tree.

Suppose we want to list which RPMs were actually used to create the tree.  The manifest in the JSON files are only the packages you wanted included but this does not include the RPM dependencies that were also resolved.  Or in our case, perhaps we want to verify that vim was truly added.  We can use rpm-ostree to perform that query:

[baude@localhost ]$ rpm-ostree rpm --repo=/srv/rpm-ostree/centos-atomic-host/7/ list centos-atomic-host/7/x86_64/vim | grep vim

You can also peek into what the composed tree would like when extracted out.  This is handy for looking for files or directories within the tree compose.  For example, if you want to see what is in /boot we can use ostree to get that information:

[baude@localhost ]$ ostree ls --repo=/srv/rpm-ostree/centos-atomic-host/7/ centos-atomic-host/7/x86_64/vim /boot
d00555 0 0 0 /boot
-00644 0 0 170 /boot/.vmlinuz-3.10.0-123.9.3.el7.x86_64.hmac
-00600 0 0 2841475 /boot/
-00644 0 0 122093 /boot/config-3.10.0-123.9.3.el7.x86_64
-00600 0 0 6832770 /boot/initramfs-3.10.0-123.9.3.el7.x86_64.img-ae094c0414df1b83583362a328716dec3af53dbc3df00adc797a4a1586c1063d
-00644 0 0 228621 /boot/symvers-3.10.0-123.9.3.el7.x86_64.gz
-00755 0 0 4905984 /boot/vmlinuz-3.10.0-123.9.3.el7.x86_64-ae094c0414df1b83583362a328716dec3af53dbc3df00adc797a4a1586c1063d
d00755 0 0 0 /boot/efi
d00755 0 0 0 /boot/grub
d00755 0 0 0 /boot/grub2


You can also query the tree to understand its history with ostree and its log command.  This, much like git, will provide you with commit IDs that you can use to look at delta history of an image.  Here I will query both the standard and vim refs.

[bbaude@localhost ]$ ostree log --repo=/srv/rpm-ostree/centos-atomic-host/7/ centos-atomic-host/7/x86_64/standard
commit 205377fc9b152918cd20fd21b1c228f1b7a08d9e2ab20dfb6df81a7d269055c6
Date: 2014-12-17 20:26:00 +0000

[bbaude@localhost ]$ ostree log --repo=/srv/rpm-ostree/centos-atomic-host/7/ centos-atomic-host/7/x86_64/vim
commit c0498ec94732b35254f39df5c5a6805369b7f42601a745b0f9c6a06dac3804ff
Date: 2014-12-17 20:34:35 +0000

With the two commit IDs, we can now get a diff of two trees.  Diff output from ostree can be very verbose and lengthy, which really is the point.  So in this case, I will just grep for 'vim' and only pipe out the first 10 lines using head.

[bbaude@localhost ]$ ostree diff --repo=/srv/rpm-ostree/centos-atomic-host/7/ 205377fc9b152918cd20fd21b1c228f1b7a08d9e2ab20dfb6df81a7d269055c6 c0498ec94732b35254f39df5c5a6805369b7f42601a745b0f9c6a06dac3804ff | grep vim | head -n10
A /usr/bin/rvim
A /usr/bin/vim
A /usr/bin/vimdiff
A /usr/bin/vimtutor
A /usr/etc/profile.d/vim.csh
A /usr/etc/profile.d/
A /usr/etc/vimrc
A /usr/share/doc/vim-common-7.4.160
A /usr/share/doc/vim-common-7.4.160/Changelog.rpm
A /usr/share/doc/vim-common-7.4.160/LICENSE

Here we can see that vim was indeed added to our custom tree.

Upgrading to our custom tree

To now consume and use our custom tree, we will need a test image that we can boot.  Note that CentOS recently announced CentOS7 Atomic images, grab an image from there and boot it up. Once booted, the first thing we need to do is somehow serve our new tree content from the host (where the tree is composed) to our test CentOS Atomic guest.

The ostree application comes with a subcommand that can start a simple httpd server that is more than capable.  The subcommand is called trivial-httpd and will run on a random port when started.  On the host, you must provide trivial-httpd with the repo you want served:

[baude@localhost ]$ ostree trivial-httpd -p - /srv/rpm-ostree/centos-atomic-host/7/

Author's Note: You should use a production webserver for you custom trees if you are doing more than development oriented work.  One key advantage of using something like Apache is that you will not be dealing with port assignments.

The CentOS7 image I referenced earlier is booted in a KVM guest on my laptop.  We can check that the communication between the trivial-httpd and my guest is working using curl, the IP of where the trivial-httpd is running and the port number that was assigned:

[centos@atomic-baude ~]$ curl

Author's Note: Firewalld will block this communication by default.  You can add a custom rule to allow it or take a different action.

The successful curl indicates the communication is working.  The next step is to make our CentOS7 image (guest) aware of the remote tree we have built.  This is done with the ostree command on the guest image.  You should substitute the IP address of the system hosting your content and the port number given to you by trivial-httpd.

[centos@atomic-baude ~]$ sudo ostree remote add withvim --no-gpg-verify

The addition of that tree (or repo) is now stored in /etc/ostree/remotes.d/ on the CentOS guest.  If you need to delete the repo, you just delete the file in that directory associated with your remote add.  We can now rebase our guest with our new tree using the rpm-ostree command with the rebase subcommand.

[centos@atomic-baude ~]$ sudo rpm-ostree rebase withvim:centos-atomic-host/7/x86_64/vim
Receiving objects: 36% (146/403) 7.3 MB
[centos@atomic-baude ~]$

The content from our custom tree has now been put onto our guest.  The references to vim in the RPM list is a good hint.  But note, it is not available yet until we reboot the system.  For proof, let's try to run vim.

[centos@atomic-baude ~]$ vim
-bash: vim: command not found

It clearly is not there.  To make the rebase take effect, we reboot.  When you reboot your image, you may notice in the bootloader two entries (where only one was present earlier).  The image ending in -1 is the initial or original image and the one ending in -0 is our new tree.  Once the guest is booted up, we can check for vim again.

[centos@atomic-baude ~]$ file /usr/bin/vim
/usr/bin/vim: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.32, BuildID[sha1]=0x530e3b1a3084847f35bfddc8527f432c028b7ca0, stripped

And there is clear proof it is now present.

Wrap Up

This concludes the part 1 of customizing and creating Atomic builds.  Reviewing what we have learned, you should now be able to:

  • Build the required tools to customize Atomic
  • Obtain the control files from the CentOS git repository
  • Compose a customized tree
  • Inspect and explore your new tree
  • Rebase an image to your new tree

In part 2 of the blog, we focus on creating custom disk images and isos.


Last updated: April 5, 2018