Weg van GitHub? Wat de exodus naar Codeberg en self-hosting betekent voor jouw .NET-workflow

112 uur downtime in een jaar, een ICE-contract en verplichte AI. Een praktijkanalyse van de GitHub-exodus, met Forgejo self-hosting via Docker en een werkende .NET CI-pipeline.

Jean-Pierre Broeders

Freelance .NET Developer

10 juli 20269 min. leestijd
Weg van GitHub? Wat de exodus naar Codeberg en self-hosting betekent voor jouw .NET-workflow

Deze week stond een artikel van How-To Geek hoog op Hacker News: "Why developers are ditching GitHub for Codeberg and self-hosting alternatives" (discussie hier, 349 punten en ruim 240 reacties). Het raakt een zenuw. Ghostty vertrok in april, Zig ging al in november 2025, en op de HN-thread lopen de meningen flink uiteen.

Ik ben freelance .NET-developer. Mijn brood komt deels binnen via repositories die op GitHub staan, met CI die op GitHub Actions draait en NuGet-feeds die daaraan hangen. Dus toen ik dit artikel las, was mijn eerste reactie niet "eindelijk, ik ga weg". Het was: hoe erg is het echt, en wat kost het mij als ik iets doe. Die twee vragen ga ik hier eerlijk beantwoorden, met code die je vanavond kunt draaien.

waar de klachten vandaan komen

Even de feiten uit het artikel, want die zijn concreter dan het gemopper op X doet vermoeden. GitHub had tussen mei 2025 en mei 2026 zo'n 112 uur downtime verdeeld over 48 grote storingen. GitHub Actions was verantwoordelijk voor 57 van die incidenten. Dat is geen ruis. Als je pipeline midden op een deploydag stilvalt omdat de runners van iemand anders het begeven, dan voel je dat in je facturatie.

Daarnaast speelt het contract van 200.000 dollar tussen GitHub en ICE, dat zowel bij makers als bij eigen personeel kwaad bloed zette. En er is de AI-richting. CEO Thomas Dohmke zei letterlijk dat developers AI moeten omarmen "or get out of this career". Mitchell Hashimoto, de man achter Ghostty, verwoordde zijn frustratie zo: "It's not a fun place for me to be anymore." Copilot dat overal in je interface geduwd wordt, valt niet bij iedereen goed.

Je hoeft het niet met alle bezwaren eens te zijn om te snappen dat mensen wegrennen. Voor mij weegt de downtime het zwaarst. De rest is principieel, en principes zijn persoonlijk.

de kandidaten, kort

Het artikel noemt een handjevol alternatieven. Codeberg is een Duitse non-profit die draait op Forgejo, met privacy en ethiek als uitgangspunt. Forgejo zelf is een fork van Gitea, en Gitea is een lichtgewicht Git-server geschreven in Go die op bijna niks draait. GitLab kennen de meesten wel, met een complete DevOps-suite en de optie om zelf te hosten. En Sourcehut, voor de mensen die hun workflow via e-mail en patches willen doen.

Voor een .NET-freelancer valt de keuze in de praktijk tussen twee smaken. Optie een: je zet je publieke code op Codeberg en laat GitHub GitHub. Optie twee: je host zelf een Forgejo-instance en houdt de controle in eigen hand. Ik ga voor de tweede uitleggen hoe dat eruitziet, want daar zit de echte les.

Forgejo self-hosten met Docker Compose

Dit is het deel waar mensen bang voor zijn en waar ze niet bang voor hoeven te zijn. Een werkende Forgejo-instance staat in tien minuten. Hier is een compose.yml die ik zelf op een kleine VPS draai:

services:
  forgejo:
    image: codeberg.org/forgejo/forgejo:11
    container_name: forgejo
    restart: unless-stopped
    environment:
      - USER_UID=1000
      - USER_GID=1000
      - FORGEJO__database__DB_TYPE=postgres
      - FORGEJO__database__HOST=db:5432
      - FORGEJO__database__NAME=forgejo
      - FORGEJO__database__USER=forgejo
      - FORGEJO__database__PASSWD=${DB_PASSWORD}
      - FORGEJO__server__DOMAIN=git.jouwdomein.nl
      - FORGEJO__server__ROOT_URL=https://git.jouwdomein.nl/
    volumes:
      - ./forgejo-data:/data
      - /etc/timezone:/etc/timezone:ro
      - /etc/localtime:/etc/localtime:ro
    ports:
      - "3000:3000"
      - "222:22"
    depends_on:
      - db

  db:
    image: postgres:16-alpine
    container_name: forgejo-db
    restart: unless-stopped
    environment:
      - POSTGRES_USER=forgejo
      - POSTGRES_PASSWORD=${DB_PASSWORD}
      - POSTGRES_DB=forgejo
    volumes:
      - ./postgres-data:/var/lib/postgresql/data

Zet je DB_PASSWORD in een .env-bestand naast de compose, en start met docker compose up -d. De web-installer opent op poort 3000. Belangrijk detail: die SSH-poort mapt naar 222 op de host, dus in je Git-remote wordt het ssh://git@git.jouwdomein.nl:222/.... Vergeet dat niet, anders zit je een uur te debuggen waarom git push weigert. Vraag me niet hoe ik dat weet.

Zet er een reverse proxy voor met TLS, bijvoorbeeld Caddy of Traefik, en je hebt HTTPS zonder gedoe. De opslag zit in twee mapjes op je host, dus een backup is letterlijk een tar van forgejo-data en je database-dump. Geen magie.

en de CI dan? Forgejo Actions

Hier komt de verrassing die de meeste mensen niet zien aankomen. Forgejo heeft een eigen Actions-implementatie die de syntax van GitHub Actions grotendeels overneemt. Je YAML verhuist met minimale aanpassingen. Je runner is een los binary dat je registreert bij je instance.

Eerst de runner registreren. Haal een token op onder de admin-instellingen of je repo-instellingen, en dan:

docker run --rm -it \
  -v $(pwd)/runner-data:/data \
  code.forgejo.org/forgejo/runner:6 \
  forgejo-runner register --no-interactive \
    --instance https://git.jouwdomein.nl \
    --token JOUW_RUNNER_TOKEN \
    --name dotnet-runner \
    --labels docker:docker://mcr.microsoft.com/dotnet/sdk:9.0

Die laatste label is het interessante stuk. Ik wijs het label direct naar de officiële .NET SDK-image, zodat mijn jobs in een container met de complete toolchain draaien. Geen setup-dotnet-stap nodig, de SDK zit er al in.

Dan de workflow. Dit bestand komt in je repo onder .forgejo/workflows/ci.yml:

name: build-and-test
on:
  push:
    branches: [main]
  pull_request:

jobs:
  build:
    runs-on: docker
    container:
      image: mcr.microsoft.com/dotnet/sdk:9.0
    steps:
      - uses: actions/checkout@v4

      - name: Restore
        run: dotnet restore

      - name: Build
        run: dotnet build --no-restore -c Release

      - name: Test
        run: dotnet test --no-build -c Release --logger "trx;LogFileName=results.trx"

      - name: Publish artifact
        uses: actions/upload-artifact@v3
        with:
          name: test-results
          path: "**/results.trx"

Als je dit naast je bestaande GitHub Actions-workflow legt, zie je dat het bijna een op een hetzelfde is. actions/checkout en upload-artifact worden gespiegeld vanuit een compatibele action-index, dus die draaien gewoon. De grootste verschillen: je runt op runs-on: docker in plaats van ubuntu-latest, en niet elke exotische marketplace-action bestaat in het Forgejo-ecosysteem. Voor een standaard .NET-pipeline met restore, build, test en publish loop je daar zelden tegenaan.

mijn eigenlijke advies: niet migreren, mirroren

Nu het punt waar ik anders over denk dan de luidste stemmen op HN. Volledig weglopen van GitHub is voor de meeste freelancers een slecht idee. Niet vanwege loyaliteit, maar omdat je klanten, je open-source dependencies en je netwerk daar zitten. Een pull request van een onbekende krijg je op je zelfgehoste Forgejo bijna nooit.

Wat wel slim is: je maakt Forgejo je bron van waarheid en je mirrort naar GitHub. Zo houd je controle over je code en je CI, terwijl je zichtbaar blijft op het platform waar de wereld kijkt. Forgejo heeft push-mirroring ingebouwd, maar je kunt het ook expliciet in Git regelen:

# Forgejo is je origin, GitHub is een extra push-target
git remote add origin ssh://git@git.jouwdomein.nl:222/jp/mijn-project.git
git remote set-url --add --push origin ssh://git@git.jouwdomein.nl:222/jp/mijn-project.git
git remote set-url --add --push origin git@github.com:jp/mijn-project.git

# Eén push, twee bestemmingen
git push origin main

Nu schrijft elke git push naar allebei. Valt GitHub Actions een dag uit tijdens een release, dan draait je Forgejo-runner gewoon door en lever je alsnog op tijd. Dat is precies het scenario dat die 112 uur downtime pijnlijk maakte, en je hebt het met drie regels config afgevangen.

Wil je andersom werken, met GitHub als bron en Forgejo als warme backup, dan kun je in Forgejo een "pull mirror" instellen die periodiek van GitHub trekt. Ik draai zelf de eerste variant, omdat ik mijn CI niet wil laten afhangen van een status-pagina waar ik geen invloed op heb.

je bestaande repo verhuizen zonder je historie te verliezen

Een remote toevoegen is één ding. Je jarenlange issues, pull requests en labels meenemen is een ander verhaal, en daar knelt het bij een echte verhuizing. Forgejo heeft hiervoor een migratie-tool die via de GitHub API je repo inclusief metadata binnenhaalt. In de web-UI kies je "New Migration", plak je de GitHub-URL en een personal access token, en vink je aan wat je mee wilt: issues, PR's, releases, wiki, milestones. Dat werkt beter dan je zou verwachten. Je PR-geschiedenis komt over als issues met de originele discussie eronder.

Waar je op moet letten als .NET'er: je NuGet-packages. Publiceer je naar GitHub Packages, dan verhuist die feed niet vanzelf mee. Forgejo heeft een eigen package-registry die NuGet spreekt, dus je kunt je nuget.config ombouwen naar je eigen instance:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <packageSources>
    <add key="forgejo" value="https://git.jouwdomein.nl/api/packages/jp/nuget/index.json" />
  </packageSources>
</configuration>

Een dotnet nuget push met een API-key uit je Forgejo-profiel duwt je package er zo in. Voor interne bibliotheken die je alleen zelf gebruikt is dat prima. Publieke packages laat ik gewoon op nuget.org staan, want daar zoekt iedereen.

de trade-offs die niemand hardop zegt

Self-hosting is geen gratis lunch. Je runt nu zelf een database, je moet backups testen (een backup die je nooit teruggezet hebt bestaat niet), en je bent zelf verantwoordelijk voor security-updates van je Forgejo-image. Reken op een half uur per maand onderhoud. Voor mij is dat de moeite waard, voor een team van twintig met een eigen platform-afdeling ligt de rekensom anders.

En Codeberg? Prima voor open source, maar het is een non-profit met vrijwilligers en beperkte capaciteit. Ze hebben de afgelopen tijd meerdere keren aangegeven dat de groei hard gaat. Gooi er niet zomaar je zakelijke productie-CI op zonder na te denken over wat je terugdoet, bijvoorbeeld met een donatie of een eigen runner.

De echte winst zit niet in welk logo er in je browser staat. Het zit in het besef dat Git gedecentraliseerd is ontworpen en dat je meerdere remotes kunt hebben. Eén platform als single point of failure is een keuze, geen natuurwet. Die 112 uur downtime is een goede aanleiding om die keuze opnieuw te maken.

Begin klein. Zet dit weekend een Forgejo-instance op voor één project, hang er die .NET-workflow aan, en push naar allebei. Merk je na een maand dat je het fijn vindt, dan schuif je de rest erin. Merk je dat het niks voor je is, dan heb je een avond geleerd hoe zelfgehoste Git werkt en ben je niks kwijt.

Wil je op de hoogte blijven?

Schrijf je in voor mijn nieuwsbrief of neem contact op voor freelance projecten.

Neem Contact Op