A deployment library for Haskell applications


Version on this page:
LTS Haskell 18.28:
Stackage Nightly 2021-06-14:
Latest on Hackage:

See all snapshots hapistrano appears in

MIT licensed by Justin Leitgeb
This version can be pinned in stack with:hapistrano-,5319

Build and Test Hackage version Docker Hub

Table of Contents


Hapistrano is a deployment library for Haskell applications similar to Ruby’s Capistrano.


We created Hapistrano because:

  • Deploys should be simple, but as close to atomic as possible (eg, they shouldn’t require much application downtime).
  • Rollback should be trivial to achieve to bring the application back to the last-deployed state.
  • Deploys shouldn’t fail because of dependency problems.

How it Works

Hapistrano (like Capistrano for Ruby) deploys applications to a new directory marked with a timestamp on the remote host. It creates this new directory quickly by placing a git repository for caching purposes on the remote server.

When the build process completes, it switches a symlink to the current release directory, and optionally restarts the web server.

By default, Hapistrano keeps the last five releases on the target host filesystem and deletes previous releases to avoid filling up the disk.


Hapistrano looks for a configuration file called hap.yaml that typically looks like this:

deploy_path: '/var/projects/my-project'
host: myserver.com
port: 2222
# To perform version control operations
repo: 'https://github.com/stackbuilders/hapistrano.git'
revision: origin/master
# To copy the contents of the directory
local_directory: '/tmp/my-project'
  - stack setup
  - stack build
restart_command: systemd restart my-app-service

The following parameters are required:

  • deploy_path — the root of the deploy target on the remote host.
  • Related to the source of the repository, you have the following options:
    • Git repository default — consists of two parameters. When these are set, hapistrano will perform version control related operations. Note: Only GitHub is supported.
      • repo — the origin repository.
      • revision — the SHA1 or branch to deploy. If a branch, you will need to specify it as origin/branch_name due to the way that the cache repo is configured.
    • local_directory — when this parameter is set, hapistrano will copy the contents of the directory.

The following parameters are optional:

  • host — the target host, if missing, localhost will be assumed (which is useful for testing and playing with hap locally).
  • port — SSH port number to use. If missing, 22 will be used.
  • shell — Shell to use. Currently supported: zsh ans bash. If missing, Bash will be used.
  • ssh_args — Optional ssh arguments. Only -p is passed via the port variable.
  • build_script — instructions how to build the application in the form of shell commands.
  • restart_command — if you need to restart a remote web server after a successful rollback, specify the command that you use in this variable. It will be run after both deploy and rollback.
  • vc_action - Controls if version control related activity should take place. It defaults to true. When you don’t want activity like cloning, fetching etc. to take place, set this to false.
  • linux - Specify, whether or not, the target system where Hapistrano will deploy to is a GNU/Linux or other UNIX (g.e. BSD, Mac). This is set to true by default so unless the target system is not GNU/Linux, this should not be necessary. The platform where Hapistrano is running won’t affect the available options for commands (g.e. A Mac deploying to a Ubuntu machine, doesn’t need this flag)
  • release_format - The release timestamp format, the ‘–release-format’ argument passed via the CLI takes precedence over this value. If neither CLI or configuration file value is specified, it defaults to ‘short’
  • keep_releases - The number of releases to keep, the ‘–keep-releases’ argument passed via the CLI takes precedence over this value. If neither CLI or configuration file value is specified, it defaults to ‘5’
  • linked_files:- Listed files that will be symlinked from the {deploy_path}/shared folder into each release directory during deployment. Can be used for configuration files that need to be persisted (e.g. dotenv files). NOTE: The directory structure must be similar in your release directories in case you need to link a file inside a nested directory (e.g. shared/foo/file.txt).
  • linked_dirs:- Listed directories that will be symlinked from the {deploy_path}/shared folder into each release directory during deployment. Can be used for data directories that need to be persisted (e.g. upload directories). NOTE: Do not add a slash / at the end of the directory (e.g. foo/) because we use parseRelFile to create the symlink.
  • run_locally:- Instructions to run locally on your machine in the form of shell commands. Example:
  - pwd
  - bash deploy.sh

Note how we are even able to execute a bash script named deploy.sh above. Be sure to use set -e in your bash script to avoid headaches. Hapistrano will stop the execution on non zero exit codes. Without the usage of set -e, there is a possiblity that your bash script may return a zero exit code even if your intermediate command resulted in an error.

After creating a configuration file as above, deploying is as simple as:

$ hap deploy

Rollback is also trivial:

$ hap rollback # to rollback to previous successful deploy
$ hap rollback -n 2 # go two deploys back in time, etc.

Environment Variables

Configuration files are parsed using loadYamlSettings, therefore, variable substitution is supported. Considering the following configuration file:

revision: "_env:HAPISTRANO_REVISION:origin/master

The revision value could be overwritten as follows:

HAPISTRANO_REVISION=origin/feature_branch hap deploy

What to do when compiling on server is not viable

Sometimes the target machine (server) is not capable of compiling your application because e.g. it has not enough memory and GHC exhausts it all. You can copy pre-compiled files from local machine or CI server using copy_files and copy_dirs parameters:

  - src: '/home/stackbuilders/my-file.txt'
    dest: 'my-file.txt'
  - src: .stack-work
    dest: .stack-work

src maybe absolute or relative, it’s path to file or directory on local machine, dest may only be relative (it’s expanded relatively to cloned repo) and specifies where to put the files/directories on target machine. Directories and files with clashing names will be overwritten. Directories are copied recursively.

Deploying to multiple machines concurrently

Beginning with Hapistrano it’s possible to deploy to several machines concurrently. The only things you need to do is to adjust your configuration file and use targets parameter instead of host and port, like this:

  - host: myserver-a.com
    port: 2222
  - host: myserver-b.com
# the rest is the same…

A few things to note here:

  • host item is required for every target, but port may be omitted and then it defaults to 22.

  • The deployment will run concurrently and finish when interactions with all targets have finished either successfully or not. If at least one interaction was unsuccessful, the hap tool will exit with non-zero exit code.

  • The log is printed in such a way that messages from several machines get intermixed, but it’s guaranteed that they won’t overlap (printing itself is sequential) and the headers will tell you exactly which machine was executing which command.

If you don’t specify host and targets, hap will assume localhost as usually, which is mainly useful for testing.


If you would like to use Docker, there is a lightweight image available on Docker Hub.


If you want to use Nix for building Hapistrano, the required release.nix and default.nix are available.

For installing the hap binary in your local path:

nix-env -i hapistrano -f release.nix

For developing Hapistrano with Nix, you can create a development environment using:

nix-shell --attr env release.nix

For just building Hapistrano, you just:

nix-build release.nix


MIT, see the LICENSE file.


Pull requests for modifications to this program are welcome. Fork and open a PR. Feel free to email me if you have questions about what may be accepted before working on a PR.

If you’re looking for a place to start, you may want to check the open issue.



  • Add support for GHC 9.0
  • Docker image is built on a newer compiler, cabal and alpine version


  • Official support for GHC versions older than 8.6


  • Add support for working directory


  • GHC support for versions older than 8.0. Bounds for base corrected


  • Bump path version upper constraint to 0.9


  • Allow formatting-7.0


  • Allow optparse-applicative-


  • Allow ansi-terminal 0.11


  • Support for GHC 8.10
  • Support for aeson-1.5


  • Allow time 1.10
  • Correct the package to reenable Hapistrano in Stackage


  • Copy a directory’s contents with local_directory instead of using git with repo and revision.


  • Update upper bounds for path and path-io packages.


  • Update Dockerfile and maintainer.


  • Colorize the output in the terminal.


  • Support for GHC 8.8
  • Support for ssh args in the config file.


  • Support for optparse-applicative-0.15
  • Replace deprecated function “withProcess” to “withProcessTerm” and add the version of “typed-process-” as extra dependency.


  • Update Docker base image from alpine:3.7 to alpine:3.9


  • Add timestamp to output commands:
[16:29:58,  2019-01-23 (-05)] INFO -- : $ find /tmp/hapistrano/releases/ -maxdepth 1 -type d


  • Support to deploy to a host that has default zsh shell.
  • Support to deploy using a different shell. Currently supported: zsh and bash.
  • linked_files and linked_dirs to link files and directories located in the {deploy_path}/shared/ directory.


  • execWithInheritStdout was added to System.Hapistrano.Core to stream output children’s to the parent’s stdout.


  • playScript and playScriptLocally use execWithInheritStdout to stream children’s stdout to parent’s stdout.

  • Read release-format and keep-releases from the configuration file.

  • Loose upper bound for yaml 0.11

  • Add support to interpolate ENV variables in a configuration file.
  • Add support for GHC 8.6.1
  • Loose constraint for stm-

  • Updated upper bound for yaml 0.10

  • Loose upper bound for path-io 1.4

  • Loose upper bound for yaml 0.9

  • Loose upper bound for aeson 1.4

  • Add Dockerfile

  • Adding tested compatibility with GHC 8.4

  • Support for temporary 1.3

  • Support for aeson 1.3

  • Loose uppers bounds for async

  • Standarize style
  • When showing version information also show git branch and commit

  • Add support for deploying to other Unix systems, besides GNU/Linux which didn’t supported all the flags that Hapistrano was using. See issue #63

  • Use git checkout instead of git reset to set the release revision

  • Correct bounds for base. GHC support for versions older than 7.10 was dropped on
  • Add run_locally to run user defined commands locally before deployment. Thanks to Sibi (GitHub: psibi) for this contribution

  • Allow time 1.8
  • Allow process 1.6

  • Allow path-io 1.3

  • Allow optparse-applicative 0.14

  • Add support for help in subcommands. Thanks to Vanessa McHale (GitHub: vmchale) for this contribution

  • Fix -v switch for hap. Thanks to Sibi (GitHub: psibi) for this contribution
  • Add vc_action to control version control related tasks. Thanks to Sibi (GitHub: psibi) for this contribution

  • Fixed a bug with repos not being fetched properly.
  • Implemented concurrent deployment to multiple hosts.
  • Now completion tokens are dropped automatically like old releases.

  • Reduced verbosity of some commands to make reading logs easier.
  • Restart command is now invoked after activation of new release (as it should).
  • Fix a typo in flag that specifies SSH port for scp.
  • Ensure that containing directories for files and directories to copy exist before invoking scp.

  • Add proper set of dependency version constraints.
  • Use optparse-applicative to parse arguments.
  • Allow to specify non-standard SSH port.
  • Drop support for GHCs older than 7.10 (because Chris Done’s path does not compile with them, see: https://github.com/chrisdone/path/issues/46).
  • Now Hapistrano uses hap.yaml file for all its configuration.
  • Added the ability to copy arbitrary files and directories verbatim from local machine to target host.

  • Add change log (#23).
  • Add README.md to extra source files.
  • Handle missing environment variables more graciously.
  • Allow GHC 8 and base 4.9.

  • Fix tests (#31).


  • Use Stack (#17).
  • Clean up package (#20).
  • Fix tests (#25).

  • GHC 7.10 support.

  • Refactoring and documentation improvements.

  • Various refactoring and relaxed dependency constraints.

  • Print error messages to stderr, return non-zero exit code on failure.

  • Initial release.