Skip to content
Engine has no release yet: these pages describe unreleased main.

Installation

A glibc userland, on linux. Crewlet is one binary with no runtime to install: it embeds its event stream (a NATS JetStream server) and its database is a local file it creates. There is no broker to operate and nothing to point a DSN at.

It is not, however, a static binary on linux, and it is worth knowing why before you pick a base image. The store’s database engine is a native library loaded with dlopen, so the linux binaries are dynamically linked against glibc (libc.so.6) — a pure-Go build does not avoid that. They will not run on scratch, on a distroless image without a C library, or on Alpine and other musl systems, where the failure is no such file or directory about a file that plainly exists. Use a glibc base, or the published container image, which is debian:trixie-slim for exactly this reason. macOS binaries are unaffected.

Two things are worth having anyway, for what runs around it:

  • uv — many MCP servers are launched with uvx, so a company whose roles use one needs it on the engine’s PATH.
  • Docker — only for the local integration loops (GitLab, Mattermost) and for the container mode of the code sandbox.
Terminal window
go install github.com/crewlet/crewlet/cmd/crewlet@latest

Or take a signed release binary — every tag publishes archives for linux and macOS on amd64 and arm64, plus a checksums.txt with a keyless Sigstore signature beside it (checksums.txt.sigstore.json, which cosign 3 verifies):

There is no Windows build, and no musl build. The store driver ships its database engine as a native library that upstream embeds for windows/amd64 and not for windows/arm64, so half of the Windows matrix could never open a store; rather than ship one architecture and break the other, the target went. On linux use a glibc distribution (see the prerequisites above), the container image, or — on Windows — WSL.

Terminal window
# from https://github.com/crewlet/crewlet/releases
tar xzf crewlet_<version>_<os>_<arch>.tar.gz
cosign verify-blob checksums.txt \
--bundle checksums.txt.sigstore.json \
--certificate-identity 'https://github.com/crewlet/crewlet/.github/workflows/release.yml@refs/tags/v<version>' \
--certificate-oidc-issuer https://token.actions.githubusercontent.com
sha256sum -c checksums.txt --ignore-missing

Each archive holds the crewlet binary, LICENSE, README.md and the third-party notices it redistributes: THIRD_PARTY_NOTICES.txt for the Go toolchain and every Go module the binary links, and dashboard/THIRD_PARTY_NOTICES.txt for the embedded dashboard: its npm packages, the Geist and Geist Mono faces (OFL-1.1) and the Lucide glyphs (ISC, with Feather’s MIT text for the glyphs derived from it). The container image carries the same files under /usr/share/doc/crewlet/, and a running engine serves the dashboard’s at /static/dashboard/THIRD_PARTY_NOTICES.txt.

Or run the container image, ghcr.io/crewlet/crewlet — a Debian userland rather than a distroless one, because the engine spawns process trees (stdio MCP servers, and the local sandbox’s coding agent). Each release pushes it as ghcr.io/crewlet/crewlet:<tag> (v0.2.0, the tag itself) for linux/amd64 and linux/arm64, and a stable release also moves latest. The image is signed by digest with the same keyless identity as the checksums, which names the release workflow at that exact tag (cosign 3, or cosign 2 with --new-bundle-format):

Terminal window
cosign verify ghcr.io/crewlet/crewlet:<tag> \
--certificate-identity 'https://github.com/crewlet/crewlet/.github/workflows/release.yml@refs/tags/<tag>' \
--certificate-oidc-issuer https://token.actions.githubusercontent.com

main is published too, unreleased, as one tag, ghcr.io/crewlet/crewlet:main, for linux/amd64 and linux/arm64. It is rebuilt on every push to main and moves to that build, and nothing else is published from main — no tag per commit, and never latest. When pushes land close together only the newest is built, and a commit that reaches main without starting a build — the dependency bumps Dependabot merges with GitHub’s workflow token start none — is never built: main stays on the last pushed commit’s image until the next push. Its binary reports main- and the first twelve hex digits of the commit it was built from, then the whole commit:

Terminal window
$ docker run --rm ghcr.io/crewlet/crewlet:main version
crewlet main-4e45c6b992a2 (4e45c6b992a2133639abebeec271236969b4ead4)

It is not a release and is promised nothing: compatibility is promised only between tags. The tag moves on every push to main and the image it moved from is deleted within the hour, so a deployment that must keep running one build runs a release rather than main. It is signed by digest like a release’s image, by a different workflow, so it verifies under a different identity (cosign 3, or cosign 2 with --new-bundle-format):

Terminal window
cosign verify ghcr.io/crewlet/crewlet:main \
--certificate-identity 'https://github.com/crewlet/crewlet/.github/workflows/main-image.yml@refs/heads/main' \
--certificate-oidc-issuer https://token.actions.githubusercontent.com

Add --certificate-github-workflow-sha with a commit’s full hash to check that the image was built from that commit rather than from any commit on main.

A binary from go install reports its module version, one from a release reports its tag and one from the image of main reports main-<commit>, and none ever claims to be something it is not.

Terminal window
git clone https://github.com/crewlet/crewlet.git
cd crewlet
go build ./cmd/crewlet

That is the whole setup — see CONTRIBUTING.md for the test and lint commands.

The engine needs none. crewlet run in a directory with a config is a working company:

  • the event stream is a JetStream server inside the process. A deployment that outgrows one node either clusters those embedded servers together or points the same config slot at an external NATS server — the client code is identical, so it is a connection choice rather than a second backend. See Fleet.
  • the store is one local file this process owns exclusively. Not a shared database, and no DSN: two engines pointed at one file corrupt it. Coordination between nodes goes through a separate KV slot, never the file.

docker-compose.yml in a repo checkout starts nothing by default — every service in it is behind a profile, because none of them is the engine:

Terminal window
docker compose --profile gitlab up -d # local GitLab (code host)
docker compose --profile mattermost up -d --wait # self-hosted Mattermost (chat)

Each of the two self-hostable integrations pairs with a bootstrap script under scripts/ that seeds the instance and provisions the agent seats. See GitLab § Local testing and Mattermost § Local testing. Jira and Confluence have no profile here — Atlassian is not something a compose file can stand up. Nor is there a profile for the stream or the store: the engine brings both up itself, which is the whole point of embedding them.

Running either of these on a remote host rather than your own machine? Each one has to be told the address browsers reach it on — MATTERMOST_PUBLIC_URL, and GitLab’s external URL — before the stack comes up. For Mattermost that setting also gates live updates, so getting it wrong looks like a working install where messages only appear on refresh; the bootstrap script settles it for you, and The Site URL explains why.

(--wait is safe for the Mattermost profile — every service there has a healthcheck.)

Terminal window
crewlet --version

Next: the Quickstart brings up a four-agent company, and Choosing your stack walks through the external services (LLM, tracker, code host, chat, sandbox) and their alternatives.

Part of Crewlet. Generated from crewlet/crewlet main at ee70177.