This example needs four container images deployed in lockstep:
mesh-sidecar, patroni, webdemo, signaling. CI publishes them to
GHCR with Sigstore-backed GitHub Build Provenance; consumers pin by
tag (or, better, by digest) and verify provenance with
gh attestation verify.
mesh-sidecar is the consolidated platform-plumbing image. It is a single
container that runs bootstrap-secrets, mesh-conn, attestation
admission helpers, consul, and (on workers) envoy. It's the heaviest
by a wide margin because it inherits from envoyproxy/envoy and bundles
several more binaries on top.
This doc covers the three paths you'll actually use:
- CI publish (the steady-state)
- Manual one-off publish (dev iteration / breaking glass)
- Hot-patch on a dev cluster (debugging without a redeploy)
.github/workflows/publish.yml runs on push to main
when any of the four image build contexts (or the workflow itself)
change, and on PRs touching the same paths. Each run:
- Builds all four images via a matrix job. The
mesh-sidecarbuild uses the repository root as its Docker context (instead ofcomponents/mesh-sidecar/) so its Dockerfile can pull the embedded Go components fromcomponents/bootstrap-secrets/,components/mesh-conn/,components/admission-broker/, andcomponents/admission-client/. - On
main, pushes toghcr.io/dstack-tee/service-mesh/<name>with two tags: the long-form commit SHA (sha-<40-hex>) andlatest. - Generates a GitHub Build Provenance attestation per image via
actions/attest-build-provenance@v2. The attestation is signed by Sigstore using a short-lived cert obtained through the workflow's GitHub OIDC token. We do not manage signing keys. It binds the image digest to the commit SHA, workflow file, and runner identity. - Pushes the attestation to GHCR alongside the image, so consumers can fetch and verify it via either GitHub's API or any cosign-style tool.
- On PRs, builds without pushing or attesting (verification only).
# By tag (lower assurance because `latest` floats):
gh attestation verify \
oci://ghcr.io/dstack-tee/service-mesh/mesh-sidecar:latest \
--repo Dstack-TEE/service-mesh
# By digest (preferred because it is pinned and will not drift):
gh attestation verify \
oci://ghcr.io/dstack-tee/service-mesh/mesh-sidecar@sha256:<digest> \
--repo Dstack-TEE/service-meshA successful verification proves: this image's digest was attested in a
GitHub Actions run on Dstack-TEE/service-mesh, with a workflow
file and commit SHA you can inspect to decide whether to trust it.
Failed or absent attestations should fail your deploy.
For dev and demo deploys, image references live in the cluster compose
files. Pin those compose references to sha-<40-hex> tags or digests
rather than latest, so a CI rebuild of latest does not silently
change the next deployment.
For production admission policy that must bind image identity, put
digest-pinned image references directly in the Compose source.
Environment variable values are not a sufficient image-binding policy.
See docs/attestation.md.
When iterating fast on the mesh-sidecar (or any other component) you
don't want to round-trip through CI for every byte. Two equivalent
shortcuts. Note that mesh-sidecar builds from the repository root
(it pulls Go sources from components/); the rest build from their
own workload/component subdir.
TS=$(date +%s)
TAG=ttl.sh/dstack-mesh-sidecar-${TS}:24h
docker build -t $TAG -f components/mesh-sidecar/Dockerfile .
docker push $TAGThen replace the image reference in the compose file for a fresh dev
cluster, or hot-patch one SSH-enabled dev CVM (see section 3).
Existing provider-0.3 named-slot clusters cannot roll measured image
changes in place until the revision-redeploy provider path lands.
ttl.sh images expire 24h after push.
If you want a longer-lived dev image without going through main:
echo "$GITHUB_TOKEN" | docker login ghcr.io -u <your-user> --password-stdin
TAG=ghcr.io/<your-user>/service-mesh/mesh-sidecar:dev-$(date +%s)
docker build -t $TAG -f components/mesh-sidecar/Dockerfile .
docker push $TAGThese manual builds do not carry a build-provenance attestation. That comes from CI's OIDC identity. For anything user-facing, run the real CI workflow.
Sometimes you need to swap a binary on one running CVM right now to debug a fix before building a proper image. This is a dev-only path. It requires SSH, so it works only on dev OS images; production Phala OS images disable SSH. It also bypasses dstack compose measurements, so do not use it for attestation or production validation.
GW=dstack-pha-prod5.phala.network
APP_ID=<cvm-app-id>
NEW=ttl.sh/dstack-mesh-sidecar-<ts>:24h
OLD=$(ssh ... root@${APP_ID}-22.${GW} \
"docker inspect dstack-sidecar-1 --format '{{.Config.Image}}'")
ssh ... root@${APP_ID}-22.${GW} "
docker pull $NEW
docker tag $NEW $OLD
cd /tapp && docker compose \
--env-file /dstack/.host-shared/.decrypted-env \
-p dstack -f /tapp/docker-compose.yaml \
up -d --force-recreate sidecar
"The retag tricks compose into using the new bits without touching the declared image string. This bypasses dstack's attestation hashes. Use it for dev smoke testing only. Next CVM reboot re-renders the declared compose source.
When CI publishes new images:
- Decide whether you're pinning to
:latest(drifts) or to the:sha-...tag from the new run (recommended). Find the new SHA by inspecting the workflow run's output orgh run view. - For a new dev or demo cluster, edit the cluster compose files to use
that pin before
terraform apply. For production image binding, update the compose source to use digest-pinned image references. - For an existing provider-0.3 named-slot cluster, app-level image and compose changes are blocked until the Cloud revision-redeploy endpoint is wired into the provider. Today, use a new cluster or an explicit destroy/recreate for measured changes; use the hot-patch path only for dev smoke testing on a single SSH-enabled CVM.
- Verify with
gh attestation verify oci://...@<digest>if you want to be sure the image you're pinning was built by this repo.