From d81ab6193ef0176ad740a1be18cd5089596d0ecd Mon Sep 17 00:00:00 2001 From: Rick Staa Date: Wed, 19 Aug 2026 12:23:33 +0200 Subject: [PATCH 1/3] ci: make ghcr the only target and the published image runnable Docker Hub livepeer/ is a curated product namespace and this is an example, so drop the opt-in second target rather than leave a path into it. Matches runner-app-examples. compose.yml builds from source and named no image, so nothing could land on the published one without hand-editing a compose file. An overlay adds it, keeping the registry out of the default path; at ~15 GB pulling is the sane route for anyone not changing the Dockerfile. Co-Authored-By: Claude Opus 5 (1M context) --- .github/workflows/build.yml | 21 ++++++--------------- .github/workflows/lint.yml | 4 +++- README.md | 12 +++++++++--- compose.image.yml | 15 +++++++++++++++ 4 files changed, 33 insertions(+), 19 deletions(-) create mode 100644 compose.image.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b502883..cd143eb 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -8,9 +8,10 @@ name: build # layer cache (an image this size would blow the 10 GB Actions cache anyway), and does # not run on pull requests, where a build this size buys little for what it costs. # -# GHCR needs no setup (ghcr.io//, public if the repo is). Docker Hub -# is optional and additive: set the DOCKERHUB_NAMESPACE variable plus the -# DOCKERHUB_USERNAME and DOCKERHUB_TOKEN secrets. +# GHCR needs no stored credentials: the built-in GITHUB_TOKEN publishes, and the +# package is public, so pulling needs none either. Docker Hub is deliberately not +# a second target: livepeer/ there is a curated product namespace, and this is an +# example. on: push: @@ -48,11 +49,8 @@ jobs: id: meta uses: docker/metadata-action@v6 with: - # Second line is empty unless Docker Hub is configured, and empty - # entries are ignored, so this is GHCR-only by default. - images: | - ghcr.io/${{ github.repository }} - ${{ vars.DOCKERHUB_NAMESPACE && format('{0}/{1}', vars.DOCKERHUB_NAMESPACE, github.event.repository.name) || '' }} + # Owner comes from the repo, so a fork publishes under its own namespace. + images: ghcr.io/${{ github.repository }} tags: | type=raw,value=latest,enable={{is_default_branch}} type=sha,format=short @@ -69,13 +67,6 @@ jobs: username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - - name: Log in to Docker Hub - if: vars.DOCKERHUB_NAMESPACE != '' - uses: docker/login-action@v4 - with: - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} - - name: Build and push uses: docker/build-push-action@v7 with: diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 994fcef..a5a0ef2 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -18,4 +18,6 @@ jobs: # Parses and merges the compose file; builds and starts nothing. # The Dockerfile is covered by build.yml. - name: Validate compose - run: docker compose -f compose.yml config --quiet + run: | + docker compose -f compose.yml config --quiet + docker compose -f compose.yml -f compose.image.yml config --quiet diff --git a/README.md b/README.md index d41435e..a3d3871 100644 --- a/README.md +++ b/README.md @@ -104,11 +104,17 @@ The price is unchanged by the overlay: static runners advertise it from `runners ## Ship it to an orchestrator -CI publishes the image to `ghcr.io/livepeer/streamdiffusion-livepeer-runner` on `main` and `v*` tags. An operator then runs it with a `runners.json` like this repo's, pointed at wherever they run the container, and needs no credentials for a public package. +CI publishes the image to `ghcr.io/livepeer/streamdiffusion-livepeer-runner` on `main` and `v*` tags. The package is public, so pulling needs no account and no login. An operator then runs it with a `runners.json` like this repo's, pointed at wherever they run the container. -The image is **~15 GB** (torch, TensorRT, ONNX Runtime), which is still close enough to what a GitHub-hosted runner has free that [build.yml](.github/workflows/build.yml) reclaims disk before building and skips the build on pull requests. Building locally is `docker compose build`. +[compose.image.yml](compose.image.yml) swaps the local build for that image, which is the sane path unless you are changing the Dockerfile: + +```sh +docker compose -f compose.yml -f compose.image.yml up -d +``` + +The base [compose.yml](compose.yml) always builds from source, so the registry is reached only when you ask for it by adding the overlay. It stacks with the on-chain one (`-f compose.yml -f compose.onchain.yml -f compose.image.yml`). -To publish to Docker Hub as well, set the repository variable `DOCKERHUB_NAMESPACE` and the secrets `DOCKERHUB_USERNAME` and `DOCKERHUB_TOKEN`. GHCR keeps working either way. +The image is **~15 GB** (torch, TensorRT, ONNX Runtime), which is still close enough to what a GitHub-hosted runner has free that [build.yml](.github/workflows/build.yml) reclaims disk before building and skips the build on pull requests. Building locally is `docker compose build`. ## Development diff --git a/compose.image.yml b/compose.image.yml new file mode 100644 index 0000000..a920a80 --- /dev/null +++ b/compose.image.yml @@ -0,0 +1,15 @@ +# Run the published image instead of building. Layer it on the offchain base: +# docker compose -f compose.yml -f compose.image.yml up -d +# +# The base file always builds from source, so this overlay is the only way to +# reach the registry; `pull_policy: always` keeps a stale local copy from +# shadowing the published one. Stacks with the on-chain overlay too: +# docker compose -f compose.yml -f compose.onchain.yml -f compose.image.yml up -d +# +# The image is ~15 GB, so pulling it beats a local build on any machine that is +# not going to change the Dockerfile. + +services: + app: + image: ghcr.io/livepeer/streamdiffusion-livepeer-runner:latest + pull_policy: always From f830e63ad8e81cbf5febc559e8fe4a8d50302dac Mon Sep 17 00:00:00 2001 From: Rick Staa Date: Wed, 19 Aug 2026 13:45:40 +0200 Subject: [PATCH 2/3] ci: match the example image conventions from runner-app-examples Tag a v* release as stable, and declare the OCI labels where they are actually applied. metadata-action passes labels as --label, which beats anything baked into the image, so Dockerfile LABELs were both redundant and contradicted on licence: they said Apache-2.0 while the generated default said MIT. The image carries both, so it says so. Co-Authored-By: Claude Opus 5 (1M context) --- .github/workflows/build.yml | 8 ++++++++ Dockerfile | 5 ----- README.md | 2 +- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index cd143eb..f657913 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -56,6 +56,14 @@ jobs: type=sha,format=short type=semver,pattern={{version}} type=semver,pattern={{major}}.{{minor}} + type=raw,value=stable,enable=${{ startsWith(github.ref, 'refs/tags/v') }} + # Set here rather than as Dockerfile LABELs: these are passed as --label, + # which wins over anything baked in, so one source of truth. The licence + # is the payload's (Apache-2.0) plus this wrapper's (MIT). + labels: | + org.opencontainers.image.title=streamdiffusion-livepeer-runner + org.opencontainers.image.description=daydream's StreamDiffusion, packaged to run on the Livepeer live runner; example, not production-ready + org.opencontainers.image.licenses=Apache-2.0 AND MIT - name: Set up Buildx uses: docker/setup-buildx-action@v4 diff --git a/Dockerfile b/Dockerfile index f5f66d7..c4c75c3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -14,11 +14,6 @@ # and the driver arrives through the container runtime. FROM python:3.11-slim -LABEL org.opencontainers.image.title="streamdiffusion-livepeer-runner" -LABEL org.opencontainers.image.description="daydream's StreamDiffusion realtime-img2img server, packaged unmodified to run as a Livepeer live runner" -LABEL org.opencontainers.image.source="https://github.com/livepeer/streamdiffusion-livepeer-runner" -LABEL org.opencontainers.image.licenses="Apache-2.0" - ENV DEBIAN_FRONTEND=noninteractive PYTHONUNBUFFERED=1 ENV HF_HUB_ENABLE_HF_TRANSFER=1 diff --git a/README.md b/README.md index a3d3871..c7923f8 100644 --- a/README.md +++ b/README.md @@ -104,7 +104,7 @@ The price is unchanged by the overlay: static runners advertise it from `runners ## Ship it to an orchestrator -CI publishes the image to `ghcr.io/livepeer/streamdiffusion-livepeer-runner` on `main` and `v*` tags. The package is public, so pulling needs no account and no login. An operator then runs it with a `runners.json` like this repo's, pointed at wherever they run the container. +CI publishes the image to `ghcr.io/livepeer/streamdiffusion-livepeer-runner` on `main` and `v*` tags. Tags: `latest` (current `main`), `stable` (latest `v*` release), `1.2` / `1.2.3`, `sha-`. The package is public, so pulling needs no account and no login. An operator then runs it with a `runners.json` like this repo's, pointed at wherever they run the container. [compose.image.yml](compose.image.yml) swaps the local build for that image, which is the sane path unless you are changing the Dockerfile: From 2c517c586a11e1f7172bcfbf65876ec077f17db4 Mon Sep 17 00:00:00 2001 From: Rick Staa Date: Wed, 19 Aug 2026 13:48:43 +0200 Subject: [PATCH 3/3] fix: name the image in compose.yml instead of a separate overlay Matches what runner-app-examples landed. `pull_policy: build` keeps a bare `up` building from source, so naming the image cannot silently run a published one over a contributor's code, and `--pull always` reaches the registry. One file rather than two to keep in step. Co-Authored-By: Claude Opus 5 (1M context) --- .github/workflows/lint.yml | 4 +--- README.md | 6 ++---- compose.image.yml | 15 --------------- compose.yml | 3 +++ 4 files changed, 6 insertions(+), 22 deletions(-) delete mode 100644 compose.image.yml diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index a5a0ef2..994fcef 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -18,6 +18,4 @@ jobs: # Parses and merges the compose file; builds and starts nothing. # The Dockerfile is covered by build.yml. - name: Validate compose - run: | - docker compose -f compose.yml config --quiet - docker compose -f compose.yml -f compose.image.yml config --quiet + run: docker compose -f compose.yml config --quiet diff --git a/README.md b/README.md index c7923f8..1c70eb4 100644 --- a/README.md +++ b/README.md @@ -106,14 +106,12 @@ The price is unchanged by the overlay: static runners advertise it from `runners CI publishes the image to `ghcr.io/livepeer/streamdiffusion-livepeer-runner` on `main` and `v*` tags. Tags: `latest` (current `main`), `stable` (latest `v*` release), `1.2` / `1.2.3`, `sha-`. The package is public, so pulling needs no account and no login. An operator then runs it with a `runners.json` like this repo's, pointed at wherever they run the container. -[compose.image.yml](compose.image.yml) swaps the local build for that image, which is the sane path unless you are changing the Dockerfile: +`docker compose up` always builds from source. To run the published image instead, which is the sane path unless you are changing the Dockerfile: ```sh -docker compose -f compose.yml -f compose.image.yml up -d +docker compose up -d --pull always ``` -The base [compose.yml](compose.yml) always builds from source, so the registry is reached only when you ask for it by adding the overlay. It stacks with the on-chain one (`-f compose.yml -f compose.onchain.yml -f compose.image.yml`). - The image is **~15 GB** (torch, TensorRT, ONNX Runtime), which is still close enough to what a GitHub-hosted runner has free that [build.yml](.github/workflows/build.yml) reclaims disk before building and skips the build on pull requests. Building locally is `docker compose build`. ## Development diff --git a/compose.image.yml b/compose.image.yml deleted file mode 100644 index a920a80..0000000 --- a/compose.image.yml +++ /dev/null @@ -1,15 +0,0 @@ -# Run the published image instead of building. Layer it on the offchain base: -# docker compose -f compose.yml -f compose.image.yml up -d -# -# The base file always builds from source, so this overlay is the only way to -# reach the registry; `pull_policy: always` keeps a stale local copy from -# shadowing the published one. Stacks with the on-chain overlay too: -# docker compose -f compose.yml -f compose.onchain.yml -f compose.image.yml up -d -# -# The image is ~15 GB, so pulling it beats a local build on any machine that is -# not going to change the Dockerfile. - -services: - app: - image: ghcr.io/livepeer/streamdiffusion-livepeer-runner:latest - pull_policy: always diff --git a/compose.yml b/compose.yml index e837146..50f682c 100644 --- a/compose.yml +++ b/compose.yml @@ -46,6 +46,9 @@ services: start_period: 5s app: + # `up` always builds; `--pull always` runs the published image instead. + image: ghcr.io/livepeer/streamdiffusion-livepeer-runner:latest + pull_policy: build build: . container_name: streamdiffusion_runner_app # StreamDiffusion needs a GPU; compiled engines + the HF cache live in ./models