Website · Harmont Cloud · Docs · Slack
CI/CD as real code. Write your pipelines in Python or TypeScript, then run the exact same pipeline locally in Docker or on managed runners in Harmont Cloud — with layer caching and DAG parallelism built in.
Harmont lets you define CI/CD pipelines in TypeScript or Python and run them two ways from a single definition: instantly on your own machine in Docker, or on managed runners in Harmont Cloud. It's the same pipeline either way — the run you debug locally is byte-for-byte the run that ships in CI, so you stop pushing throwaway commits just to find out what breaks. Each step runs in an isolated container with built-in caching, DAG parallelism, and consistent environments.
Run it all locally, or sign up for Harmont Cloud and
push your pipelines to managed runners with a single --cloud flag.
tui-demo.mp4
Why teams switch:
- Pipelines are real code — Python or TypeScript, with the autocomplete, types, and abstractions your editor already gives you.
- Run it locally —
hm runexecutes your real pipeline in Docker on your machine, so you catch failures before you push. - …or run it in the cloud — the same pipeline runs on Harmont Cloud's
managed runners with
hm run --cloud, byte-for-byte identical to your local run. Sign up to get started. - DAG-based parallelism — independent steps run concurrently;
hmfigures out the dependency graph for you. - Automatic layer caching — Docker snapshots are reused across runs, so only changed steps re-execute. Caching works out of the box.
- Typed toolchains — first-class presets for Rust, Go, Python, JavaScript/ TypeScript, C/C++, Zig, and Elixir — each handles setup, build, test, lint, and format for you.
- Claude writes it for you —
hm initinstalls Claude Code skills that author your pipeline and migrate your GitHub Actions (see below).
curl -fsSL https://get.harmont.dev/install.sh | shOr via Cargo:
cargo install harmont-clihm inithm init scaffolds a working .hm/pipeline.{py,ts} from a template and offers
to install Claude Code skills that write and maintain your pipeline. Run it and
pick your stack from the menu, or name a template up front with -t:
hm init -t rust # cmake · elixir · nextjs · js · rust · zig · pythonThen run it:
hm runIf the repo declares only one pipeline, the slug is optional. Otherwise name it:
hm run ci.
Want it to run in CI instead of on your laptop? Sign up for Harmont
Cloud, then hm cloud login and hm run --cloud — the
same pipeline, on managed runners. See Cloud below.
A pipeline is just code. Save this as .hm/pipeline.py (or .hm/pipeline.ts):
Python
import harmont as hm
from harmont.python import PythonToolchain
@hm.target()
def project() -> PythonToolchain:
return hm.python(path=".")
@hm.pipeline(
"ci",
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/hm";
import { python } from "@harmont/hm/toolchains";
const project = python({ path: "." });
const pipelines: PipelineDefinition[] = [
{
slug: "ci",
triggers: [push({ branch: "main" })],
pipeline: pipeline(
[
project.test(),
project.lint(),
project.fmt(),
project.typecheck(),
],
),
},
];
export default pipelines;hm run ciBrowse the example projects for idiomatic pipelines in Rust, Go, Python, Elixir, Zig, C/C++, TypeScript, React, and Next.js.
hm init can install three Claude Code skills
into your repo. They turn pipeline authoring and migration into a conversation:
| Skill | What it does |
|---|---|
| write-pipeline | Ask Claude to "set up CI" and it detects your stack, reads the live Harmont docs, and writes a correct .hm/pipeline. |
| convert-gha | Point Claude at your .github/workflows/*.yml and it migrates them to a Harmont pipeline — dropping the actions/cache, actions/checkout, and actions/setup-* boilerplate Harmont handles for you. |
| validate-ci | Before you push, Claude runs the whole pipeline locally (hm run -k --logs) and only gives the green light when it actually passes. |
hm init # detects .github/workflows and offers convert-ghaAlready have a pipeline and just want the skills? Re-run hm init — it skips
the template and installs the skills.
Migration is the easy part. The convert-gha skill reads every workflow and
maps it over for you:
actions/checkout→ not needed (your source is always in the container)actions/setup-*→ replaced by a typed toolchainactions/cache→ not needed (Harmont caches Docker layers automatically)jobs.*.needs→ the DAGhmderives from your coderuns-on→ per-stepimage=(the default base isubuntu:24.04)
The result is a pipeline you can run locally before it ever hits CI.
Automatic layer caching. Every step's result is committed as a Docker snapshot, keyed deterministically from the step and its inputs. Re-run a pipeline and only the steps whose inputs changed actually execute — everything else is restored from cache. You can tune this per step in the DSL:
hm.forever() # cache until inputs change
hm.ttl(timedelta(hours=6)) # cache for a window
hm.on_change("src/") # rebuild when these paths changeDAG parallelism. hm builds a dependency graph from your pipeline and runs
independent chains concurrently. Use .fork() to branch and hm.wait() to
join. Control concurrency with --parallelism N (defaults to your CPU count).
Run everything, even after a failure. Pass -k / --keep-going and
independent chains keep running after one step fails, so you see all failures
in a single run instead of one at a time.
hm run ci -kTimeouts. Bound a single step or the whole pipeline:
hm.timeout("5m", project.test()) # per-step
@hm.pipeline("ci", timeout="30m") # whole pipelineMachine-readable output. --format json emits one BuildEvent per line
(NDJSON) on stdout — identical whether the build runs locally or in the cloud —
so the same wrapper script parses both:
hm run ci --format jsonPrefer raw logs over progress bars? Add --logs.
Harmont Cloud runs your pipelines on managed runners —
no executors to provision or babysit. hm run --cloud submits your local
working tree without committing or pushing first: the CLI renders the pipeline
locally (so a broken DSL fails fast, before any upload), archives the worktree
(respecting .gitignore, stripping .git), uploads it, and streams live job
logs.
Create an account, then:
hm cloud login # one-time browser login (or --paste for no browser)
hm cloud org switch acme # set a default org so you can skip --org
hm run --cloud # run the current tree in the cloudEverything you can do locally works in the cloud — same flags, same
--format json event stream:
hm run --cloud --no-watch # submit and exit without tailing logs
hm run --cloud --org acme # pick the org explicitly
hm run --cloud --format json # NDJSON BuildEvent stream for scriptinghm cloud login binds a loopback listener, opens app.harmont.dev/cli-login,
and stores the token in ~/.config/hm/credentials.toml (mode 0600). No browser?
Use hm cloud login --paste. In CI, set a token instead:
export HM_API_TOKEN=hm_live_... # takes precedence over the file
hm run --cloud --org acme| File | Mode | Contents |
|---|---|---|
~/.config/hm/config.toml |
0644 | backend, [cloud] (org, api_url), [preferences] (format, auto_watch) |
~/.config/hm/credentials.toml |
0600 | bearer tokens keyed by API base URL |
Settings layer defaults → user config → project .hm/config.toml → env, so
you can commit per-repo defaults and still override them locally. Env overrides:
HM_API_URL, HM_API_TOKEN.
hm cloud whoami # who am I
hm cloud pipeline list # pipelines in the active org
hm cloud build list --pipeline ci # builds for a pipeline
hm cloud build watch --pipeline ci 42 # tail build #42
hm cloud build cancel --pipeline ci 42 # cancel build #42
hm cloud job log --pipeline ci --build 42 <job-id>
hm cloud billing balance # credit balance
hm cloud billing topup 20 # add $20 via StripeNot ready to leave GitHub Actions? Run your Harmont pipelines inside GHA and get automatic Docker image caching for free. (Ready to leave? See convert-gha above.)
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 — the caching is wired
up for you.
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.
The examples/ directory has a complete, runnable pipeline for
each stack — every one shipped in both Python and TypeScript:
| Rust | Go | Python (uv) |
| Elixir · Phoenix | Zig | C · C++ |
| TypeScript · Bun | React | Next.js |
Don't see your stack? Toolchains compose from raw steps (hm.sh(...)), so you
can build a pipeline for anything that runs in a container.
For the full pipeline reference, richer examples, and more - see the docs.
Harmont is built in the open and we want your feedback while the APIs are still moving.
- Discord — discord.gg/hm-dev
- Slack — join the workspace
- Issues — github.com/harmont-dev/harmont-cli/issues
File bugs, request toolchains, or tell us what made you bounce — all of it helps.
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.