ci: allow forks to override published container image namespace (#3866)
## Summary
The container publish workflows hardcoded `ghcr.io/triggerdotdev/...` as
the image destination. As a result, a fork that builds on push-to-`main`
(or on the worker publish tags) would attempt to push to — and attest —
the upstream packages rather than its own, which fails on permissions
and is surprising besides.
This makes the image destination configurable via a single
`IMAGE_REGISTRY` repository variable, while leaving the upstream
defaults byte-identical:
- **Single source of truth** (`publish.yml`): a `resolve-registry` job
resolves the target registry namespace once — `IMAGE_REGISTRY`
repository variable, defaulting to `ghcr.io/${{ github.repository_owner
}}` — and passes it down to every publish job as an `image_registry`
input. So a fork publishes to its own namespace automatically with no
configuration.
- **Webapp** (`publish-webapp.yml`): the image now lives at
`<registry>/<repo-name>` (e.g. `ghcr.io/<owner>/trigger.dev`). The
provenance attestation and the downstream Trivy scan follow the same
computed repo via the `image_repo` workflow output.
- **Workers** (`publish-worker.yml`, `publish-worker-v4.yml`): build
under `<registry>/<worker-name>`. They keep a `vars.IMAGE_REGISTRY ||
ghcr.io/<owner>` fallback so they still resolve correctly on their
direct `infra-*` / `re2-*` push triggers (which bypass the parent
workflow).
A single `IMAGE_REGISTRY` namespace variable now governs both webapp and
workers (the earlier `WEBAPP_IMAGE_REPO` full-path override is dropped,
removing the full-path/namespace asymmetry). When `IMAGE_REGISTRY` is
unset, every resolved image name is exactly what it is today, so there
is no change for this repo.
## Test plan
- [x] `actionlint` passes on all four workflows
- [ ] On merge, confirm the webapp publish still pushes
`ghcr.io/triggerdotdev/trigger.dev:main` + the commit-SHA tag (defaults
unchanged)
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -14,6 +14,11 @@ on:
|
||||
type: string
|
||||
required: false
|
||||
default: ""
|
||||
image_registry:
|
||||
description: The registry namespace to publish under (e.g. ghcr.io/<owner>)
|
||||
type: string
|
||||
required: false
|
||||
default: ""
|
||||
outputs:
|
||||
version:
|
||||
description: The published image tag
|
||||
@@ -21,6 +26,9 @@ on:
|
||||
short_sha:
|
||||
description: Short commit SHA of the published build
|
||||
value: ${{ jobs.publish.outputs.short_sha }}
|
||||
image_repo:
|
||||
description: The image repository the build was published to (without tag)
|
||||
value: ${{ jobs.publish.outputs.image_repo }}
|
||||
secrets:
|
||||
SENTRY_AUTH_TOKEN:
|
||||
required: false
|
||||
@@ -33,6 +41,7 @@ jobs:
|
||||
outputs:
|
||||
version: ${{ steps.get_tag.outputs.tag }}
|
||||
short_sha: ${{ steps.get_commit.outputs.sha_short }}
|
||||
image_repo: ${{ steps.set_tags.outputs.image_repo }}
|
||||
steps:
|
||||
- name: 🏭 Setup Depot CLI
|
||||
uses: depot/setup-action@15c09a5f77a0840ad4bce955686522a257853461 # v1.7.1
|
||||
@@ -57,17 +66,22 @@ jobs:
|
||||
- name: 📛 Set the tags
|
||||
id: set_tags
|
||||
run: |
|
||||
ref_without_tag=ghcr.io/triggerdotdev/trigger.dev
|
||||
image_tags=$ref_without_tag:${STEPS_GET_TAG_OUTPUTS_TAG}
|
||||
# The registry namespace is resolved by the caller (defaulting to
|
||||
# ghcr.io/<owner>, overridable via the IMAGE_REGISTRY repository
|
||||
# variable); the webapp image lives at <registry>/<repo-name>. A fork
|
||||
# therefore publishes to its own package automatically.
|
||||
image_tags=$REF_WITHOUT_TAG:${STEPS_GET_TAG_OUTPUTS_TAG}
|
||||
|
||||
# when pushing the mutable main tag, also push an immutable-by-convention
|
||||
# full-commit-sha tag so a commit can be resolved to a specific digest
|
||||
if [[ "${STEPS_GET_TAG_OUTPUTS_TAG}" == "main" ]]; then
|
||||
image_tags=$image_tags,$ref_without_tag:${GITHUB_SHA}
|
||||
image_tags=$image_tags,$REF_WITHOUT_TAG:${GITHUB_SHA}
|
||||
fi
|
||||
|
||||
echo "image_tags=${image_tags}" >> "$GITHUB_OUTPUT"
|
||||
echo "image_repo=${REF_WITHOUT_TAG}" >> "$GITHUB_OUTPUT"
|
||||
env:
|
||||
REF_WITHOUT_TAG: ${{ format('{0}/{1}', inputs.image_registry || vars.IMAGE_REGISTRY || format('ghcr.io/{0}', github.repository_owner), github.event.repository.name) }}
|
||||
STEPS_GET_TAG_OUTPUTS_TAG: ${{ steps.get_tag.outputs.tag }}
|
||||
STEPS_GET_TAG_OUTPUTS_IS_SEMVER: ${{ steps.get_tag.outputs.is_semver }}
|
||||
|
||||
@@ -122,6 +136,6 @@ jobs:
|
||||
continue-on-error: true
|
||||
uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 # v4.1.0
|
||||
with:
|
||||
subject-name: ghcr.io/triggerdotdev/trigger.dev
|
||||
subject-name: ${{ steps.set_tags.outputs.image_repo }}
|
||||
subject-digest: ${{ steps.build_push.outputs.digest }}
|
||||
push-to-registry: true
|
||||
|
||||
@@ -8,6 +8,11 @@ on:
|
||||
type: string
|
||||
required: false
|
||||
default: ""
|
||||
image_registry:
|
||||
description: The registry namespace to publish under (e.g. ghcr.io/<owner>)
|
||||
type: string
|
||||
required: false
|
||||
default: ""
|
||||
push:
|
||||
tags:
|
||||
- "re2-test-*"
|
||||
@@ -65,11 +70,15 @@ jobs:
|
||||
- name: 📛 Set tags to push
|
||||
id: set_tags
|
||||
run: |
|
||||
ref_without_tag=ghcr.io/triggerdotdev/${STEPS_GET_REPOSITORY_OUTPUTS_REPO}
|
||||
# Resolved by the caller when invoked from publish.yml; falls back to the
|
||||
# IMAGE_REGISTRY repository variable (or ghcr.io/<owner>) for the direct
|
||||
# push triggers above, so a fork publishes to its own namespace.
|
||||
ref_without_tag=${IMAGE_REGISTRY}/${STEPS_GET_REPOSITORY_OUTPUTS_REPO}
|
||||
image_tags=$ref_without_tag:${STEPS_GET_TAG_OUTPUTS_TAG}
|
||||
|
||||
echo "image_tags=${image_tags}" >> "$GITHUB_OUTPUT"
|
||||
env:
|
||||
IMAGE_REGISTRY: ${{ inputs.image_registry || vars.IMAGE_REGISTRY || format('ghcr.io/{0}', github.repository_owner) }}
|
||||
STEPS_GET_REPOSITORY_OUTPUTS_REPO: ${{ steps.get_repository.outputs.repo }}
|
||||
STEPS_GET_TAG_OUTPUTS_TAG: ${{ steps.get_tag.outputs.tag }}
|
||||
STEPS_GET_TAG_OUTPUTS_IS_SEMVER: ${{ steps.get_tag.outputs.is_semver }}
|
||||
|
||||
@@ -8,6 +8,11 @@ on:
|
||||
type: string
|
||||
required: false
|
||||
default: ""
|
||||
image_registry:
|
||||
description: The registry namespace to publish under (e.g. ghcr.io/<owner>)
|
||||
type: string
|
||||
required: false
|
||||
default: ""
|
||||
secrets:
|
||||
DOCKERHUB_USERNAME:
|
||||
required: false
|
||||
@@ -83,7 +88,10 @@ jobs:
|
||||
docker tag infra_image "$REGISTRY/$REPOSITORY:$IMAGE_TAG"
|
||||
docker push "$REGISTRY/$REPOSITORY:$IMAGE_TAG"
|
||||
env:
|
||||
REGISTRY: ghcr.io/triggerdotdev
|
||||
# Resolved by the caller when invoked from publish.yml; falls back to the
|
||||
# IMAGE_REGISTRY repository variable (or ghcr.io/<owner>) for the direct
|
||||
# push triggers above, so a fork publishes to its own namespace.
|
||||
REGISTRY: ${{ inputs.image_registry || vars.IMAGE_REGISTRY || format('ghcr.io/{0}', github.repository_owner) }}
|
||||
REPOSITORY: ${{ steps.get_repository.outputs.repo }}
|
||||
IMAGE_TAG: ${{ steps.get_tag.outputs.tag }}
|
||||
|
||||
|
||||
@@ -74,6 +74,9 @@ jobs:
|
||||
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
||||
with:
|
||||
image_tag: ${{ inputs.image_tag }}
|
||||
# Target registry namespace. Defaults to ghcr.io/<owner> so a fork publishes
|
||||
# to its own namespace; set the IMAGE_REGISTRY repository variable to override.
|
||||
image_registry: ${{ vars.IMAGE_REGISTRY || format('ghcr.io/{0}', github.repository_owner) }}
|
||||
|
||||
publish-worker:
|
||||
needs: [typecheck]
|
||||
@@ -86,6 +89,7 @@ jobs:
|
||||
DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
with:
|
||||
image_tag: ${{ inputs.image_tag }}
|
||||
image_registry: ${{ vars.IMAGE_REGISTRY || format('ghcr.io/{0}', github.repository_owner) }}
|
||||
|
||||
publish-worker-v4:
|
||||
needs: [typecheck]
|
||||
@@ -96,6 +100,7 @@ jobs:
|
||||
uses: ./.github/workflows/publish-worker-v4.yml
|
||||
with:
|
||||
image_tag: ${{ inputs.image_tag }}
|
||||
image_registry: ${{ vars.IMAGE_REGISTRY || format('ghcr.io/{0}', github.repository_owner) }}
|
||||
|
||||
# OS-level CVE scan of the image just published above. Report-only (writes to
|
||||
# the run summary); runs alongside the worker publishes and never blocks them.
|
||||
@@ -106,4 +111,4 @@ jobs:
|
||||
packages: read # pull the just-published image from GHCR
|
||||
uses: ./.github/workflows/trivy-image-webapp.yml
|
||||
with:
|
||||
image-ref: ghcr.io/triggerdotdev/trigger.dev:${{ needs.publish-webapp.outputs.version }}
|
||||
image-ref: ${{ needs.publish-webapp.outputs.image_repo }}:${{ needs.publish-webapp.outputs.version }}
|
||||
|
||||
Reference in New Issue
Block a user