Habitat Core/PHP, The Story Of The PR

by Paul Adams

It turns out that packaging PHP applications for Habitat really is a little fiddly. Here is my work-in-progress for Drupal.

Today I finally got around to submitting my first PR for 2018. It's part of a greater plan that I am cooking around Habitat; the re-implentation of the core/drupal package. That's a story for another day. Today I wanted to write a little about this PR and its part in the greater picture.

The Existing Core/PHP Package

The current implementation of core/php is a pretty simple, plan-only affair:

Click here to see the plan.
pkg_maintainer="The Habitat Maintainers <humans@habitat.sh>"
pkg_description="PHP is a popular general-purpose scripting language that is especially suited to web development."
pkg_bin_dirs=(bin sbin)

do_build() {
  ./configure --prefix="$pkg_prefix" \
    --enable-exif \
    --enable-fpm \
    --with-fpm-user=hab \
    --with-fpm-group=hab \
    --enable-mbstring \
    --enable-opcache \
    --with-mysql=mysqlnd \
    --with-mysqli=mysqlnd \
    --with-pdo-mysql=mysqlnd \
    --with-readline="$(pkg_path_for readline)" \
    --with-curl="$(pkg_path_for curl)" \
    --with-gd \
    --with-jpeg-dir="$(pkg_path_for libjpeg-turbo)" \
    --with-libxml-dir="$(pkg_path_for libxml2)" \
    --with-openssl="$(pkg_path_for openssl)" \
    --with-png-dir="$(pkg_path_for libpng)" \
    --with-xmlrpc \
    --with-zlib="$(pkg_path_for zlib)"

do_install() {

  # Modify PHP-FPM config so it will be able to run out of the box. To run a real
  # PHP-FPM application you would want to supply your own config with
  # --fpm-config <file>.
  mv "$pkg_prefix/etc/php-fpm.conf.default" "$pkg_prefix/etc/php-fpm.conf"

do_check() {
  make test
  • Preamble
  • Dependencies
  • Build / install instructions
  • Run some tests

No rocket science here. But one major assumption: that the user wants to import PHP as a library, rather than run PHP-FPM as a service.

I want to run PHP-FPM as a service. Or, at least, I want others to run PHP-FPM as a service when they want to. Thankfully, Habitat allows me to easily service-ify1 an existing package without disturbing the existing users of that package2.

The PR Content

So let's start with the obvious change I needed to make to the plan.sh for core/php: adding the instruction for how to run PHP-FPM as a service.

pkg_svc_run="php-fpm --fpm-config ${pkg_svc_config_path}/php-fpm.conf -c ${pkg_svc_config_path}"

Here I provide the name of the script to run and its required options. The -c might be3 completely superfluous in the presence of the --fpm-config option. So now, for anyone invoking hab start core/php they will get a running PHP-FPM service. Right? Wrong. This is only the beginning of our servicification4.

Some Other Changes To Plan.sh

So what else needs to be done before this is a healthy service? A couple of things:

  • We should expose the port that is being used;
  • libreadline needs to be shifted from being a build dep to a runtime dep5;
  • We can do-away with the the whole do_install() function.


That do_install() served only one purpose, to put the default (is provided during the build) PHP-FPM configuration in situ after the build is completed. The problem here is that the Habitat Supervisor is not in control of this config. So let's Habitat the heck out of this.

Starting, of course, with completely removing that do_install()6.

A More Habitat-y Configuration

Hopefully you noticed that the plan.sh pkg_svc_run directive7 included the ${pkg_svc_config_path} helper8. To successfully provide this configuration, I only need to do two things:

  • Drop a template for PHP-FPM into my build's /config folder;
  • Create a default.toml to include default values for the most meaningful configuration options9.

I won't bore you with the details of PHP-FPM configuration. Gratuitous Googling tells me that the options I have provided for are the most important in this case.

One. Last. Thing.

PHP-FPM is run as hab:hab and we need to give that user the permission to write its logs into ${pkg.svc_var_path}. This is easy enough to achieve with an init hook which creates ${pkg.svc_var_path}/log and chown's it.


So there you have it, the anatomy of a PR. And, in particular, how you can easily turn a library into a service. I'm really not sure how common this use case is10.

As I write this, the PR is not yet merged. However, if/when it is merged, all downstream packages based on core/php should be unaffected and all new packages can make use of core/php as a service: hab start core/php.

So now onto a new core/drupal which will do just that. Hopefully.


  1. This is totally a word, right?
  2. Too much.
  3. Probably is.
  4. If we accept "service-ify", we accept "servicification", OK?
  5. Something I discovered only after I had loudly declared "Aha! I'm done!" in the office.
  6. I love removing code. Maintainance is way more costly that development.
  7. I totally made this up. I have no idea what to call this.
  8. Yup, I made up this name, too.
  9. The "Habitat Way" is to keep core/* package configuration small and meaningful. Not overly verbose.
  10. Not very.