La mascotte ours polaire de NordBastion, debout dans une sombre voûte de pierre nordique à côté d'une baie de serveurs, une patte posée sur un diagramme holographique cyan flottant représentant l'historique Git avec trois branches qui fusionnent, un coffre-fort runique scellé contenant du code source posé sur l'établi à ses côtés, et une lumière d'aurore boréale tombant à travers une haute fenêtre en ogive
Mode d'emploi · Infrastructure pour développeurs·18 min de lecture · 45 min de pratique

Votre propre serveur Git.
Le dépôt, c'est l'organigramme.

Un dépôt privé est un enregistrement daté de ce que vous construisez, avec qui vous le construisez, et où vous en êtes — et sur une plateforme, cet enregistrement est détenu par une entreprise, dans la juridiction de quelqu'un d'autre. Six étapes pour le déplacer sur une machine que vous louez anonymement : Forgejo derrière Caddy, PostgreSQL en dessous, push SSH sur un port qui lui est propre, un runner Actions qui ne voit jamais vos secrets de production, et une sauvegarde que vous avez réellement restaurée. La machine coûte $3.90 par mois. Testé sur Debian 12.

Les six étapes
  1. 01

    Choisir

    Dépôt nu, Forgejo, GitLab

  2. 02

    Provisionner

    Ports décidés avant le démarrage

  3. 03

    Déployer

    Forgejo, Postgres, Caddy

  4. 04

    Pousser

    SSH, clés de déploiement, signatures

  5. 05

    Construire

    Un runner, tenu à distance

  6. 06

    Conserver

    Miroirs, sauvegardes, mises à jour

Avant de commencer · Ce que détient la plateforme

Le code est la partie la moins intéressante. Les métadonnées sont l'actif.

Presque tous les débats sur l'auto-hébergement de Git portent sur le code source, alors que le code source est la partie qui compte le moins. La plupart du temps, il n'a rien de remarquable, une grande partie n'est qu'une fine couche par-dessus des bibliothèques open source, et s'il fuitait demain, le dommage pratique relèverait le plus souvent de la gêne plutôt que du préjudice. C'est pourquoi « mon code n'est pas secret » a l'air d'une réponse complète — et pourquoi ça n'en est pas une.

Ce qui entoure le code est la partie intéressante. Un dépôt est un enregistrement de travail daté, attribué et en haute résolution : les horodatages de commits décrivent quand vous dormez, les lignes de co-auteur décrivent avec qui vous travaillez, les noms de branches décrivent des produits que personne n'a annoncés, un gestionnaire de tickets décrit ce qui est cassé et à quel point, et un premier commit date le projet tout entier. Lisez cent dépôts de cette façon et vous ne lisez plus du logiciel — vous lisez une entreprise, sa feuille de route, ses effectifs et ses ennuis.

Sur une plateforme, cet enregistrement ne vous appartient pas au sens propre. Il repose sur une infrastructure que vous ne contrôlez pas, sous un compte lié à une identité que vous avez vérifiée, dans une juridiction dont la procédure judiciaire peut l'atteindre, scruté par des systèmes dont la portée est fixée par un document qui peut être modifié. Rien de tout cela n'exige de mauvaise foi de la part de qui que ce soit : c'est simplement ce que signifie louer le gestionnaire de tickets. Quatre configurations, et ce que chacune révèle réellement :

Où vit le dépôt Qui détient l'historique Lié à votre identité légale Comment ça tourne mal
Une plateforme, compte personnelLa plateforme, dans sa propre juridictionOui — e-mail, facturation, souvent un numéro de téléphoneUne suspension emporte tous les remotes avec elle
Une plateforme, compte d'organisationLa plateforme, plus quiconque administre l'organisationOui, et aussi à votre employeurLe départ de l'entreprise vous retire l'accès à votre propre travail
Auto-hébergé, chez un hyperscalerVous, au niveau de la couche applicativeOui — le compte de facturation est l'identitéLe snapshot vit dans le compte qui peut être fermé
Auto-hébergé, sur un VPS sans KYCVous, et vous seulNon — pas de pièce d'identité, pas de carte, paiement en cryptoVous êtes l'administrateur système désormais. Une mauvaise sauvegarde est définitive

La troisième ligne est celle qui piège les équipes qui pensent avoir déjà réglé la question. Une forge que vous avez installée vous-même, sur une machine louée avec une carte d'entreprise sous un compte de société vérifié, a fait sortir l'application de la plateforme mais a laissé l'identité exactement où elle était. C'est un vrai gain en contrôle et aucun gain en exposition.

Et la quatrième ligne énonce clairement son propre coût, car le reste de ce guide consiste surtout à le payer. Le mode de défaillance de l'auto-hébergement n'est pas la surveillance, c'est la perte. Une plateforme conserve des sauvegardes auxquelles vous ne pensez jamais. Votre serveur conserve les sauvegardes que vous avez configurées. L'étape 06 n'est pas la finition optionnelle de la fin — c'est l'étape qui rend les cinq autres sûres.

Étape 01 · Choisir

De combien de forge avez-vous besoin ? La réponse honnête est souvent « aucune ».

Commencez par la plus petite chose qui résout votre problème, car chaque couche ajoutée par-dessus est un service que vous devrez exploiter. Un dépôt nu en SSH tient en une seule commande sur un serveur que vous avez déjà, et vous donne un vrai remote Git avec authentification, historique et accès en push. Aucune application web, aucune base de données, aucun tapis roulant de mises à jour, rien à corriger au-delà de sshd. Si vous êtes une seule personne qui veut sortir ses commits d'un ordinateur portable et d'une plateforme, c'est une réponse complète, et vous pouvez arrêter votre lecture après l'étape 02.

# On the server, as an unprivileged user:
mkdir -p ~/git/project.git && cd ~/git/project.git
git init --bare

# On your laptop:
git remote add origin ssh://user@git.example.com/~/git/project.git
git push -u origin main

Une forge devient nécessaire dès qu'une deuxième personne est impliquée, ou dès que vous voulez l'une des quatre choses qu'un répertoire de dépôts nus ne sait pas faire : parcourir l'historique dans un navigateur, relire les changements sous forme de pull requests, suivre les tickets à côté du code, et accorder à une personne l'accès à un seul dépôt. C'est une vraie montée en capacité, et une vraie montée en poids opérationnel. Choisissez avec soin :

Propriété Dépôt nu en SSH Forgejo Gitea GitLab CE
Interface web, pull requests, ticketsAucunOuiOuiOui, et bien plus
Mémoire au reposPratiquement nulleQuelques centaines de MoQuelques centaines de MoPlusieurs Go, avant la CI
CI intégréehooks git, si vous les écrivezForgejo ActionsGitea ActionsGitLab CI, la plus mature
Qui dirige le projetLe projet GitCodeberg e.V., une association à but non lucratifUne société à but lucratif, open coreUne société cotée en bourse, open core
LicenceGPLv2GPLv3+ depuis la v9.0 (MIT avant)MIT, avec des extensions propriétairesCœur MIT, paliers propriétaires
À choisir quandVous êtes seul et voulez une maintenance nulleVous voulez une vraie forge sur une petite machineVous la faites déjà tourner et ça marcheVous avez besoin des fonctionnalités entreprise

Forgejo et Gitea partagent un ancêtre et se ressemblent encore presque à l'identique au quotidien. Forgejo est devenu un hard fork début 2024 et est détenu par Codeberg e.V., une association à but non lucratif enregistrée à Berlin ; Gitea est développé par une société à but lucratif qui possède sa marque et demande aux contributeurs une cession de droits d'auteur. Les deux sont de bons logiciels. Un seul des deux a un gardien dont les intérêts correspondent à la raison pour laquelle vous lisez cette page.

Le reste de ce guide utilise Forgejo. Tout ce qui figure dans les étapes 02 à 06 s'applique presque mot pour mot à Gitea — le nom du conteneur et quelques variables d'environnement changent, la structure non.

Étape 02 · Provisionner

Décidez des ports avant que la forge ne les décide à votre place. Deux d'entre eux sont du SSH.

Commandez d'abord la machine. Dans le panel : Order → VPS → Sentinel, image Debian 12, dans le bastion où vous voulez que l'historique vive — Helsinki, Stockholm, Oslo ou Reykjavík. Aucune adresse e-mail n'est requise pour ouvrir le compte, et la facture se règle en Monero, Bitcoin, Lightning ou tout autre actif pris en charge. Cela compte plus ici que pour la plupart des charges de travail : une forge est le seul service dont toute la proposition de valeur repose sur le fait que l'enregistrement de votre travail n'est attaché à aucun nom.

Passez ensuite la check-list de durcissement de la première heure — SSH par clé uniquement sur un port déplacé, mises à jour automatiques, pare-feu qui refuse par défaut — car tout ce qui suit suppose une machine qui n'est pas déjà celle de quelqu'un d'autre.

Ce que vous mettez réellement sur internet

Une forge n'est pas un site statique avec une connexion. C'est une application web publique avec un moteur d'exécution de code boulonné dessus. Deux réglages par défaut, laissés tels quels, en font un service gratuit pour des inconnus : l'inscription ouverte, qui remplit l'instance de comptes spam en quelques jours après l'apparition du certificat dans les journaux de transparence, et une CI qui exécute les workflows de n'importe qui peut ouvrir une pull request, ce qui explique comment un runner auto-hébergé finit par miner de la cryptomonnaie pour quelqu'un d'autre. Ni l'une ni l'autre n'est une attaque subtile, et ni l'une ni l'autre n'est rare. Les deux se referment chacune par un seul réglage, à l'étape 03 et à l'étape 05 — mais décidez dès maintenant que vous allez les fermer, car la fenêtre entre « ça marche » et « c'est utilisé » se compte en jours.

Pointez un nom d'hôte vers la machine — un enregistrement A, et un AAAA si vous la voulez joignable en IPv6 — et laissez-le se propager avant de commencer, car le reverse proxy va tenter d'obtenir un certificat pour ce nom dès son premier démarrage, et un challenge échoué est une façon déroutante de commencer. Écrivez ensuite les règles de pare-feu pendant que rien n'écoute encore.

Le problème des deux SSH. Une forge parle SSH pour Git, et votre serveur parle déjà SSH pour l'administration. Ils ne peuvent pas posséder le port 22 tous les deux, et c'est en les mélangeant qu'on finit avec un démon d'administration qui doit aussi comprendre Git. Gardez-les séparés : l'administration sur le port déplacé du guide de durcissement, Git sur un port public dédié qui pointe vers le conteneur. 2222 est le choix conventionnel, et il n'a rien de sacré.

ufw default deny incoming
ufw allow 47222/tcp                  # administrative SSH, moved
ufw allow 80/tcp                     # ACME HTTP-01 challenge
ufw allow 443/tcp                    # the web interface
ufw allow 2222/tcp                   # git over SSH, into the container
ufw enable
ufw status numbered

# Nothing else is open. In particular: PostgreSQL stays on the Docker
# network and is never published to the host, let alone the internet.

Installez Docker depuis le dépôt officiel plutôt que le paquet de la distribution, afin que Compose v2 soit présent et que la version soit à jour. Créez ensuite un répertoire pour la stack et faites-en le seul endroit de la machine qui conserve un état — c'est le répertoire que l'étape 06 sauvegarde.

curl -fsSL https://get.docker.com | sh
docker compose version

install -d -m 0750 /srv/forgejo
cd /srv/forgejo
Étape 03 · Déployer

Trois conteneurs. L'un d'eux ne doit jamais être joignable.

Forgejo, PostgreSQL et Caddy. Forgejo détient les dépôts et sert l'interface, PostgreSQL détient tout ce qui n'est pas un objet Git, et Caddy termine le TLS et obtient le certificat sans qu'on le lui demande deux fois. PostgreSQL n'est délibérément pas publié vers l'hôte : il n'est joignable que sur le réseau Compose, par son nom, depuis le seul conteneur qui en a besoin.

/srv/forgejo/.env

FORGEJO_DOMAIN=git.example.com
FORGEJO_VERSION=15                             # the LTS line; 16 is current stable
POSTGRES_PASSWORD=$(openssl rand -base64 32)   # paste the value, do not leave the $( )

/srv/forgejo/docker-compose.yml

services:
  forgejo:
    image: codeberg.org/forgejo/forgejo:${FORGEJO_VERSION}
    container_name: forgejo
    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: ${POSTGRES_PASSWORD}
      FORGEJO__server__DOMAIN: ${FORGEJO_DOMAIN}
      FORGEJO__server__ROOT_URL: https://${FORGEJO_DOMAIN}/
      FORGEJO__server__SSH_DOMAIN: ${FORGEJO_DOMAIN}
      FORGEJO__server__SSH_PORT: "2222"          # what clones will say
      FORGEJO__server__SSH_LISTEN_PORT: "22"     # what the container listens on
      FORGEJO__service__DISABLE_REGISTRATION: "true"
      FORGEJO__repository__DEFAULT_PRIVATE: private
      FORGEJO__security__INSTALL_LOCK: "true"
    restart: unless-stopped
    volumes:
      - ./data:/data
      - /etc/timezone:/etc/timezone:ro
      - /etc/localtime:/etc/localtime:ro
    ports:
      - "127.0.0.1:3000:3000"    # web, reached only by Caddy
      - "2222:22"                # git over SSH, public
    depends_on:
      - db

  db:
    image: postgres:16-alpine
    container_name: forgejo-db
    environment:
      POSTGRES_USER: forgejo
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
      POSTGRES_DB: forgejo
    restart: unless-stopped
    volumes:
      - ./postgres:/var/lib/postgresql/data
    # No ports: block. This is the point.

  caddy:
    image: caddy:2-alpine
    container_name: forgejo-caddy
    restart: unless-stopped
    network_mode: host
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile:ro
      - ./caddy-data:/data
      - ./caddy-config:/config

/srv/forgejo/Caddyfile

git.example.com {
    encode zstd gzip
    request_body {
        max_size 512MB          # raise it if you push large repositories
    }
    reverse_proxy 127.0.0.1:3000
}

Les deux lignes SSH sont celles que tout le monde se trompe, elles méritent donc d'être lues deux fois. SSH_LISTEN_PORT est le port à l'intérieur du conteneur, là où se lie le serveur SSH intégré de Forgejo. SSH_PORT est le port que Forgejo imprime dans les URL de clonage qu'il vous montre — il doit correspondre au côté public du mappage de port. Désynchronisez-les et tout fonctionne, sauf la seule chose que les utilisateurs copient-collent.

Démarrez-la, puis créez l'administrateur en ligne de commande. N'utilisez pas l'installateur web. Sur une instance neuve, il est joignable par quiconque trouve le nom d'hôte, et le premier compte créé par ce biais devient l'administrateur — une course à laquelle vous n'avez pas besoin de participer. INSTALL_LOCK dans le fichier ci-dessus le referme avant même que le conteneur ne démarre.

docker compose up -d
docker compose logs -f forgejo        # wait for "Starting new Web server"

docker compose exec -u git forgejo forgejo admin user create \
  --admin --username you --email you@example.com --random-password

Ouvrez le nom d'hôte dans un navigateur, connectez-vous avec le mot de passe imprimé et changez-le. Parcourez ensuite les réglages qui transforment une instance fonctionnelle en instance fermée — la plupart sont déjà définis par le bloc d'environnement ci-dessus, et il vaut la peine de vérifier chacun d'eux plutôt que de le supposer :

Inscription

DISABLE_REGISTRATION = true. Vous créez les comptes ; personne ne se sert lui-même. Si vous avez besoin d'inscriptions occasionnelles, utilisez le circuit d'invitation plutôt que d'ouvrir la porte en grand.

Visibilité par défaut

DEFAULT_PRIVATE = private. Les nouveaux dépôts démarrent fermés. Publier devient alors un acte délibéré plutôt que la conséquence d'un réglage par défaut.

Navigation anonyme

REQUIRE_SIGNIN_VIEW = true masque même les dépôts publics aux visiteurs non connectés. Juste pour une forge d'équipe privée ; faux si vous voulez publier quoi que ce soit.

Double facteur

Activez cette protection sur le compte administrateur avant de faire quoi que ce soit d'autre. La forge est désormais la machine capable de réécrire chacune de vos branches.

Un chemin qui mérite d'être mémorisé : la configuration active se trouve dans ./data/gitea/conf/app.ini sur l'hôte. Forgejo a conservé le nom de répertoire gitea pour rester compatible avec les installations existantes, ce qui surprend tout le monde exactement une fois. Les variables d'environnement de la forme FORGEJO__section__KEY sont écrites dans ce fichier au démarrage, ce qui permet de gérer toute la configuration depuis le fichier Compose — mais si vous modifiez app.ini à la main, une variable d'environnement pour la même clé écrasera votre modification au redémarrage suivant. Choisissez l'une des deux méthodes et restez-y fidèle.

Étape 04 · Pousser

Poussez en SSH. Puis signez, pour que l'historique ne dépende pas du serveur.

Collez votre clé publique dans les réglages du profil, puis indiquez à votre client où se trouve la forge. Comme Git est sur un port non standard, une entrée dans votre config SSH vaut bien les trente secondes que ça prend — elle garde le port hors de chaque URL que vous taperez jamais, et rend les remotes portables.

# ~/.ssh/config
Host git.example.com
    Port 2222
    User git
    IdentityFile ~/.ssh/id_ed25519

# Then, in any repository:
git remote set-url origin git@git.example.com:you/project.git
git push -u origin main
ssh -T git@git.example.com        # expect a greeting, not a shell

Cette dernière commande est celle qui vous dit si le mappage de port, le serveur SSH du conteneur et votre clé sont bien d'accord entre eux. Un message d'accueil de Forgejo signifie que toute la chaîne fonctionne. Une demande de mot de passe signifie que vous avez atteint le sshd d'administration de l'hôte à la place, et que le mappage de port est incorrect.

Signez maintenant vos commits. C'est l'étape que la plupart des guides d'auto-hébergement sautent, et sur une forge auto-hébergée elle compte plus que sur une plateforme. Le champ auteur d'un commit est du texte libre — quiconque peut pousser peut y écrire votre nom. Une signature est la preuve qu'une clé précise a fait un commit précis, et elle reste une preuve même après que le serveur sur lequel il a été poussé a été compromis, migré ou jeté. Git peut signer avec la clé SSH que vous possédez déjà, il n'y a donc plus besoin de détour par PGP :

git config --global gpg.format ssh
git config --global user.signingkey ~/.ssh/id_ed25519.pub
git config --global commit.gpgsign true
git config --global tag.gpgsign true

git commit --allow-empty -m "signed"
git log --show-signature -1

Ajoutez ensuite la même clé publique une seconde fois dans la forge, comme clé de signature plutôt que comme clé d'authentification. Forgejo vérifie la signature côté serveur et affiche le commit comme vérifié ; sans cette seconde entrée, il n'a aucun moyen de relier la signature à votre compte, et chaque commit s'affiche comme non vérifié même s'il est signé correctement.

Les clés de déploiement, pour les machines qui ne font que lire. Un serveur de production, une machine de build ou un miroir n'a pas besoin d'un compte utilisateur sur la forge. Donnez-lui plutôt une clé de déploiement : une paire de clés ajoutée à un seul dépôt, en lecture seule sauf si vous accordez explicitement l'écriture, et qui ne donne accès à rien d'autre sur l'instance. La révoquer, c'est supprimer une seule entrée. C'est l'habitude la plus efficace pour garder une forge auto-hébergée bien rangée, parce que les comptes utilisateurs accumulent les permissions, alors que les clés de déploiement ne le peuvent pas.

Migrer depuis une plateforme est un bon moment pour bien faire tout cela dès le départ. Le circuit New Migration de Forgejo prend une URL de dépôt et un jeton d'accès, et rapatrie branches, tags, tickets, commentaires, labels, jalons, releases et pull requests. Migrez un premier dépôt pour voir la forme du résultat, attendez-vous à ce que les limites de débit de l'API ralentissent les gros lots, et gardez la source en lecture seule pendant quelques semaines plutôt que de la supprimer le jour même.

Étape 05 · Construire

Un runner CI est une machine qui exécute le code d'inconnus. Placez-le en conséquence.

Forgejo Actions lit des fichiers de workflow dans le même format que GitHub Actions, depuis .forgejo/workflows/, et les confie à un programme séparé appelé le Forgejo Runner. Le runner ne fait pas partie de la forge et n'a pas à vivre sur la même machine — c'est tout l'intérêt de la conception, et la raison pour laquelle cette étape s'accompagne d'un avertissement plutôt que d'un haussement d'épaules.

Activez Actions sur l'instance, puis récupérez un jeton d'enregistrement depuis le panneau d'administration — pour toute l'instance, ou limité à une organisation ou à un dépôt, ce qui est le meilleur choix par défaut en cas de doute. Le runner se connecte vers la forge, dans le sens sortant ; il n'a donc besoin d'aucun port entrant et peut se placer derrière n'importe quel pare-feu, y compris sur une machine sans adresse publique.

# In the Compose environment of the forge, or app.ini:
#   FORGEJO__actions__ENABLED: "true"
# Then: Site administration -> Actions -> Runners -> Create new runner

# On the runner machine:
forgejo-runner generate-config > config.yml

forgejo-runner register --no-interactive \
  --instance https://git.example.com \
  --token <REGISTRATION_TOKEN> \
  --name runner-01 \
  --labels docker:docker://node:22-bookworm,debian:docker://debian:12

forgejo-runner --config config.yml daemon

Les labels sont le contrat entre le workflow et le runner : un job qui demande runs-on: docker reçoit un conteneur construit à partir de l'image indiquée à droite. Ne déclarez que les labels que vous êtes réellement prêt à servir, car un job qui demande un label que personne n'offre attend indéfiniment plutôt que d'échouer, ce qui est déroutant à déboguer à la fin d'une longue journée.

.forgejo/workflows/ci.yml

on: [push, pull_request]

jobs:
  test:
    runs-on: docker
    container:
      image: node:22-bookworm
    steps:
      - uses: actions/checkout@v4
      - run: npm ci
      - run: npm test
La règle qui fait la différence

Un runner exécute tout ce que dit le fichier de workflow, et le fichier de workflow arrive dans une pull request. Si quiconque en dehors de votre équipe peut en ouvrir une, le runner doit vivre sur une machine distincte de la forge — un second Sentinel à $3.90 par mois, sans accès au répertoire de données de la forge, sans identifiants de base de données, et sans rien dans son environnement que vous regretteriez de voir dans un journal de build. Sur la forge elle-même, exigez une approbation avant d'exécuter les workflows des contributeurs de première fois. Les deux échecs classiques sont aussi ennuyeux l'un que l'autre et aussi coûteux l'un que l'autre : une charge de minage dissimulée dans une pull request, et un secret imprimé par une étape que quelqu'un a ajoutée délibérément.

Limitez les secrets de la même façon. Forgejo les stocke au niveau du dépôt, de l'organisation et de l'instance ; une clé de déploiement dont un seul dépôt a besoin ne devrait exister que sur ce dépôt, afin qu'un workflow compromis dans un projet sans rapport ne puisse pas la lire. Préférez des jetons de courte durée à des jetons de longue durée partout où la cible le permet, et ne mettez jamais un secret dans un fichier de workflow — le fichier de workflow est dans le dépôt, et le dépôt a un historique.

Si vos builds tirent les mêmes images de base à chaque exécution, mettez-les en cache. Un miroir de registre local ou un cache persistant sur le runner transforme un pull d'image de deux minutes en un pull de deux secondes, et c'est la moindre des politesses envers les registres amont que vous martelez. Et notez le schéma que toute cette conception invite, qui est la raison pour laquelle ce guide se trouve à côté du guide sur le runtime des agents : le runner est un travailleur généraliste qui tire des jobs de votre forge, c'est donc aussi là que trouvent leur place la maintenance planifiée, les pipelines de données et les charges de travail d'agents.

Étape 06 · Conserver

Chaque clone est une sauvegarde du code. Rien n'est une sauvegarde de la conversation.

Git est étonnamment indulgent ici, et cela vaut la peine d'être précis sur pourquoi. Chaque clone est une copie complète de l'historique, donc tant qu'un développeur a une copie de travail à jour, le code survit au serveur. C'est réellement rassurant, et cela couvre exactement l'une des deux choses que détient votre forge. L'autre — tickets, discussions de pull requests, fils de revue, releases, pages de wiki, utilisateurs, équipes, permissions, historique de CI — n'existe que dans la base de données, personne n'en a de clone, et c'est généralement la partie qui a mis trois ans à s'accumuler.

D'abord, un miroir push. C'est gratuit et ça tourne tout seul. Dans un dépôt : Settings → Mirror settings → ajoutez un miroir push avec l'URL et un jeton d'accès pour la destination. Forgejo réplique alors chaque branche et chaque tag après chaque push, vers une seconde instance Forgejo, vers Codeberg, ou vers une plateforme que vous avez décidé de garder comme vitrine plutôt que comme domicile. Une mise en garde qui mérite d'être dite clairement : un miroir push republie l'historique fidèlement, y compris un secret que vous vouliez retirer.

Puis une vraie sauvegarde, qui n'est pas l'archive intégrée. Forgejo fournit une commande forgejo dump qui compresse la configuration, les dépôts, les données et un dump de la base de données dans un unique zip. C'est réellement utile à remettre à un mainteneur lors du signalement d'un bug. Ce n'est pas ce sur quoi vous devriez compter pour revenir d'entre les morts : le SQL qu'elle produit a des problèmes connus de longue date à la réimportation, et l'archive réécrit l'ensemble des dépôts à chaque exécution, ce qui, sur une vraie instance, signifie un gros fichier réécrit de zéro chaque nuit. Utilisez plutôt la voie ennuyeuse et vérifiable — dumpez la base de données avec l'outil propre à la base de données, et sauvegardez le répertoire de données sous forme de fichiers :

#!/bin/sh
# /usr/local/bin/forgejo-backup — run from a systemd timer, nightly
set -eu
cd /srv/forgejo

# 1. Consistent database dump, written next to the data.
docker compose exec -T db pg_dump -U forgejo -Fc forgejo > ./db.dump

# 2. Everything that matters, to an encrypted offsite repository.
restic backup ./data ./db.dump ./docker-compose.yml ./.env ./Caddyfile

# 3. Retention, then prove the repository is still readable.
restic forget --keep-daily 7 --keep-weekly 4 --keep-monthly 12 --prune
restic check

Envoyez-la vers une machine située dans une juridiction différente de celle de la forge, via un dépôt en ajout seul, afin qu'une compromission de la forge ne puisse pas effacer son propre historique — le guide des sauvegardes chiffrées est la version longue des trois lignes ci-dessus, minuteur systemd et restriction de clé en ajout seul compris.

Restaurez-la ensuite une fois, volontairement. Commandez un VPS de test, restaurez-y le snapshot, démarrez le même fichier Compose sur le répertoire de données restauré, chargez le dump de la base de données, pointez une entrée du fichier hosts vers lui et connectez-vous. Chronométrez l'ensemble. Ce chiffre — et non l'existence de la tâche de sauvegarde — est votre véritable position de reprise, et tous ceux qui ont fait cet exercice ont trouvé au moins un élément manquant dans la liste d'inclusion la première fois.

# restic keeps absolute paths, so the stack reappears under the target.
restic restore latest --target /srv/restore
cd /srv/restore/srv/forgejo

docker compose up -d db                # empty volume: the entrypoint creates the database
docker compose exec -T db pg_restore -U forgejo -d forgejo \
  --clean --if-exists --no-owner < db.dump

docker compose up -d                   # then sign in and walk the issue tracker

Les mises à jour, brièvement. Épinglez l'image à une version majeure plutôt qu'à :latest, pour qu'un pull automatique ne vous fasse pas franchir un changement cassant pendant votre sommeil. Lisez les notes de version avant de franchir une limite majeure, prenez d'abord un snapshot, et souvenez-vous qu'une migration de base de données s'exécute au premier démarrage de la nouvelle version — ce qui est une porte sans retour. La mise à jour automatique façon Watchtower est une bonne idée pour un site statique et une mauvaise pour la machine qui détient chacune de vos branches.

Quatre vérifications avant de considérer que c'est terminé. Déconnectez-vous et vérifiez que l'instance n'offre aucun moyen de créer un compte. Depuis un navigateur sans session, vérifiez qu'un dépôt privé est invisible plutôt que simplement interdit. Vérifiez que le runner ne peut pas lire le répertoire de données de la forge. Et vérifiez que le dernier restic check a réussi — la tâche de sauvegarde qui échoue silencieusement, tout le monde en a une, jusqu'au jour où on en a besoin.

La partie honnête · Ce que ça ne fait pas

Vous avez retiré une partie. Vous n'avez pas retiré le bâtiment.

Auto-héberger une forge change réellement qui détient votre travail, mais ce n'est pas l'invisibilité. Quatre choses restent vraies une fois tout ce qui précède accompli, et les connaître fait la différence entre une vraie défense et une histoire que vous vous racontez.

Le disque est dans le bâtiment de quelqu'un. Un serveur loué tourne sur du matériel dans un datacentre avec des portes et du personnel. NordBastion n'inspecte pas les charges de travail de ses clients, et le rapport de transparence ainsi que le warrant canary décrivent ce qui se passerait si on nous le demandait — mais l'affirmation architecturale est la plus honnête : ce qui change, c'est qu'il n'existe aucun index de vos dépôts au niveau du produit, aucun pipeline d'analyse sur vos commits et aucune politique de confidentialité qui puisse être modifiée. Ce qui ne change pas, c'est que la machine est physique et qu'elle n'est pas chez vous.

Le nom d'hôte devient public dès que le certificat est émis. Tout certificat reconnu publiquement est publié dans des journaux de transparence des certificats que n'importe qui peut consulter, si bien que git.yourproject.com est repérable même si vous ne le liez jamais nulle part. Ce n'est pas une fuite de votre code, mais c'est une fuite du fait que le projet existe, et à peu près depuis quand. Utilisez un sous-domaine d'un nom que vous publiez déjà, ou faites entièrement l'impasse sur le certificat public et accédez à la forge via WireGuard ou un service onion.

Vous corrigez désormais une application web. La forge est une page de connexion, un pipeline de rendu pour du balisage non fiable, un serveur SSH et un moteur de CI, tous joignables depuis internet. C'est une surface considérablement plus large que le dépôt nu de l'étape 01, et c'est une surface que vous devez maintenir à jour. Abonnez-vous aux annonces de version, et traitez une release de sécurité comme une tâche de la semaine plutôt que du trimestre.

La fédération n'est pas encore là. Forgejo travaille réellement sur la fédération entre forges et publie ses avancées à ce sujet, mais à ce jour, vous ne pouvez pas ouvrir de pull request sur l'instance de quelqu'un d'autre depuis la vôtre comme vous le feriez sur une plateforme partagée. En pratique, cela signifie qu'un contributeur externe a encore besoin d'un compte sur votre forge, ou que vous acceptez des patches par e-mail, ou que vous gardez un miroir public pour les contributions de passage. Prévoyez celle des trois options qui correspond à votre projet plutôt que de vous la voir imposer par surprise.

Ce que vous avez réellement acheté, précisément : l'enregistrement daté de ce que vous construisez, avec qui, et quand, n'est plus détenu par une entreprise dont le modèle économique consiste notamment à l'analyser. C'est une affirmation étroite, et c'est une affirmation réelle. Elle ne vaut son coût que si l'étape 06 est faite — une forge auto-hébergée sans restauration testée n'est pas une amélioration de la confidentialité, c'est un point unique de défaillance avec une meilleure image de marque.

Dimensionnement · Que commander

La forge est petite. La CI ne l'est pas.

Niveau Mensuel Ressources Ce qu'elle fait tourner confortablement
Sentinel$3.902 vCPU · 4 GB · 120 GBForgejo + PostgreSQL pour une personne seule ou une petite équipe. Le bon point de départ.
Un second Sentinel$3.90Le runner, sur sa propre machineL'isolation qui compte vraiment, pour le prix d'un café
Garrison$7.904 vCPU · 8 GB · 240 GBUne forge pour dix personnes, un registre de conteneurs et des artefacts
Ravelin$16.908 vCPU · 16 GB · 480 GBCI chargée, gros dépôts, plusieurs jobs en parallèle

Rien dans ce tableau n'est dimensionné pour Git. Forgejo et PostgreSQL réunis tournent au repos à quelques centaines de mégaoctets ; ce qui remplit une machine, c'est la CI — couches de conteneurs, caches de build, artefacts — et le contenu des dépôts qui n'est pas du code source. Dimensionnez d'abord pour le disque, ensuite pour les cœurs, et enfin pour la mémoire. Deux petites machines valent mieux qu'une grosse ici, parce que la seconde est une frontière de sécurité plutôt qu'une question de capacité.

Notes de terrain · Six pièges

Six façons que ça tourne mal. Deux d'entre elles coûtent de l'argent.

Piège 01 · Bruyant

Inscription ouverte, et une forge pleine d'inconnus

Le certificat apparaît dans les journaux de transparence, les scanners suivent en quelques jours, et une instance avec les inscriptions activées se remplit de comptes spam et de dépôts spam. Réglez DISABLE_REGISTRATION avant le premier démarrage, pas après le premier incident.

Piège 02 · Coûteux

Le runner qui mine de la cryptomonnaie pour quelqu'un d'autre

Une CI qui exécute les workflows de n'importe quelle pull request est du calcul gratuit pour qui la trouve. Exigez une approbation pour les contributeurs de première fois, gardez le runner sur sa propre machine, et jetez de temps en temps un œil aux durées des jobs.

Piège 03 · Silencieux

ROOT_URL erroné, et tout fonctionne à moitié

Les URL de clonage pointent vers le mauvais hôte, les webhooks se déclenchent vers la mauvaise adresse, les callbacks OAuth échouent et les avatars se cassent. L'interface a l'air normale, donc personne ne soupçonne une seule ligne. Réglez DOMAIN, ROOT_URL, SSH_DOMAIN et SSH_PORT ensemble, et vérifiez une URL de clonage dans l'interface.

Piège 04 · Déroutant

Deux serveurs SSH, un seul port

Le conteneur veut le port 22 et l'hôte l'utilise déjà, si bien que le mappage atterrit silencieusement ailleurs que prévu et que git push demande un mot de passe. Déplacez d'abord le SSH d'administration, publiez le conteneur sur un port qui lui est propre, et vérifiez avec ssh -T.

Piège 05 · Fatal

Un dump qui n'a jamais été restauré

L'archive intégrée est un artefact de support, pas un chemin de reprise testé, et son SQL a des problèmes connus à la réimportation. Dumpez la base de données avec pg_dump, sauvegardez le répertoire de données sous forme de fichiers, et restaurez le tout sur une machine de test, une fois.

Piège 06 · Lent

Le disque se remplit de couches de build, pas de code

Le code source est minuscule ; les couches de conteneurs, les artefacts et les caches de CI ne le sont pas. Une machine de 120 Go avec un pipeline enthousiaste atteint son plafond en quelques semaines. Purgez les images sur un calendrier, plafonnez la rétention des artefacts, et surveillez le disque plutôt que le CPU.

FAQ · Hébergement Git privé

Questions, réponses.

Onze questions qui reviennent lors de la construction de tout cela — en commençant par les deux qui déterminent si vous devriez seulement le construire.

Forgejo ou Gitea — lequel faut-il vraiment installer ?

Forgejo, pour la plupart des gens, et la raison tient à la gouvernance plutôt qu'aux fonctionnalités. Les deux projets partagent un ancêtre commun et restent presque identiques à l'usage : même binaire Go, même empreinte légère, même interface web, CI comparable. Ce qui diffère, c'est qui les dirige. Forgejo est détenu par Codeberg e.V., une association à but non lucratif enregistrée à Berlin, et est devenu un hard fork début 2024 ; il publie tout en logiciel libre et ne demande à ses contributeurs aucune cession de droits d'auteur. Gitea est développé par une société à but lucratif qui possède la marque et le domaine, exige une cession de droits d'auteur de la part des contributeurs, et applique un modèle open-core avec des fonctionnalités payantes autour d'une base gratuite. Aucun de ces faits ne disqualifie Gitea en tant que logiciel. Mais tout l'intérêt de sortir ses dépôts d'une plateforme est de ne plus dépendre de la feuille de route d'une entreprise, donc choisir le fork dont le gardien est une association à but non lucratif est le choix qui correspond à la raison de votre présence ici. Une remarque pratique si vous changez plus tard : Forgejo peut s'installer par-dessus un répertoire de données Gitea existant, mais l'inverse n'est pas pris en charge.

Ai-je seulement besoin d'une forge ? Un dépôt nu en SSH ne suffit-il pas ?

Pour une seule personne avec un seul ordinateur portable et l'habitude d'écrire de bons messages de commit, un dépôt nu suffit réellement, et c'est la chose la plus robuste de ce guide : git init --bare sur le serveur, un compte SSH normal, et il n'y a aucune application web à corriger, aucune base de données à sauvegarder et aucune surface d'attaque au-delà de sshd. Rien dans cette configuration ne deviendra jamais obsolète. Une forge devient nécessaire dès qu'une deuxième personne entre en jeu, ou dès que vous voulez l'une des quatre choses qu'un dépôt nu ignore totalement — un historique consultable dans un navigateur, des pull requests avec revue, un gestionnaire de tickets qui vit à côté du code, et un contrôle d'accès par dépôt. Ce n'est pas une petite montée en gamme ; c'est la différence entre du stockage et de la collaboration. Soyez honnête sur ce que vous cherchez à résoudre, car une forge est un service que vous exploitez désormais, alors qu'un dépôt nu est un simple répertoire.

Puis-je migrer mes dépôts GitHub, tickets et pull requests compris ?

Oui, et c'est l'une des meilleures histoires de migration de tout l'auto-hébergement. L'outil de migration intégré de Forgejo prend une URL de dépôt et un jeton d'accès personnel, et rapatrie bien plus que le code : branches et tags, tickets avec leurs commentaires, pull requests, labels, jalons et releases. Donnez-lui un jeton en lecture seule, migrez un premier dépôt pour voir à quoi ressemble le résultat, puis enchaînez sur le reste. Trois choses à prévoir. Les migrations volumineuses ou nombreuses buteront sur les limites de débit de l'API de la plateforme source et prendront simplement plus de temps — lancez-les et laissez-les tourner. Les pièces jointes des tickets sont récupérées par le réseau, donc une instance derrière un pare-feu sortant restrictif produira des tickets aux images cassées. Et les fils de revue n'atterrissent pas toujours exactement là où ils étaient, car les deux modèles de données ne sont pas identiques. Prévoyez de garder les dépôts source en lecture seule pendant quelques semaines plutôt que de les supprimer l'après-midi même.

Une forge auto-hébergée est-elle vraiment plus privée, ou seulement différente ?

Elle est réellement différente d'une manière précise et importante : personne d'autre que vous ne détient l'index. Sur une plateforme, le dépôt privé est privé pour les autres utilisateurs — il ne l'est pas pour la plateforme, qui le stocke, l'analyse, en calcule le graphe de dépendances, et peut être contrainte de le produire par un tribunal de sa propre juridiction. Cet index est étonnamment révélateur même quand le code est banal : les horodatages des commits décrivent vos heures de travail, les co-auteurs décrivent votre équipe, les noms de branches décrivent des produits non annoncés, et le premier commit date un projet qui n'a pas encore été annoncé. Le déplacer vers votre propre serveur retire entièrement la plateforme de ce tableau. Ce que cela ne fait pas, c'est faire disparaître la machine. Vous louez toujours du matériel dans un datacentre, le disque reste physiquement accessible aux personnes qui possèdent le bâtiment, et le nom d'hôte que vous avez choisi reste dans les journaux de transparence des certificats. Le résumé honnête : vous avez remplacé une entreprise dotée d'une politique de confidentialité contractuelle par un serveur dont la seule politique est votre configuration, dans une juridiction que vous avez choisie délibérément — et le compte qui le paie n'a pas besoin de porter votre nom.

Quelle taille de VPS faut-il ?

Plus petit que ce que la documentation de GitLab a appris à tout le monde à attendre. Forgejo et Gitea sont de simples binaires Go qui tournent au repos à quelques centaines de mégaoctets de RAM ; ajoutez PostgreSQL et un reverse proxy, et la forge d'un développeur seul tient confortablement dans un Sentinel — 2 vCPU, 4 Go, 120 Go de NVMe, $3.90 par mois — avec de la marge. La pression vient de trois autres endroits, et chacun mérite d'être dimensionné délibérément. La CI est le plus gros morceau : un runner qui construit des conteneurs utilisera chaque cœur que vous lui donnez et remplira le disque de couches. La taille des dépôts est le second : les assets de jeu, les notebooks et les dépendances vendorisées grossissent bien plus vite que le code source. Les artefacts et un registre de conteneurs sont le troisième. Une règle qui a fait ses preuves en pratique — mettez la forge sur un Sentinel, mettez le runner sur son propre second Sentinel, et ne montez de palier que lorsque le disque vous le dit, pas le CPU.

Forgejo Actions est-il vraiment compatible avec mes workflows GitHub Actions ?

Compatible au point que la plupart des workflows simples tournent sans modification, mais pas au point de migrer un pipeline complexe sans le relire. Le format des fichiers de workflow est le même, il vit dans .forgejo/workflows/ (et .github/workflows/ est aussi lu), et les briques communes — checkout, configuration du langage, cache, builds en matrice — se comportent comme prévu, car le runner les exécute dans des conteneurs de la même façon. Ce qui casse, c'est tout ce qui suppose GitHub la plateforme plutôt qu'Actions le format : les actions du marketplace épinglées à des URL github.com que votre instance doit être autorisée à récupérer, les actions qui appellent l'API REST de GitHub pour des métadonnées de release ou de PR, les images de runner hébergées par GitHub avec une chaîne d'outils préinstallée très spécifique, et l'authentification cloud basée sur OIDC. Le schéma de migration qui fonctionne : commencez par un workflow qui ne fait que construire et tester, obtenez-le au vert, puis rajoutez les étapes de déploiement une par une.

Que se passe-t-il pour mon code si le VPS meurt ?

Le code lui-même est la seule partie qui va bien, et c'est l'avantage discret de Git sur presque tout autre service auto-hébergé : chaque clone est une copie complète de l'historique, donc tant qu'un développeur a une copie de travail à jour, le dépôt n'est pas perdu. Ce qui meurt avec le serveur, c'est tout ce qui entoure le code — tickets, discussions de pull requests, commentaires de revue, releases, wiki, utilisateurs, règles d'accès, historique de CI. Cette matière ne vit que dans la base de données, personne n'en a de clone, et c'est généralement la partie qui a mis des années à s'accumuler. Traitez donc les deux différemment. Pour le code, configurez un miroir push vers un second emplacement et vous obtenez une protection délocalisée, continue et automatique, sans effort. Pour les métadonnées, il vous faut une vraie sauvegarde : un dump PostgreSQL plus le répertoire de données, chiffré et envoyé vers une machine dans une autre juridiction selon un calendrier. L'étape 06 couvre les deux, et l'exercice de restauration à la fin est ce qui transforme cela d'une intention en une garantie.

Puis-je garder GitHub comme miroir plutôt que le quitter entièrement ?

Oui, et pour beaucoup de projets c'est la bonne réponse plutôt qu'un compromis. Forgejo prend en charge les miroirs push, donc votre instance est l'origine sur laquelle votre équipe travaille, et chaque push est répliqué automatiquement vers un dépôt sur GitHub, Codeberg ou une seconde instance Forgejo. La visibilité publique, les contributeurs de passage, les registres de paquets qui attendent une URL GitHub et le lien du gestionnaire de tickets dans un vieux README continuent tous de fonctionner, tandis que la source de vérité — ainsi que les dépôts privés, les journaux de CI et l'historique des tickets de tout ce que vous ne publiez pas — reste sur votre machine. Cela fonctionne aussi dans l'autre sens : les miroirs pull maintiennent une copie locale et toujours à jour des projets amont dont vous dépendez, ce qui vaut la peine pour tout ce dont vous seriez privé s'il était supprimé demain. La seule chose à décider avec soin, c'est ce que vous mettez en miroir : un miroir push d'un dépôt dont l'historique contient un secret que vous vouliez retirer republie ce secret, immédiatement et automatiquement.

Comment donner à un prestataire l'accès à un seul dépôt sans lui donner accès au reste ?

Utilisez le niveau qui correspond à la durée prévue de l'accès. Pour une personne qui va lire, committer et ouvrir des pull requests, créez un compte utilisateur et ajoutez-la comme collaborateur sur exactement les dépôts dont elle a besoin, avec un niveau de permission explicite — lecture, écriture ou admin — plutôt que de la mettre dans une équipe d'organisation qui accorde discrètement plus de droits. Pour un système automatisé qui n'a besoin que de récupérer — une cible de déploiement, une machine de build, un miroir — utilisez une clé de déploiement en lecture seule plutôt qu'un compte utilisateur : elle est limitée à un seul dépôt, n'accorde rien d'autre, et la révoquer revient à supprimer une seule ligne. Pour les scripts qui parlent à l'API, émettez un jeton d'accès limité plutôt que de communiquer un mot de passe. Deux habitudes font tenir l'ensemble : gardez les inscriptions fermées afin que les seuls comptes existants soient ceux que vous avez créés, et notez une date dans votre calendrier lorsque vous accordez un accès temporaire, car rien dans aucune forge ne vous rappellera jamais qu'un contrat a pris fin.

La forge a-t-elle besoin d'un nom de domaine, et cela révèle-t-il mon identité ?

Elle a besoin d'un nom d'hôte pour l'interface web, car les certificats TLS sont émis pour des noms, et aucun navigateur ne devrait faire confiance à une adresse IP ici. Elle n'a pas besoin que ce nom soit rattachable à vous. Trois choses à décider avec soin. Enregistrez le domaine quelque part qui n'exige pas l'identité que vous avez pris soin de protéger ailleurs, et gardez la confidentialité WHOIS activée — le bureau d'enregistrement est une décision de confiance distincte de celle de l'hébergeur, et c'est celle que tout le monde oublie. Attendez-vous à ce que le nom d'hôte devienne public dès que le certificat est émis, car tout certificat reconnu publiquement est publié dans des journaux de transparence des certificats que n'importe qui peut consulter ; un wildcard sur un domaine que vous utilisez déjà évite d'ajouter un nouveau nom cherchable. Et si vous préférez n'avoir aucun nom public du tout, l'alternative est réelle : placez la forge sur un service onion Tor ou accessible uniquement via WireGuard, faites entièrement l'impasse sur le certificat public, et acceptez que la commodité d'ouvrir un navigateur depuis n'importe où disparaisse avec.

NordBastion voit-il un inconvénient à ce que je fasse tourner une forge Git et des runners CI sur un VPS ?

Pas du tout — une forge et ses runners sont une charge de travail ordinaire, et c'est l'une des choses les plus courantes que construisent nos clients. La politique d'utilisation acceptable ne dit rien sur l'hébergement de code source ou sa compilation. Deux remarques opérationnelles plutôt que réglementaires. D'abord, un runner CI qui accepte des workflows d'inconnus est un service de calcul gratuit pour qui le trouve : l'abus classique est le minage de cryptomonnaie glissé dans une pull request, et il sera découvert en quelques jours à peine. Gardez les inscriptions fermées, et exigez une approbation avant l'exécution des workflows pour quiconque n'est pas déjà collaborateur. Ensuite, si vos builds tirent de grosses images de conteneurs à chaque exécution, c'est un trafic normal et il est inclus — mais cela vaut quand même la peine de les mettre en cache localement, car cela accélère votre pipeline et c'est la moindre des politesses envers les registres amont dont vous dépendez.

Obtenir la machine

Une forge bien à vous, sur un compte qui n'est pas votre nom. Sans KYC, payé en crypto.

Sentinel — 2 vCPU et 4 Go pour $3.90 par mois, à Helsinki, Stockholm, Oslo ou Reykjavík. Assez pour Forgejo, PostgreSQL et le reverse proxy, avec de la marge pour les dépôts que vous comptiez déplacer depuis longtemps. Ajoutez-en un second pour le runner. Aucun e-mail à l'inscription, aucune pièce d'identité, et aucun index de ce que vous poussez au niveau du produit.

Dernière révision · 2026-10-12 · Références · Documentation administrateur de Forgejo (installation, Actions, ligne de commande), la comparaison de Forgejo avec Gitea, les pages de gouvernance de Codeberg e.V., la documentation de PostgreSQL et de Caddy, git-config(1) · Fréquence · annuellement

Hébergement privé, conçu dans le Nord.