Skip to main content

Running Chef Habitat Linux Containers

When you run hab pkg export docker, you’ll get a Docker container that provides a few things. First, a minimal Linux OS filesystem is provided, with just enough configuration (e.g., /etc/passwd, /etc/resolv.conf, etc.) to run. Second, the contents of the exported Chef Habitat package, along with its complete dependency tree, as well as a complete Chef Habitat Supervisor installation, are provided, unpacked, in the /hab/pkgs directory. Finally, an entry-point script that will start the Supervisor, running the exported Chef Habitat package, is provided, allowing the container itself to behave as though it were the Supervisor.

On Linux, the Chef Habitat Supervisor will normally run as the root user, and will start Chef Habitat services as the pkg_svc_user specified by that service. However, in some deployment scenarios, it undesirable–or even impossible–to run the Supervisor as root. The OpenShift container platform, for instance, does not run containers as root by default, but as randomly-chosen anonymous user IDs. From version 0.53.0 onward, the Chef Habitat Supervisor can now run as an arbitrary user, providing users with more flexibility in how and where the use Chef Habitat.

In order to support this in containers and provide maximal flexibility, the contents of the /hab directory are both readable and writable by the root group. When specifying a user to run a container process as, the user’s primary group will be reported as root if no matching group can be found in /etc/group. This will allow the user to create and populate the /hab/sup directory for managing Supervisor state, as well as the /hab/svc directory, which will contain all the service’s state. This is in line with recommendations from OpenShift on how to create containers that can run as a non-root user, but nothing in Chef Habitat’s implementation is specific to OpenShift; indeed, all the examples provided below use pure Docker.

Caveats to Running as a Non-root User

“There’s no such thing as a free lunch”, as the saying goes, and that holds true here. If the Supervisor is running as a non-root user, any processes that it supervises will be run as the same user; any values that the process might specify via pkg_svc_user and pkg_svc_group are essentially ignored. Furthermore, any files written out by the service during its operation are also owned by that same user.


Actually, the Supervisor does not care what user it is running as; rather, it uses Linux capabilities to guide its behavior. If the process has the CAP_SETUID, CAP_SETGID, and CAP_CHOWN capabilities, it will be able to run processes as the specified pkg_svc_user and pkg_svc_group (CAP_CHOWN is needed to ensure that the service processes can read and write files within the service’s state directories). The Supervisor checks for the presence of these capabilities, and does not rely on having a user ID of 0 or the username root.

Container Deployment Scenarios

Running a Chef Habitat Container as a Root User

This is the base case. If you are fine with running your container as root, you can do that directly:

docker run --rm -it core/redis:latest

Here, core/redis:latest would be the image exported from the core/redis Chef Habitat package. The Supervisor will run as normal, with supervised processes running as the desired user.

Running a Chef Habitat Container as a Non-root User

If you cannot run as the root user, but you are fine with root being the container user’s primary group, you can simply specify a user ID to run as. This user need not exist in the container itself, and it’s better if it doesn’t. Using pure Docker, it might look like this:

docker run --rm -it --user=888888 core/redis:latest

Again, we use our core/redis Chef Habitat package container; the user ID 888888 is simply a number chosen at random (this is how platforms like OpenShift operate). No user inside the container has this ID, meaning that the user will be an anonymous user with root as its primary group. Because of how we generate Chef Habitat containers, this fact ensures that the user has write permissions within the /hab directory.

Due to the current logic around package installation, there is an extra step needed if you would like to have your containerized Supervisors update either themselves or the services they supervise. When installing packages as a non-root user, Chef Habitat will download the origin keys and compressed hart files into the user’s ${HOME}/.hab directory, rather than the global /hab/cache directory. You will need to ensure that a user-writable directory is mounted into the container, and specify it as the user’s home directory using the HOME environment variable. Using pure Docker with a volume that is accessible by the user, that might look like this:

docker volume create --driver local \\
       --opt type=tmpfs \\
       --opt device=tmpfs \\
       --opt o=size=100m,uid=888888 \\

docker run --rm -it \\
       --user=888888 \\
       --mount type=volume,src=test_home,dst=/myhome \\
       --env HOME=/myhome \\
       core/redis:latest --auto-update --strategy=at-once

This is merely an illustration; use whatever volume management approaches and service update strategies that are appropriate for your container scheduling system and your local deployment.

As illustrated, updates of this kind are completely optional; you may prefer to move update responsibility to your container scheduler and treat your containers as immutable in this regard.

Running a Chef Habitat Container as a Non-root User in a Non-root Group

If for whatever reason you do not want your user to be in the root group inside the container, you will need to add some additional volumes in order to create the needed directories. However, since you will (by definition) not have write permissions on the /hab directory as a whole, your Supervisor will not be able to update either itself or the services it supervises.

To implement this using pure Docker, you could do something like this (the group ID of 999999 was again chosen arbitrarily, as with the user ID):

docker volume create --driver local \\
       --opt type=tmpfs \\
       --opt device=tmpfs \\
       --opt o=size=100m,uid=888888 \\

docker volume create --driver local \\
       --opt type=tmpfs \\
       --opt device=tmpfs \\
       --opt o=size=100m,uid=888888 \\

docker volume create --driver local \\
       --opt type=tmpfs \\
       --opt device=tmpfs \\
       --opt o=size=100m,uid=888888 \\

docker run --rm -it \\
       --user=888888:999999 \\
       --mount type=volume,src=sup_state,dst=/hab/sup \\
       --mount type=volume,src=svc_state,dst=/hab/svc \\
       --mount type=volume,src=launcher_state,dst=/hab/launcher \\

Again, this is just an illustrative example; use the appropriate strategies for your specific circumstances. The key information here is to ensure that the /hab/sup, /hab/svc, and /hab/launcher directories are writable by the user inside the container.

Edit this page on GitHub

Thank you for your feedback!


Search Results