Several repositories, one pipeline
A service rarely lives in one repository, and its deployment has to know which commit of each it ships. In Pipemesh, one pipeline can declare the repositories it reads:
- Every revision pins a commit of each.
- A push to any of them starts a revision.
- A job in one repository can consume an artifact built in another.
This guide follows
pipemesh/demo-multirepo-pipeline,
a repository that holds nothing but its pipemesh.yaml. The code lives
in the two repositories it declares:
| Repository | What it holds |
|---|---|
| demo-multirepo-orders | the orders service, and in client/ the library other services call it through |
| demo-multirepo-billing | a service that calls orders through that client library |
The client library crosses over as an artifact. orders_client builds
orders-client.jar from orders' client/ directory, and billing
builds against that jar without ever reading orders' sources.
What happens on a change
| The push | orders_client | orders | billing | deploys |
|---|---|---|---|---|
orders service/ |
reused | runs | reused | orders only |
orders client/ |
runs | runs | runs, against the new jar | both |
a comment in orders client/ |
runs | runs | reused: the jar is byte-identical | none: the jars are the same |
billing src/ |
reused | reused | runs | billing only |
| the pipeline's own README | reused | reused | reused | none |
Two job types make this hold:
- A build (
job_type: build) reuses (skip: built). When its fingerprint matches a stored run, it takes that run's jar instead of building again. That covers a revert, too. - A deploy (
job_type: deploy) checks out nothing and consumes exactly one jar. It runs when that jar is new to it, and skips the one it already shipped.
How it works
repos:declares the other repositories by alias and URL. Each must first be added to the organization in Pipemesh (step 2). Pipemesh then watches its default branch like any other repository. It needs nopipemesh.yamlof its own.- A revision pins every repository: the commit of the pipeline's own repository and of each declared one.
repo:sets a job's workspace. A job withrepo: orderschecks out orders at the revision's pinned commit, and itscheckout:lists files of that repository. A job withoutrepo:works in the pipeline's own repository.produces:andconsumes:cross repositories.billingconsumesorders_client/jar: the jar is mounted into billing's workspace, with its path inPIPEMESH_ORDERS_CLIENT_JAR. Consuming is also a dependency:billingwaits fororders_client, and a deploy waits for its build.- Fingerprints follow the pinned commits. A job's fingerprint covers its definition, what it checks out in its repository, and what it consumes.
The definition covers repos:, repo:,
produces: and consumes: in full.
Set it up
1. The repositories
Keep each service in its own repository, as today. The pipeline can live in one of them or, as in the demo, in a repository of its own.
2. Add every repository to Pipemesh
Open Repositories for the organization and add the code repositories. Each is listed in the catalog, and the pipelines that declare it appear under Used by. Then enable the repository that holds the pipeline.
3. The pipeline: pipemesh.yaml
The demo's definition, abridged. The jobs compile with javac; yours
run whatever builds your services.
pipeline:
type: pipeline
repos:
orders: github.com/pipemesh/demo-multirepo-orders
billing: github.com/pipemesh/demo-multirepo-billing
stages:
- build
- staging
- production
jobs:
# The client library: works in orders, checks out only its client/.
orders_client:
job_type: build
stage: build
repo: orders
checkout:
- client
script: |
javac -g:none -d out/classes $(find client/src -name '*.java')
jar --create --date=2026-01-01T00:00:00Z --file out/orders-client.jar -C out/classes .
produces:
jar: out/orders-client.jar
# The orders service, built with the client sources it shares a model with.
orders:
job_type: build
stage: build
repo: orders
checkout:
- client
- service
script: |
javac -g:none -d out/classes $(find client/src service/src -name '*.java')
jar --create --date=2026-01-01T00:00:00Z --file out/orders.jar -C out/classes .
produces:
jar: out/orders.jar
# Billing, in its own repository, built against the client jar orders produced.
billing:
job_type: build
stage: build
repo: billing
checkout:
- src
consumes:
- orders_client/jar
script: |
javac -g:none -cp "$PIPEMESH_ORDERS_CLIENT_JAR" -d out/classes $(find src -name '*.java')
jar --create --date=2026-01-01T00:00:00Z --file out/billing.jar -C out/classes .
produces:
jar: out/billing.jar
# Each deploy reads only the jar it ships.
deploy_billing_staging:
job_type: deploy
production: false
stage: staging
consumes:
- billing/jar
script: |
java -jar "$PIPEMESH_BILLING_JAR" --self-test
echo "billing → staging"
deploy_orders_staging:
job_type: deploy
production: false
stage: staging
consumes:
- orders/jar
script: |
java -jar "$PIPEMESH_ORDERS_JAR" --self-test
echo "orders → staging"
deploy_billing_prod:
job_type: deploy
production: true
stage: production
needs:
- deploy_billing_staging
consumes:
- billing/jar
script: echo "billing → production"
deploy_orders_prod:
job_type: deploy
production: true
stage: production
needs:
- deploy_orders_staging
consumes:
- orders/jar
script: echo "orders → production"
pipemesh:
pipelines:
pipeline: !ref pipeline
Two details to copy:
- Reproducible jars.
-g:none(no debug information) and--date=…make each jar byte-reproducible. A comment in the client compiles to the same classes, so the same jar, and nothing that consumes it moves. Do the same with whatever your build produces, or accept that a cosmetic change rebuilds its consumers. - Narrow checkouts. Each build's
checkout:names the files it compiles. Its workspace holds only those, so its fingerprint can't miss one.
4. Watch the board
- The Sources stage has a card per repository: the commit it's at, and when it was last polled.
- The Revisions stage lists the revisions in flight. One started by a push to orders or billing names that repository.
- A revision shows the commit of every repository it pinned, with the pull request that merged it. Compare it with the one before, or any other, to see which repositories moved and link to the diff on GitHub.
- A job says why it ran or didn't: a build that reused a stored jar, a deploy that already had its jar. Its Artifacts tab lists what it produced and consumed, even when it reused or skipped. Its Revision tab shows the revision it ran at.
Try it
The pipeline is public at pipemesh.io/github.com/pipemesh/demo-multirepo-pipeline. Push a change to one of the three repositories and watch the table come true.