CI/CD that speaks CircleCI
ForgeCI runs your .circleci/config.yml-style pipelines on your own
infrastructure. A pure-Ruby compiler turns CircleCI 2.1 configs into
pipelines; Docker runners execute them and stream the logs back live.
# Looks like CircleCI. That's the point.
version: 2.1
jobs:
build:
docker:
- image: cimg/ruby:3.4
steps:
- checkout
- run:
name: Install dependencies
command: bundle install
- run:
name: Run tests
command: bundle exec rake test
workflows:
build-and-test:
jobs:
- build
Everything a CI needs. Nothing it doesn't.
A complete continuous-integration system in two pieces: a Rails control plane and zero-dependency Docker runners.
CircleCI 2.1 compatible
A pure-Ruby, zero-dependency config compiler turns CircleCI-style YAML into executable pipelines. Bring your existing config and muscle memory.
Docker runners
Stdlib-only Ruby runners claim queued jobs and run each step in isolated Docker containers. Scale horizontally — just add more runners.
Live log streaming
Watch job output in real time. Runners push log chunks over the API; the dashboard tails them via Server-Sent Events.
Cache & artifacts
Dependency caches and build artifacts backed by S3, with per-project scoping and configurable retention.
Contexts & secrets
Encrypted environment contexts shared across projects, injected at runtime — with admin-gated secret rotation.
GitHub App integration
Push webhooks trigger pipelines automatically, and commit statuses report build results back to your pull requests.
How it works
Three stages, from git push to green build.
Push
Install the GitHub App on your repos. Every push fires a webhook; ForgeCI fetches .forge-ci/config.yml at that commit.
Compile
The control plane compiles your CircleCI-style config into a pipeline of jobs, workflows, and dependencies — validated before anything runs.
Run
Runners claim queued jobs, execute each step in Docker, and stream logs live to the dashboard while commit statuses update on GitHub.
Simple architecture
One control plane, as many runners as you need, boring dependable storage.
┌─────────────────────┐
│ GitHub App │
│ webhooks + statuses │
└──────────┬──────────┘
│ push
┌──────────▼──────────┐ ┌──────────────┐
│ Control plane │─────▶│ Postgres │
│ (Rails 8) │ │ jobs + state│
└──────────┬──────────┘ └──────────────┘
│ claim / heartbeat / logs
┌──────────▼──────────┐ ┌──────────────┐
│ Runners (Docker) │─────▶│ S3 │
│ zero-dep Ruby │ │ logs/artifacts│
└─────────────────────┘ └──────────────┘
Quickstart
The whole stack — Postgres, control plane, worker, runner — with one compose file.
$ cp .env.example .env # fill in S3 credentials + bucket, pick a RUNNER_TOKEN secret
$ openssl rand -hex 32 # → paste into .env as RUNNER_TOKEN
$ docker compose up --build -d
$ docker compose up -d runner
$ # → open http://localhost:3000
Object storage is AWS S3 — create the bucket first
(aws s3 mb s3://<your-bucket>). The same
RUNNER_TOKEN value authenticates the runner against the control
plane, so there's no token to copy around. See
DEPLOY.md
for production deploys.