Skip to content

dockerio

dockerio #282

Workflow file for this run

name: dockerio
on:
push:
workflow_dispatch:
inputs:
image:
description: 'Docker Hub image (e.g., nginx, library/alpine)'
required: true
type: string
schedule:
- cron: '0 0 * * *' # Daily at midnight UTC
env:
# Define images to mirror (format: source -> mirrored to ghcr.io/$REPO/docker.io/source)
IMAGES: |
netbirdio/management
netbirdio/dashboard
netbirdio/relay
netbirdio/signal
library/haproxy
library/busybox
library/alpine
rclone/rclone
kanidm/server
pingcap/tcpcopy
weechat/weechat
jobs:
# Build matrix of images to mirror
prepare:
runs-on: ubuntu-latest
outputs:
matrix: ${{ steps.set-matrix.outputs.matrix }}
steps:
- id: set-matrix
run: |
if [ -n "${{ inputs.image }}" ]; then
# Manual trigger - single image
echo "matrix=[\"${{ inputs.image }}\"]" >> $GITHUB_OUTPUT
else
# Scheduled - all images from IMAGES env
MATRIX=$(echo "$IMAGES" | grep -v '^$' | jq -R -s -c 'split("\n") | map(select(length > 0))')
echo "matrix=$MATRIX" >> $GITHUB_OUTPUT
fi
mirror:
needs: prepare
runs-on: ubuntu-latest
permissions:
packages: write
strategy:
fail-fast: false
max-parallel: 4 # Parallel image mirroring
matrix:
image: ${{ fromJson(needs.prepare.outputs.matrix) }}
steps:
- name: Setup crane
uses: imjasonh/setup-crane@v0.4
- name: Login to GHCR
run: crane auth login ghcr.io -u ${{ github.actor }} -p ${{ secrets.GITHUB_TOKEN }}
- name: Login to Docker Hub (optional, increases rate limits)
env:
DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}
run: |
if [ -n "$DOCKERHUB_TOKEN" ]; then
crane auth login docker.io -u "$DOCKERHUB_USERNAME" -p "$DOCKERHUB_TOKEN"
fi
continue-on-error: true
- name: Mirror ${{ matrix.image }}
run: |
SOURCE="docker.io/${{ matrix.image }}"
DEST="ghcr.io/${{ github.repository }}/docker.io/${{ matrix.image }}"
# Retry with exponential backoff
retry() {
local max=5 delay=30 attempt=1
while [ $attempt -le $max ]; do
if "$@" 2>&1; then return 0; fi
[ $attempt -lt $max ] && echo "⏳ Retry $attempt/$max in ${delay}s..." && sleep $delay && delay=$((delay * 2))
attempt=$((attempt + 1))
done
return 1
}
# Copy single tag if digest differs
copy_tag() {
local tag="$1"
local src_digest dst_digest
src_digest=$(retry crane digest "$SOURCE:$tag" 2>/dev/null) || return 1
dst_digest=$(crane digest "$DEST:$tag" 2>/dev/null) || dst_digest=""
if [ "$src_digest" = "$dst_digest" ]; then
echo "⏭️ $tag (unchanged)"
else
retry crane copy "$SOURCE:$tag" "$DEST:$tag" && echo "✅ $tag"
fi
}
echo "Mirroring: $SOURCE -> $DEST"
# Get last 10 tags + ensure latest is included first
TAGS=$(retry crane ls "$SOURCE")
if retry crane manifest "$SOURCE:latest" >/dev/null 2>&1; then
TAGS=$(echo -e "latest\n$TAGS" | awk '!seen[$0]++')
fi
echo "Tags: $TAGS"
# Export for subshells
export SOURCE DEST
export -f retry copy_tag
# Copy in parallel
echo "$TAGS" | xargs -P4 -I{} bash -c 'copy_tag "{}"'