Quad CI

Quad CI is a simple, tiny and beginner friendly Continuous Integration system written in Haskell.


  • sandboxed builds in docker containers
  • multi-node architecture with agents picking up jobs to work on
  • http api to interact with the frontend and other nodes
  • support for triggering builds with github webhooks

All in 1K lines of code!

📼 Watch the Intro video (~2 minutes).

Getting Started

# Needed for RecordDotSyntax
$ stack install record-dot-preprocessor

# Run server
$ stack run -- start-server

# Run agent
$ stack run -- start-agent

Try running a simple build:

$ curl -X POST -H "Content-Type: application/json" -d \
@test/github-payload.sample.json "http://localhost:9000/webhook/github"

Quad CI comes with a web UI, which can be accessed at http://localhost:3000. To install it, run the following:

cd frontend/
yarn next


This project tries to answer the question: How do I build an application with Haskell?

Intermediate level practical resources on Haskell are notoriously hard to find. My goal is to provide a real world example of an Haskell application, while keeping the scope small (Quad CI is only 1000 lines of code, including tests).

Another goal is to showcase Simple Haskell (or at least my own interpretation of it).

Finally, I think RecordDotSytax is one of the coolest things that happened in Haskell land recently and I wanted to show how to use it in practice.


Single server - multiple agents.

Builds share workspace.

STM queue

1 build/agent concurrency limit

TODO say more

Codebase overview

Domain types (Build, Pipeline etc.) along with main state machine (progress)

Talks to Docker api

Runs a single build, collecting logs (Core.collectLogs) and processing state updates (Core.progress)

Introduces Job type, which is just a Build that can be queued and scheduled

An in-memory implementation of JobHandler, built on top of STM

Talks to Github api

Agents ask the server for work to do, run builds (Runner) and send updates back to the server

The server collects jobs to be run (when receiving webhook events). It keeps an internal job queue (JobHandler) exposed as an http api (used by web ui)

Main entrypoint. Calls either Server.run or Agent.run

Low-level code to send http requests to a socket. Not interesting, can be ignored.