Let’s say you have a staging environment that usually has the latest commit of the master branch deployed. Sometimes though you might want to stage multiple branches at the same time.

The staging environment is at staging.example.com and we want to be able to have any feature branch available at mybranch.staging.example.com.

We don’t want to automatically deploy every feature branch every time a new commit is pushed, so deployment is still done manually.

In this example we’re using Laravel Envoyer, Apache, and a PHP Symfony 2.x project.

Current setup

The basic server directory structure for the staging environment is currently something like below.

# Before

/var/www/staging.example.com/

current -> releases/20161231190018

releases/

20161231171425/

20161231181915/

20161231190018/

...

Apache has a basic VirtualHost configuration as follows.

Apache configuration for staging.example.com

When deploying, a new release directory is created, caches are warmed up, the current symlink is pointed at the new release, and then old releases are cleaned up. All handled by Envoyer.

New setup

In this case we want to preserve this behaviour when master is being deployed, but do something different if it’s a feature branch. The directory structure when we have deployed a couple of feature branches should look something like this.

# After

/var/www/staging.example.com/

branches/

another-feature/

my-feature-branch/

current -> releases/20161231190018

releases/

20161231171425/

20161231181915/

20161231190018/

...

Configuring Apache

Using some VirtualHost magic in Apache we can make this work dynamically.

Dynamic Apache configuration for *.staging.example.com

The VirtualDocumentRoot directive makes it possible to use the server name, or parts of it, to dynamically configure the document root. To make this useful we need to first set UseCanonicalName Off which will make Apache use the Host-header as the server name. Then we can use %1 in the VirtualDocumentRoot directive to reference the first part of the server name (%0 is the full Host header, %1 is the part before the first dot, etc.)

Using mod_rewrite we set the environment variable SUBDOMAIN to the same first part of the Host header, so we can reference it in ProxyPassMatch with the interpolate option, in combination with ProxyPassInterpolateEnv On.

Deploy hooks

We’ll add some deploy hooks before and after the Activate New Release step, which is when the current symlink is pointed to the new release directory.

The before hook is pretty straight-forward; the important part is we save the current symlink so that we can restore it later.

The {{branch}} variable seems to be undocumented, but it works.

Add this hook before Activate New Release

The after hook is where we do the heavy lifting. Make sure to configure Envoyer to not restart php-fpm, since we need to delay that until after we have restored the current symlink.

Add this hook after Activate New Release

First, we restore the current symlink so that staging.example.com is still running the same release as before. Then we move the release we just deployed to the branches directory with the name of the branch. We can’t keep the release in the releases directory, because then it might be cleaned up by Envoyer, or cause another deployed release to be cleaned up.

We don’t keep release history and a current symlink for each branch, since we’re not that concerned with atomic deploys and quick rollbacks for this use case.

Finally we do some cache warmup (since we moved the cache some paths might need to be updated), and reload php-fpm.