Satvik Akkarajuall systems operational
← All projects

Ligit

Self-hosted Git server with integrated CI/CD engine and web UI

The problem

I wanted a small self-hosted Git host with built-in CI that I could run on my own machine. Gitea and GitLab are heavy to operate, and I wanted to understand how the pieces fit together. If you just need a Git server, Gitea is the better choice. Ligit was about building one. The users were me and small side-project setups.

Key decisions

Split Git access in two. Browsing (trees, logs, diffs, branches, tags) goes through a C++ library on libgit2, exposed to Java over JNI, which gives fast in-process reads for the UI. Clone, fetch and push go through the real git-upload-pack and git-receive-pack subprocesses. Reimplementing the wire protocol is risky and gains nothing.

Run CI steps in Docker. A push creates a pipeline run. Each job with an image gets a container with the workspace mounted, and steps run through docker exec. Containers drop all capabilities, set no-new-privileges, and are capped at 512 MB and 1 CPU. Workflows use GitHub-Actions-style YAML, so the format is familiar. Step output streams live into the database and reaches the UI over SSE.

Tradeoffs

  • Execution is basic. Each pipeline runs in a single @Async method, with no real queue, concurrency limits, retries or persisted scheduling. If the app restarts mid-run, that run is lost.
  • Isolation isn’t hardened. Jobs with no image fall back to running bash on the host, which is unsafe for untrusted code.
  • Workspaces are fresh every run in /tmp, with no caching.
  • Auth was bolted on late. JWT, an admin user, and an SSH server on Apache Mina, with HTTP push blocked. It works, but it was retrofitted instead of designed in.
  • JNI plus C++ in a Java service adds build and deploy complexity for a modest gain.

If I rebuilt it: a real job queue with worker limits, every job in a container, and auth designed from the start.

Outcome

I built it in early 2026 (first commit 27 February). It runs as a single container plus Postgres via Docker Compose. I’ve stopped running it for now, because the machine that hosted it is being used for other learning.

What I took from it: Git’s smart-HTTP protocol, JNI, subprocess streaming, and how much of a CI system is process and lifecycle management. The source is private for now.