← Home

Mirroring GitHub to Codeberg Without a Third-Party Action

You do not need an external action for this mirror setup.

A standard workflow using git push over SSH is enough.

Goal

  • Mirror from GitHub to Codeberg
  • No third-party dependency
  • Only branches and tags (clean repo, no origin/* refs)

1. Generate an SSH key

Create a dedicated key pair locally:

ssh-keygen -t ed25519 -C "github-actions-codeberg-mirror" -f ~/.ssh/codeberg_mirror -N ""

2. Store keys in the correct places

Important: do not mix up public and private keys.

  • Codeberg Deploy Key (Repository -> Settings -> Deploy keys): paste the content of ~/.ssh/codeberg_mirror.pub
  • Enable Allow write access
  • GitHub Secret (Settings -> Secrets and variables -> Actions): create CODEBERG_SSH with the content of ~/.ssh/codeberg_mirror (private key)

If a key form shows
Key is invalid. You must supply a key in OpenSSH public key format,
you pasted a private key where a public key is expected.

3. Add the workflow

File: .github/workflows/sync-mirror.yml

name: 🪞 Mirror to Codeberg
on:
  push:
    branches: [main]
  workflow_dispatch:
  schedule:
    - cron: "30 0 * * 0"

jobs:
  codeberg:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Configure SSH
        run: |
          mkdir -p ~/.ssh
          printf '%s\n' "${{ secrets.CODEBERG_SSH }}" > ~/.ssh/id_ed25519
          chmod 600 ~/.ssh/id_ed25519
          ssh-keyscan -H codeberg.org >> ~/.ssh/known_hosts
          cat <<'EOF' > ~/.ssh/config
          Host codeberg.org
            HostName codeberg.org
            User git
            IdentityFile ~/.ssh/id_ed25519
            IdentitiesOnly yes
          EOF

      - name: Verify SSH access
        run: |
          ssh -T git@codeberg.org || true
          git ls-remote git@codeberg.org:adrian-altner/website.git > /dev/null

      - name: Mirror to Codeberg
        run: |
          git remote add mirror git@codeberg.org:adrian-altner/website.git

          # Remove previously mirrored remote-tracking refs (for example refs/remotes/origin/*).
          while IFS= read -r ref; do
            git push mirror ":${ref}"
          done < <(git ls-remote --refs mirror 'refs/remotes/*' | awk '{print $2}')

          # Mirror only real branches and tags.
          git push --prune mirror \
            +refs/heads/*:refs/heads/* \
            +refs/tags/*:refs/tags/*

4. Expected behavior during testing

With successful SSH auth, this Forgejo message is expected:

... successfully authenticated ... but Forgejo does not provide shell access.

This is not an error. Git operations (fetch, push) still work.

Result

The setup mirrors reliably to Codeberg, runs automatically on pushes to main, can be started manually via workflow_dispatch, and also runs weekly via cron.

← Home