Skip to content

Commit 19b0eb8

Browse files
committed
feat: use skopeo to copy manifests from GHCR to Docker Hub
- Create multi-arch manifests on GHCR first (no rate limits) - Use skopeo copy --all to replicate to Docker Hub repos - Avoids Docker Hub pull rate limits during manifest creation - Keeps retry logic for skopeo copy commands
1 parent 71336b7 commit 19b0eb8

File tree

3 files changed

+42
-18
lines changed

3 files changed

+42
-18
lines changed

.github/workflows/build-group.yml

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,11 @@ on:
1010
required: true
1111
type: boolean
1212
default: false
13-
manifestCmds:
13+
ghcrManifestCmds:
14+
required: false
15+
type: string
16+
default: ""
17+
skopeoCopyCmds:
1418
required: false
1519
type: string
1620
default: ""
@@ -57,7 +61,7 @@ jobs:
5761
manifest:
5862
name: 📦 Manifest
5963
needs: build
60-
if: ${{ inputs.push && inputs.manifestCmds != '' }}
64+
if: ${{ inputs.push && inputs.ghcrManifestCmds != '' }}
6165
runs-on: ubuntu-latest
6266
concurrency:
6367
group: docker-manifest
@@ -72,12 +76,22 @@ jobs:
7276
registry: ghcr.io
7377
username: ${{ github.actor }}
7478
password: ${{ secrets.GITHUB_TOKEN }}
75-
- name: Create multi-arch manifests
79+
- name: Create GHCR multi-arch manifests
80+
run: |
81+
echo '${{ inputs.ghcrManifestCmds }}' | while IFS= read -r cmd; do
82+
[ -z "$cmd" ] && continue
83+
eval "$cmd"
84+
done
85+
- name: Copy to Docker Hub with skopeo
86+
env:
87+
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
88+
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
89+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
7690
run: |
77-
echo '${{ inputs.manifestCmds }}' | while IFS= read -r cmd; do
91+
echo '${{ inputs.skopeoCopyCmds }}' | while IFS= read -r cmd; do
7892
[ -z "$cmd" ] && continue
7993
for attempt in 1 2 3 4 5; do
80-
if eval "$cmd"; then
94+
if $cmd --src-creds="${{ github.actor }}:${GITHUB_TOKEN}" --dest-creds="${DOCKER_USERNAME}:${DOCKER_PASSWORD}"; then
8195
break
8296
fi
8397
echo "Attempt $attempt failed, waiting ${attempt}0s..."

.github/workflows/build.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,5 +36,6 @@ jobs:
3636
with:
3737
tasks: ${{ toJson(matrix.tasks) }}
3838
push: ${{ github.ref == 'refs/heads/master' || github.event_name == 'schedule' }}
39-
manifestCmds: ${{ matrix.manifestCmds }}
39+
ghcrManifestCmds: ${{ matrix.ghcrManifestCmds }}
40+
skopeoCopyCmds: ${{ matrix.skopeoCopyCmds }}
4041
secrets: inherit

src/main.ts

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,9 @@ export interface BuildTask {
2727
cacheTo: string;
2828
}
2929

30-
const REPOS = [
31-
"hertzg/rtl433",
32-
"hertzg/rtl_433",
33-
"ghcr.io/hertzg/rtl_433_docker",
34-
];
30+
const GHCR_REPO = "ghcr.io/hertzg/rtl_433_docker";
31+
const DOCKER_HUB_REPOS = ["hertzg/rtl433", "hertzg/rtl_433"];
32+
const REPOS = [...DOCKER_HUB_REPOS, GHCR_REPO];
3533

3634
const MAX_RELEASE_VERSIONS = 3;
3735

@@ -80,13 +78,18 @@ const platformToRunner = (platform: string) => {
8078
return "ubuntu-24.04";
8179
};
8280

83-
// Generate manifest create command for a tag across all platforms
84-
const generateManifestCmd = (repo: string, tag: string, platforms: string[]): string => {
85-
const manifest = `${repo}:${tag}`;
81+
// Generate GHCR manifest create command for a tag across all platforms
82+
const generateGhcrManifestCmd = (tag: string, platforms: string[]): string => {
83+
const manifest = `${GHCR_REPO}:${tag}`;
8684
const platformImages = platforms.map((p) => `${manifest}-${tagify(p)}`).join(" ");
8785
return `docker buildx imagetools create -t ${manifest} ${platformImages}`;
8886
};
8987

88+
// Generate skopeo copy command to copy from GHCR to Docker Hub
89+
const generateSkopeoCopyCmd = (tag: string, destRepo: string): string => {
90+
return `skopeo copy --all docker://${GHCR_REPO}:${tag} docker://${destRepo}:${tag}`;
91+
};
92+
9093
interface GroupData {
9194
tasks: TaskGroupEntry[];
9295
originalTask: BuildTask;
@@ -119,15 +122,21 @@ for (const task of tasks) {
119122
}
120123

121124
const groupEntries = Object.entries(groups).map(([key, { tasks, originalTask }]) => {
122-
// Generate all manifest commands for this group (all repos × all tags)
123-
const manifestCmds = REPOS.flatMap((repo) =>
124-
originalTask.tags.map((tag) => generateManifestCmd(repo, tag, originalTask.platforms))
125+
// Step 1: Create manifests on GHCR (no rate limits)
126+
const ghcrManifestCmds = originalTask.tags
127+
.map((tag) => generateGhcrManifestCmd(tag, originalTask.platforms))
128+
.join("\n");
129+
130+
// Step 2: Copy from GHCR to Docker Hub repos using skopeo
131+
const skopeoCopyCmds = DOCKER_HUB_REPOS.flatMap((repo) =>
132+
originalTask.tags.map((tag) => generateSkopeoCopyCmd(tag, repo))
125133
).join("\n");
126134

127135
return {
128136
name: key,
129137
tasks,
130-
manifestCmds,
138+
ghcrManifestCmds,
139+
skopeoCopyCmds,
131140
};
132141
});
133142
setOutput("matrix", groupEntries);

0 commit comments

Comments
 (0)