Warning
Harmont is in early alpha.
Today it's a powerful task runner -- like make or just, but with DAG-based
parallel execution, Docker isolation, layer caching, and typed toolchain
presets for many languages.
The cloud CI/CD platform at harmont.dev is under active development. APIs will change. We'd love your feedback -- join the community.
The performance of the hm CLI is not as good as I'd like it to be. I'm
actively working on cross-run caching. The code quality is similar -- needs
improving and is a work in progress.
hm will always remain open-source, and pluggable into any CI/CD
provider.
Harmont lets you define CI/CD workflows in TypeScript or Python and run them
instantly on your machine in Docker containers. No YAML. No commit -m "run ci" --allow-empty spam. Each pipeline step runs in an isolated container with
built-in caching, parallel execution, and consistent environments.
tui-demo.mp4
Features:
- Pipelines as real code - Python or TypeScript, not YAML.
- Instant local runs -
hm runexecutes in Docker on your machine. - DAG-based parallelism - independent chains run concurrently.
- Layer caching - Docker snapshots are reused across runs; only changed steps re-execute.
- Typed toolchains - first-class presets for Rust, Go, Python, Java, C++, React, and more.
curl -fsSL https://get.harmont.dev/install.sh | shOr via Cargo:
cargo install harmont-cliSave this as .harmont/pipeline.py (or .harmont/pipeline.ts):
Python
import harmont as hm
from harmont.python import PythonToolchain
@hm.target()
def project() -> PythonToolchain:
return hm.python(path=".")
@hm.pipeline(
"ci",
default_image="ubuntu:24.04",
triggers=[hm.push(branch="main")],
)
def ci(project: hm.Target[PythonToolchain]) -> tuple[hm.Step, ...]:
return (
project.test(),
project.lint(),
project.fmt(),
project.typecheck(),
)TypeScript
import { pipeline, push, type PipelineDefinition } from "harmont";
import { python } from "harmont/toolchains";
const project = python({ path: "." });
const pipelines: PipelineDefinition[] = [
{
slug: "ci",
triggers: [push({ branch: "main" })],
pipeline: pipeline(
project.test(),
project.lint(),
project.fmt(),
project.typecheck(),
{ defaultImage: "ubuntu:24.04" },
),
},
];
export default pipelines;hm run ciIf the repo declares only one pipeline, the slug is optional - just hm run.
Browse the example projects for idiomatic pipelines in Rust, Go, Python, Java, C++, React, Next.js, and more.
Use harmont-dev/actions-hm to run
your pipelines in GitHub Actions with automatic Docker image caching:
name: CI
on: [push, pull_request]
permissions:
contents: read
packages: write # needed for Docker image caching via GHCR
jobs:
ci:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: harmont-dev/actions-hm@main
with:
pipeline: ciThe action installs hm, runs your pipeline, and caches Docker images in GitHub
Container Registry so subsequent runs skip unchanged steps. No actions/cache
configuration required.
Multiple pipelines
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: harmont-dev/actions-hm@main
with:
pipeline: lint
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: harmont-dev/actions-hm@main
with:
pipeline: test
parallelism: 4Without caching
- uses: harmont-dev/actions-hm@main
with:
pipeline: ci
cache: 'false'See the action repo for the full input reference, sub-actions, and caching details.
For the full pipeline reference, richer examples, and more - see the docs.
The CLI is dual-licensed under either of
- Apache License, Version 2.0 (
LICENSE-APACHE) - MIT license (
LICENSE-MIT)
The reason I started this project is because every other CI/CD tool I've used in my life has sucked.
I've worked at Tesla, Bun, Mesa and never did I find a CI/CD system that was easy to use and was also fast.
At Tesla, we used Jenkins -- executors are finite, so your builds are stuck in queues.
At Bun, we used Buildkite -- large shell pipelines, and really pricy service, and a TS SDK that's only slightly better than YAMLs.
At Mesa, I migrated everyone to use BuildBuddy and Buildkite. Bazel is awesome, but the mental overhead required to use it is way too high. We, sadly, ended up reverting to plain Buildkite.
I asked myself a couple questions:
- Why can't I run my CI/CD pipelines locally? act is an awesome project, but it's surprisingly slow (not to the author's fault -- but rather GHA's model).
- Why is my CI/CD system not just a
Makefile? Why is there nohm runcommand that is shared between local dev and CI/CD?- Why can't I get preview environments for Haskell, Rust, Zig or whatever? Vercel does an awesome job with
next.jspreview environments, but there is no good way to do this for arbitrary environments.- Why do we have to write YAMLs for our pipelines? All my pipelines end up being YAML documents from hell. I think we can do better.
- Why do I need
artifacts-uploadandartifacts-downloadeverywhere? I don't need it locally, so why do I need it in CI/CD? In other words, why aren't our CI/CD systems stateful? If my build scripts can write anopenapi.jsonin the local directory, why do I need some magic to transfer it between individual steps?
Harmont's goal is to make all these questions obsolete. CI/CD can be better, and that's what Harmont wants to be -- a CI/CD that sucks a lot less.