# Semaphore CI/CD: an Elixir monorepo where the enterprise edition lives in one folder

> Self-hosted continuous integration with a community edition under Apache 2.0 and a commercial edition in the ee/ directory of the same repository. The Makefile is the clearest map of how a build actually happens.

**semaphoreio/semaphore** — All-in-one delivery platform for AI-driven development.

- Repository: https://github.com/semaphoreio/semaphore
- Website: https://semaphore.io
- Stars: 1,616 · Forks: 95
- Language: Elixir
- License: NOASSERTION
- Published: 2026-10-07 · Updated: 2026-10-07 · Language: en
- Canonical page: https://hysenlabs.com/projects/semaphoreio-semaphore

## Three editions and how the source tree draws the line

Semaphore's README describes three flavors, and the interesting part is that two of them share one repository.

Community Edition is free and open source under the Apache 2.0 license, and the README is specific about where it lives: everything outside the `ee/` folder. That phrasing does more work than a license section usually does, because it means you can see the boundary in the source tree. Anything you can read outside `ee/` is community code under Apache 2.0.

Enterprise Edition is the commercial version with extra features for larger organizations under a commercial license, and it is in the `ee/` directory. Professional support comes with it.

Semaphore Cloud is the hosted version at semaphoreci.com, positioned for people who do not want to run infrastructure, with free plans for small projects up to enterprise scale.

The metadata for this repository lists the license as NOASSERTION, which is GitHub's label when it cannot detect a license automatically. The README states Apache 2.0 explicitly and links to a LICENSE file in the tree. For anything outside `ee/`, the README's statement is the authoritative one; for code inside `ee/`, assume the commercial terms apply and read the LICENSE file before you build a business on top of it.

If you are self-hosting and never intend to use the commercial code, `ee/` is a directory you can reason about as a boundary rather than a puzzle.

## A monorepo of about sixty top-level directories

The repository is written primarily in Elixir, and the tree shows a system decomposed into many internal services rather than one application. Named directories include `projecthub/`, `repohub/`, `dashboardhub/`, `public-api/`, `public-api-gateway/`, `branch_hub/`, `repository_hub/`, `self_hosted_hub/`, `secrethub/`, `rbac/`, `plumber/`, `scouter/`, `zebra/`, `encryptor/`, `keycloak/`, `loghub2/`, `guard/`, `auth/`, `bootstrapper/`, `ephemeral_environment/`, `feature_provider/`, `hooks_processor/`, `hooks_receiver/`, `github_hooks/`, `github_notifier/`, `periodic_scheduler/`, `notifications/`, `security-toolbox/`, `statsd/` and `badge/`.

Naming conventions carry the architecture. Directories ending in `hub` are stateful services, `loghub2` being a second-generation rewrite of an earlier one. `plumber` and `scouter` are named rather than descriptive, which is a hint that the team named services after roles rather than after what they contain. `ephemeral_environment/` and `feature_provider/` point at two of the distinctive ideas in the platform: per-pipeline throwaway environments, and a system that decides which capabilities a given account has.

Alongside the services sit the operational pieces: `helm-chart/` for Kubernetes deployment, `skaffold.yaml` for local development against a cluster, `e2e/` for end-to-end tests, `rfcs/` for design proposals, `sigs/` for special interest groups, `artifacthub/` for artifact publishing metadata, and an `mcp_server/` directory that is new enough to still be worth finding.

Not everything is Elixir. There is a `front/` directory for the JavaScript dashboard and a `get_id_token.py` for identity token handling. Elixir and Go both appear as topics for the project, which suggests the newer components are the ones written in Go.

For a contributor, this is the real cost of the project: understanding how roughly twenty services talk to each other.

## The Makefile is the honest description of the build

Documentation for CI products tends to describe the workflow. The Makefile here describes the machinery, and it is more informative for anyone planning to build or fork it.

Branch naming is derived from Git rather than configured. If the current commit is exactly a tag, the Makefile asks `git describe --exact-match --tags HEAD`, finds which branch contains that tag, strips non-alphabetic characters and truncates to 40 characters. If a pull request number is available in the environment it uses `pr$(SEMAPHORE_GIT_PR_NUMBER)`. Otherwise it falls back to the abbreviated ref name, cleaned and truncated the same way. The result becomes the image name:

```makefile
TAG_NAME=$(shell git describe --exact-match --tags HEAD 2>/dev/null)
ifneq ($(TAG_NAME),)
	export BRANCH?=$(shell git branch --contains tags/$(TAG_NAME) | head -n 1 | sed '/HEAD/d' | sed 's/[^a-z]//g' | cut -c 1-40)
else ifneq ($(SEMAPHORE_GIT_PR_NUMBER),)
	export BRANCH?=pr$(SEMAPHORE_GIT_PR_NUMBER)
else
	export BRANCH?=$(shell git rev-parse --abbrev-ref HEAD | sed 's/[^a-z]//g' | cut -c 1-40)
endif
```

Image names follow from that. The registry host defaults to `local`, images are named `$(APP_NAME)/$(BRANCH)` and `$(APP_NAME)/main`, and the registry and image names are overridable from the environment.

The build target is chosen by environment. `BUILD_ENV` prefers `MIX_ENV` and falls back to `APP_ENV`, and when the build environment is not `prod` the Docker build target becomes `dev`, otherwise it becomes `runner`. That is a meaningful split: development images include the toolchain and bind-mount source, while runner images are built for execution.

Two more details say the team runs this in CI themselves. Locally, the working directory is bind-mounted into the container so your edits take effect, and buildkit inline cache is disabled because it does not help a bind-mounted dev loop. In CI, no volumes are bound because the data is already on the host, formatting switches to `--dry-run --check-formatted` so the check does not rewrite files, and Docker build progress switches from `tty` to `plain` because tty progress output makes the logs hard to read. Warnings are treated as errors through a flag the Makefile names `EX_CATCH_WARRNINGS_FLAG`.

## Installation paths and what a self-hosted deployment involves

The README states that installing and running Semaphore takes 10 to 30 minutes, and offers three paths through badges linking to the documentation: Ubuntu single machine, Kubernetes, and a local Kubernetes cluster with Minikube for development. The Minikube link points at `LOCAL-DEVELOPMENT.md` in the repository, so the developer path is documented locally rather than on the website.

That third path is the honest entry point for anyone evaluating the project, because `skaffold.yaml` sits in the tree. Skaffold rebuilds and redeploys on change, which is how a twenty-service monorepo stays workable in development: you run a local Kubernetes cluster, Skaffold syncs your edits into the running pods, and you do not rebuild images for every change.

What a self-hosted install actually contains is worth spelling out, because it goes well beyond a pipeline runner. There is a Keycloak directory, so identity is handled by a separate server. There is a dashboard, a public API and a public API gateway, RBAC, secret management through `secrethub/`, log aggregation through `loghub2/`, notifications, periodic scheduling, and a Helm chart for deployment. A security toolbox component appears in the release notes with configurable scanners and a vulnerability severity source setting.

The README describes features in marketing terms, including blazing-fast CI/CD, YAML-based config with parallel execution, scaling from solo developers to large teams, and built for the modern cloud with containers, Kubernetes and multi-cloud. Those claims are not measurable from the repository. The structure is: a self-hosted platform you operate, with identity, secrets, scheduling and a dashboard as part of the deal.

## Release cadence: v1.5.0 is the newest tag in this repository

The three most recent releases all belong to the 1.5.0 line, and reading them is a good way to see how the project actually spends its effort.

The 1.5.0 release candidate 1, dated 2025-09-23, is a feature release. It allows scanners to be configured in the security toolbox, adds a vulnerability severity source configuration option for the security toolbox's Docker target, adds CSS processing to the front-end asset pipeline, allows some HTML tags in markdown reports, adds new Flutter templates, adds a project config option to control draft pull request builds, and shows artifact size in the interface.

Release candidate 2, dated 2025-10-06, is fixes. The public API v1alpha allowed run-now without branch and reference fields, and mishandled empty reference fields. The front end now filters based on tags on top of branch names. A GitHub hooks dependency was updated.

The final 1.5.0, dated 2025-10-10, is also fixes, and the entries are a useful signal about where users hit friction. The service account role selection dropdown overflowed. Service account project creation produced a bad error message. Service accounts were showing up in the user list. There is also an end-to-end test change for user management and project creation.

Three service account bugs in one patch is informative. Service accounts are how a pipeline authenticates to the platform that runs it, so that path gets less manual testing than the human-facing UI, which is the normal consequence of a feature being used mostly by machines.

The repository was pushed on 2026-09-18, well after the newest tag. Recent commits are therefore not necessarily in a released version, and the changelog rather than the commit log is where you should look to see what shipped.

## Where Semaphore fits, and the honest alternatives

The comparison that matters is not feature lists, it is what you are willing to operate.

Jenkins is the other self-hosted option, and the difference is architectural. Jenkins is a plugin platform where almost everything interesting is a plugin maintained by somebody else, which means flexibility and a large attack surface maintained outside the core team. Semaphore is a single product from one company with a defined source tree, so the behavior of any given feature is decided by the people who built the platform.

GitLab CI and GitHub Actions avoid the self-hosting problem entirely, at the cost of running your pipelines on someone else's infrastructure. If your build does not need to touch data that cannot leave your network, that is the cheaper answer by a wide margin.

Other self-hosted options include Drone, Woodpecker, Concourse and Tekton. Drone and Woodpecker are small and pipeline-centric. Concourse is built around immutable pipeline resources and has a very different conceptual model. Tekton is a set of Kubernetes primitives you assemble yourself, which is more flexible and more work.

Semaphore's specific appeal is the combination of a first-party dashboard and public API with a self-hosted deployment, plus the option to start on the community edition inside `ee/` boundaries and move up later. Its specific cost is that you are running a platform with identity, secrets, scheduling and an API gateway attached to your Kubernetes cluster, and that someone has to own it.

The repository's own governance documents point the same way. There are separate files for CONTRIBUTING.md, DEVELOPMENT.md, RELEASE.md, ROADMAP.md, GOVERNANCE.md, SECURITY.md and SUPPORT.md, plus `rfcs/` for proposals and `sigs/` for working groups. That is the document set of a company-run open source project rather than a community one, and it is the right expectation to bring to it.

## Conclusion

Semaphore is worth considering when your team already runs Kubernetes and wants a CI/CD platform it operates itself, with the option to buy the enterprise features later without changing products. The strongest argument for it is the folder split: community code under Apache 2.0 sits outside `ee/`, so the boundary between what you get free and what you pay for is legible in the source tree rather than hidden behind a license server. Two caveats. Self-hosting a platform with Keycloak, a dashboard, roughly twenty internal services and a Helm chart is a real operating commitment, not a weekend install, and the README's own claim is 10 to 30 minutes with a guided path. And the release cadence is slower than the commit activity suggests, with v1.5.0 dated 2025-10-10 while the repository was pushed on 2026-09-18, so read the changelog before assuming a recent commit is in a release. The last release before that push was a fix for a service account dropdown.

## FAQ

### What does semaphore mean?

A semaphore is an object used to control access to a shared resource, such as a counting semaphore in concurrent programming. In this repository the name refers to Semaphore CI/CD, the self-hosted continuous integration and delivery platform, not to that synchronization primitive.

### Which license applies to the Semaphore community edition?

The README states that everything outside the `ee/` folder is free and open source under the Apache 2.0 license, with the commercial edition living inside `ee/`. License detection on GitHub reports NOASSERTION for this repository, which is the label for a license it cannot infer, so follow the README and the LICENSE file for code outside `ee/`.

### How long does it take to install Semaphore?

The README says 10 to 30 minutes, and offers three paths: Ubuntu single machine, Kubernetes, and a local Minikube cluster for development. The Minikube path points at LOCAL-DEVELOPMENT.md in the repository. Plan on more than that if you are evaluating, since the platform includes Keycloak, a dashboard, a public API, RBAC and secret management.

### What is the newest Semaphore release in this repository?

Version 1.5.0, dated 2025-10-10, which fixed three service account issues including an overflowing role selection dropdown and service accounts appearing in the user list. The repository was pushed on 2026-09-18, so recent commits are not necessarily part of a released version.

### What language is Semaphore written in?

Mostly Elixir, with a JavaScript front end in `front/` and newer components including an `mcp_server/` directory. Elixir is the primary language, with elixir and golang both carried as topics, and there is a small Python script, `get_id_token.py`, for identity token handling.

## Sources

- [Issues](https://github.com/semaphoreio/semaphore/issues)
- [Project website](https://semaphore.io)
- [README](https://github.com/semaphoreio/semaphore/blob/main/README.md)
- [Releases](https://github.com/semaphoreio/semaphore/releases)
- [semaphoreio/semaphore on GitHub](https://github.com/semaphoreio/semaphore)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/semaphoreio-semaphore
