Skip to content

Commit e9c459f

Browse files
d-csclaude
andauthored
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>
1 parent d7028e2 commit e9c459f

4 files changed

Lines changed: 43 additions & 7 deletions

File tree

.github/workflows/publish-webapp.yml

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,21 @@ on:
1414
type: string
1515
required: false
1616
default: ""
17+
image_registry:
18+
description: The registry namespace to publish under (e.g. ghcr.io/<owner>)
19+
type: string
20+
required: false
21+
default: ""
1722
outputs:
1823
version:
1924
description: The published image tag
2025
value: ${{ jobs.publish.outputs.version }}
2126
short_sha:
2227
description: Short commit SHA of the published build
2328
value: ${{ jobs.publish.outputs.short_sha }}
29+
image_repo:
30+
description: The image repository the build was published to (without tag)
31+
value: ${{ jobs.publish.outputs.image_repo }}
2432
secrets:
2533
SENTRY_AUTH_TOKEN:
2634
required: false
@@ -33,6 +41,7 @@ jobs:
3341
outputs:
3442
version: ${{ steps.get_tag.outputs.tag }}
3543
short_sha: ${{ steps.get_commit.outputs.sha_short }}
44+
image_repo: ${{ steps.set_tags.outputs.image_repo }}
3645
steps:
3746
- name: 🏭 Setup Depot CLI
3847
uses: depot/setup-action@15c09a5f77a0840ad4bce955686522a257853461 # v1.7.1
@@ -57,17 +66,22 @@ jobs:
5766
- name: 📛 Set the tags
5867
id: set_tags
5968
run: |
60-
ref_without_tag=ghcr.io/triggerdotdev/trigger.dev
61-
image_tags=$ref_without_tag:${STEPS_GET_TAG_OUTPUTS_TAG}
69+
# The registry namespace is resolved by the caller (defaulting to
70+
# ghcr.io/<owner>, overridable via the IMAGE_REGISTRY repository
71+
# variable); the webapp image lives at <registry>/<repo-name>. A fork
72+
# therefore publishes to its own package automatically.
73+
image_tags=$REF_WITHOUT_TAG:${STEPS_GET_TAG_OUTPUTS_TAG}
6274
6375
# when pushing the mutable main tag, also push an immutable-by-convention
6476
# full-commit-sha tag so a commit can be resolved to a specific digest
6577
if [[ "${STEPS_GET_TAG_OUTPUTS_TAG}" == "main" ]]; then
66-
image_tags=$image_tags,$ref_without_tag:${GITHUB_SHA}
78+
image_tags=$image_tags,$REF_WITHOUT_TAG:${GITHUB_SHA}
6779
fi
6880
6981
echo "image_tags=${image_tags}" >> "$GITHUB_OUTPUT"
82+
echo "image_repo=${REF_WITHOUT_TAG}" >> "$GITHUB_OUTPUT"
7083
env:
84+
REF_WITHOUT_TAG: ${{ format('{0}/{1}', inputs.image_registry || vars.IMAGE_REGISTRY || format('ghcr.io/{0}', github.repository_owner), github.event.repository.name) }}
7185
STEPS_GET_TAG_OUTPUTS_TAG: ${{ steps.get_tag.outputs.tag }}
7286
STEPS_GET_TAG_OUTPUTS_IS_SEMVER: ${{ steps.get_tag.outputs.is_semver }}
7387

@@ -122,6 +136,6 @@ jobs:
122136
continue-on-error: true
123137
uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 # v4.1.0
124138
with:
125-
subject-name: ghcr.io/triggerdotdev/trigger.dev
139+
subject-name: ${{ steps.set_tags.outputs.image_repo }}
126140
subject-digest: ${{ steps.build_push.outputs.digest }}
127141
push-to-registry: true

.github/workflows/publish-worker-v4.yml

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,11 @@ on:
88
type: string
99
required: false
1010
default: ""
11+
image_registry:
12+
description: The registry namespace to publish under (e.g. ghcr.io/<owner>)
13+
type: string
14+
required: false
15+
default: ""
1116
push:
1217
tags:
1318
- "re2-test-*"
@@ -65,11 +70,15 @@ jobs:
6570
- name: 📛 Set tags to push
6671
id: set_tags
6772
run: |
68-
ref_without_tag=ghcr.io/triggerdotdev/${STEPS_GET_REPOSITORY_OUTPUTS_REPO}
73+
# Resolved by the caller when invoked from publish.yml; falls back to the
74+
# IMAGE_REGISTRY repository variable (or ghcr.io/<owner>) for the direct
75+
# push triggers above, so a fork publishes to its own namespace.
76+
ref_without_tag=${IMAGE_REGISTRY}/${STEPS_GET_REPOSITORY_OUTPUTS_REPO}
6977
image_tags=$ref_without_tag:${STEPS_GET_TAG_OUTPUTS_TAG}
7078
7179
echo "image_tags=${image_tags}" >> "$GITHUB_OUTPUT"
7280
env:
81+
IMAGE_REGISTRY: ${{ inputs.image_registry || vars.IMAGE_REGISTRY || format('ghcr.io/{0}', github.repository_owner) }}
7382
STEPS_GET_REPOSITORY_OUTPUTS_REPO: ${{ steps.get_repository.outputs.repo }}
7483
STEPS_GET_TAG_OUTPUTS_TAG: ${{ steps.get_tag.outputs.tag }}
7584
STEPS_GET_TAG_OUTPUTS_IS_SEMVER: ${{ steps.get_tag.outputs.is_semver }}

.github/workflows/publish-worker.yml

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,11 @@ on:
88
type: string
99
required: false
1010
default: ""
11+
image_registry:
12+
description: The registry namespace to publish under (e.g. ghcr.io/<owner>)
13+
type: string
14+
required: false
15+
default: ""
1116
secrets:
1217
DOCKERHUB_USERNAME:
1318
required: false
@@ -83,7 +88,10 @@ jobs:
8388
docker tag infra_image "$REGISTRY/$REPOSITORY:$IMAGE_TAG"
8489
docker push "$REGISTRY/$REPOSITORY:$IMAGE_TAG"
8590
env:
86-
REGISTRY: ghcr.io/triggerdotdev
91+
# Resolved by the caller when invoked from publish.yml; falls back to the
92+
# IMAGE_REGISTRY repository variable (or ghcr.io/<owner>) for the direct
93+
# push triggers above, so a fork publishes to its own namespace.
94+
REGISTRY: ${{ inputs.image_registry || vars.IMAGE_REGISTRY || format('ghcr.io/{0}', github.repository_owner) }}
8795
REPOSITORY: ${{ steps.get_repository.outputs.repo }}
8896
IMAGE_TAG: ${{ steps.get_tag.outputs.tag }}
8997

.github/workflows/publish.yml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,9 @@ jobs:
7474
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
7575
with:
7676
image_tag: ${{ inputs.image_tag }}
77+
# Target registry namespace. Defaults to ghcr.io/<owner> so a fork publishes
78+
# to its own namespace; set the IMAGE_REGISTRY repository variable to override.
79+
image_registry: ${{ vars.IMAGE_REGISTRY || format('ghcr.io/{0}', github.repository_owner) }}
7780

7881
publish-worker:
7982
needs: [typecheck]
@@ -86,6 +89,7 @@ jobs:
8689
DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}
8790
with:
8891
image_tag: ${{ inputs.image_tag }}
92+
image_registry: ${{ vars.IMAGE_REGISTRY || format('ghcr.io/{0}', github.repository_owner) }}
8993

9094
publish-worker-v4:
9195
needs: [typecheck]
@@ -96,6 +100,7 @@ jobs:
96100
uses: ./.github/workflows/publish-worker-v4.yml
97101
with:
98102
image_tag: ${{ inputs.image_tag }}
103+
image_registry: ${{ vars.IMAGE_REGISTRY || format('ghcr.io/{0}', github.repository_owner) }}
99104

100105
# OS-level CVE scan of the image just published above. Report-only (writes to
101106
# the run summary); runs alongside the worker publishes and never blocks them.
@@ -106,4 +111,4 @@ jobs:
106111
packages: read # pull the just-published image from GHCR
107112
uses: ./.github/workflows/trivy-image-webapp.yml
108113
with:
109-
image-ref: ghcr.io/triggerdotdev/trigger.dev:${{ needs.publish-webapp.outputs.version }}
114+
image-ref: ${{ needs.publish-webapp.outputs.image_repo }}:${{ needs.publish-webapp.outputs.version }}

0 commit comments

Comments
 (0)