SSD Nodes Learn 🎉 VPS da $5.50/mese
Guide Matt ConnorDi Matt Connor · Aggiornato 2026-08-13

Server Git self-hosted: Forgejo, Gitea o cgit?

Confronta quattro server Git self-hosted in base alla RAM: repository bare via SSH, cgit, Forgejo o Gitea e GitLab. Scopri cosa gestisce un VPS da 1 GB.

Quale server Git self-hosted eseguire

Un server Git self-hosted non è un singolo prodotto. La RAM (memoria ad accesso casuale) disponibile sul VPS determina quale versione puoi eseguire. Git non richiede un daemon dedicato: un repository bare e un account SSH (secure shell) costituiscono già un server funzionante anche sul server più piccolo che puoi noleggiare. Tutto ciò che supera questa configurazione è un'applicazione web che scegli di eseguire insieme a Git. Ogni livello aggiuntivo richiede memoria che un VPS di piccole dimensioni potrebbe non avere.

Esistono quattro livelli. Un repository bare tramite SSH, senza alcun processo in ascolto aggiuntivo. cgit, una vista web veloce in sola lettura che non richiede un database. Forgejo o Gitea, forge completi con account, issue e pull request, che richiedono alcune centinaia di megabyte. GitLab, che richiede un server con risorse molte volte superiori a quelle necessarie per le altre soluzioni.

Decidi in base alle attività che devi svolgere, quindi confronta il requisito di memoria con le risorse incluse nel piano che stai pagando.

Quanta RAM richiede realmente ciascuna opzione

Solo due di questi progetti pubblicano un valore relativo all’hardware. Considera il valore pubblicato come una soglia minima, non come una garanzia, e misura la tua istanza dopo l’avvio usando systemd-cgtop o ps -o rss= -C forgejo.

ChartRAM the projects document, official docs, August 2026
The data behind this chart
[
  {
    "label": "Gitea, small team",
    "ram_gb": 1
  },
  {
    "label": "GitLab, memory constrained",
    "ram_gb": 8
  },
  {
    "label": "GitLab, single node baseline",
    "ram_gb": 16
  }
]

Gitea documenta 1 GB di RAM con 2 core CPU come quantità generalmente sufficiente per piccoli team e progetti, e indica Raspberry Pi 3 come adeguato per carichi di lavoro ridotti. GitLab documenta 16 GB come base per un’installazione a nodo singolo e 8 GB come limite inferiore per quello che la sua stessa pagina definisce un ambiente con memoria limitata. Forgejo non pubblica alcun requisito hardware. È un fork di Gitea e si comporta in modo analogo, quindi il valore indicato da Gitea è il riferimento pubblicato più vicino disponibile.

Su un VPS da 1 GB questo significa che i repository bare e cgit funzionano con margine, perché nessuno dei due esegue un servizio residente. Forgejo o Gitea si avviano e possono servire un piccolo team usando SQLite, ma sei già sulla soglia minima documentata. Lascia quindi PostgreSQL e il runner CI (continuous integration) fuori da quel server. Se l’interfaccia web scompare senza mostrare errori, esegui sudo dmesg -T | grep -i oom e cerca una riga come Out of memory: Killed process 1181 (forgejo). Indica che il kernel ha terminato il processo tramite l’OOM killer. GitLab su un server da 1 GB non è un problema di ottimizzazione. Non funzionerà.

Livello 0: un repository bare tramite SSH

Git non dispone di un demone di rete da avviare. git push tramite SSH esegue git-receive-pack sull'host remoto come un normale processo Unix, quindi qualsiasi account raggiungibile con una chiave è già un remote Git. Crea un account per i repository e mantieni i repository al di fuori della relativa home directory, perché su Ubuntu 24.04 una nuova home directory ha modalità 0750 e una vista web aggiunta in seguito non può accedervi.

sudo adduser --system --shell /bin/bash --gecos 'Git Version Control' \
  --group --disabled-password --home /home/git git
sudo install -d -m 0755 -o git -g git /srv/git
sudo -u git git init --bare /srv/git/project.git

--bare crea un repository senza working copy, che è il tipo di repository utilizzato da un server. Il push verso un repository che dispone di una working copy viene rifiutato con refusing to update checked out branch: refs/heads/main, ed è l'errore più comune a questo livello.

Ora assegna una chiave all'account e clona il repository.

sudo -u git install -d -m 700 /home/git/.ssh
sudo -u git tee -a /home/git/.ssh/authorized_keys <<'EOF'
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIexamplekeyhere alice@laptop
EOF
sudo -u git chmod 600 /home/git/.ssh/authorized_keys
git remote add origin git@vps.example.com:/srv/git/project.git
git push -u origin main

Un primo push completato correttamente termina con * [new branch] main -> main. Se termina con git@vps.example.com: Permission denied (publickey), l'autenticazione non è mai stata completata; in questo caso leggi il log del server con sudo journalctl -u ssh -n 20. Una riga con Authentication refused: bad ownership or modes for file /home/git/.ssh/authorized_keys indica che la modalità del file è errata, perché sshd ignora un file di chiavi scrivibile da altri utenti.

Ora rimuovi la shell dall'account.

command -v git-shell | sudo tee -a /etc/shells
sudo chsh -s "$(command -v git-shell)" git

git-shell accetta soltanto i pochi comandi che Git invia tramite SSH, quindi un accesso interattivo ora si interrompe con un messaggio invece di mostrare un prompt:

fatal: Interactive git shell is not enabled.
hint: ~/git-shell-commands should exist and have read and execute access.

Questo è l'intero server. Non sono presenti database né processi web da aggiornare. Rinunci a tutte le funzioni di una forge: niente consultazione del codice, issue tracker, pull request o autorizzazioni per singolo utente. Ogni chiave presente in quel file può leggere e scrivere in ogni repository di proprietà dell'utente git.

Livello 1: cgit fornisce una vista web senza database

cgit è un programma CGI (common gateway interface) scritto in C. Il server web lo esegue una volta per ogni richiesta, legge direttamente dal disco i repository e non conserva alcuno stato proprio. Ubuntu 24.04 lo include nel componente universe.

sudo apt update
sudo apt install -y cgit fcgiwrap nginx
sudo install -d -o www-data -g www-data /var/cache/cgit

Indicateli la directory dei repository in /etc/cgitrc:

root-title=Git on example.com
css=/cgit.css
logo=/cgit.png
cache-size=1000
cache-root=/var/cache/cgit
snapshots=tar.gz zip
scan-path=/srv/git

scan-path esamina ricorsivamente quella directory ed elenca ogni repository trovato, quindi un nuovo repository bare viene visualizzato senza ulteriore configurazione. cache-size indica il numero di pagine memorizzate nella cache; la cache resta disabilitata quando il valore è zero. Prima di aggiungere righe, leggete il contenuto che il pacchetto ha già inserito in /etc/cgitrc, perché i pacchetti Debian e Ubuntu forniscono alcune impostazioni predefinite.

Ogni voce mostra la prima riga del file description del repository, quindi un nuovo repository bare viene visualizzato come Unnamed repository; edit this file 'description' to name the repository.. Correggetelo una volta per ogni repository:

echo 'Project X, internal tooling' | sudo -u git tee /srv/git/project.git/description
Il file del sito nginx e come verificarlo
server {
    listen 80;
    server_name git.example.com;
    root /usr/share/cgit;

    try_files $uri @cgit;

    location @cgit {
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME /usr/lib/cgit/cgit.cgi;
        fastcgi_param PATH_INFO $uri;
        fastcgi_param QUERY_STRING $args;
        fastcgi_param HTTP_HOST $server_name;
        fastcgi_pass unix:/run/fcgiwrap.socket;
    }
}
sudo systemctl enable --now fcgiwrap.socket
sudo nginx -t && sudo systemctl reload nginx
systemctl show fcgiwrap.socket -p Listen

root /usr/share/cgit serve cgit.css e cgit.png come file statici, mentre try_files inoltra tutto il resto al CGI in /usr/lib/cgit/cgit.cgi. Una pagina di errore 502, con connect() to unix:/run/fcgiwrap.socket failed (2: No such file or directory) in /var/log/nginx/error.log, indica che l'unità socket non è in esecuzione oppure è in ascolto su un altro percorso. La riga systemctl show stampa il percorso effettivamente utilizzato.

Prima di basarvi su questa configurazione, considerate due limitazioni. cgit è in sola lettura e non dispone di autenticazione, quindi tutto ciò che si trova sotto scan-path è pubblico: tenete i repository privati fuori da quella directory oppure proteggete l'intero sito con l'autenticazione HTTP Basic. Inoltre, il CGI viene eseguito con l'utente del server web, che deve poter attraversare /srv/git e leggere ogni repository. Una directory a cui l'utente non può accedere viene mostrata come un indice vuoto, non come un errore.

Livello 2: Forgejo o Gitea per issue e pull request

Forgejo e Gitea seguono lo stesso modello: un unico binario Go che fornisce una forge web con utenti, organizzazioni, issue, pull request, release, un registro dei pacchetti e un sistema CI integrato. L'installazione completa consiste nel binario e in SQLite. Per questo possono essere eseguiti su hardware che GitLab non supporterebbe. Il file Compose riportato di seguito è quello della documentazione di Forgejo, con il tag dell'immagine indicato dalla documentazione ad agosto 2026.

networks:
  forgejo:
    external: false

services:
  server:
    image: codeberg.org/forgejo/forgejo:16
    container_name: forgejo
    environment:
      - USER_UID=1000
      - USER_GID=1000
    restart: always
    networks:
      - forgejo
    volumes:
      - ./forgejo:/data
      - /etc/localtime:/etc/localtime:ro
    ports:
      - '3000:3000'
      - '222:22'
docker compose up -d
docker compose ps
curl -sI http://127.0.0.1:3000 | head -1

La riga curl dovrebbe stampare una riga di stato HTTP. Prima di completare la configurazione iniziale, potrebbe restituire un redirect a /install. Anche in questo caso il servizio è attivo. Se invece il container termina, la causa più comune sono i permessi del proprietario: la directory ./forgejo deve appartenere all'UID (user id) indicato in USER_UID, altrimenti il processo non può scrivere nella propria directory dei dati. Docker Compose su un VPS descrive in dettaglio la struttura dei file e la regola relativa al proprietario del volume.

Due risposte nella pagina di configurazione determinano il funzionamento degli URL di clone. La porta SSH deve essere 222, perché il file Compose associa la porta 222 dell'host alla porta 22 del container. Il dominio deve essere il nome che gli utenti inseriranno effettivamente. Se uno dei due valori è errato, ogni pagina del repository propone un comando di clone che non funziona per chiunque lo copi. In seguito entrambi si trovano nella sezione [server] di app.ini, come SSH_PORT, SSH_DOMAIN e ROOT_URL.

Per un'istanza pubblica, pubblica la porta web soltanto sull'indirizzo di loopback ('127.0.0.1:3000:3000') e usa nginx come reverse proxy per TLS (transport layer security). Gitea può essere installato allo stesso modo dall'immagine gitea/gitea, oppure come singolo binario con una sola unità systemd e un solo app.ini. Ad agosto 2026, la release stabile corrente è la 1.27.1.

Mantieni SQLite finché possibile. L'istanza resta composta da un solo processo e un solo file e sopravvive ai riavvii senza richiedere un servizio aggiuntivo da supervisionare. PostgreSQL giustifica il costo quando più persone eseguono scritture contemporaneamente, perché SQLite serializza le scritture e le esecuzioni CI prolungate scrivono continuamente. Entrambi i progetti consentono di migrare in seguito un'istanza esistente a PostgreSQL, quindi non è una scelta definitiva.

Forgejo o Gitea: quali sono le differenze effettive

La linea di sviluppo è condivisa. Gitea è nato come fork di Gogs nel 2016. Alla fine del 2022, il controllo del dominio e del marchio Gitea è passato a una società, Gitea Ltd, e diversi maintainer, insieme a Codeberg, hanno avviato Forgejo. Forgejo è pubblicato da Codeberg e.V., un'associazione senza scopo di lucro registrata in Germania, e nel 2024 è passato dalla licenza MIT alla GPLv3 (GNU general public license version 3). Gitea mantiene la licenza MIT ed è sviluppato con il supporto di interessi commerciali.

Nell'uso quotidiano, i set di funzionalità sono simili. Il percorso di migrazione tra i due progetti, invece, è diverso. Forgejo v10.0, pubblicato a gennaio 2025, è stata l'ultima release in grado di utilizzare direttamente un database Gitea, e soltanto da Gitea v1.22 o versioni precedenti. Ad agosto 2026 Gitea è alla versione 1.27.1, quindi un'istanza Gitea aggiornata non dispone di una procedura supportata per il passaggio diretto a Forgejo. Scegli una delle due soluzioni prima di inserirvi dati e considera ogni migrazione successiva come un'operazione di esportazione e nuova importazione.

Una regola semplice per la scelta. Se per te la governance è importante o vuoi che il progetto resti presso un'organizzazione senza scopo di lucro, usa Forgejo. Se vuoi una base di installazioni più ampia e un'opzione di supporto commerciale, usa Gitea. Entrambi i progetti sono sviluppati apertamente e pubblicano release frequenti: Forgejo pubblica una release stabile ogni tre mesi e una release LTS (long term support) ogni anno; ad agosto 2026, la versione corrente è v16.0.2 e la versione LTS è v15.0.6.

Livello 3: quanto costa GitLab prima di fare qualsiasi cosa

GitLab CE è una categoria di software diversa. Una singola istanza è composta da servizi che collaborano tra loro: Puma per l'applicazione web, Sidekiq per i job in background, PostgreSQL, Redis, Gitaly per l'accesso ai repository e nginx come proxy front-end. Il pacchetto Omnibus li installa insieme. Questo semplifica l'installazione, ma aumenta il consumo minimo di memoria.

La pagina dei requisiti di GitLab indica 16 GB di RAM e 8 vCPU come configurazione di riferimento per un'installazione su un singolo nodo. Indica inoltre 8 GB come valore minimo in un ambiente con memoria limitata. La stessa pagina consiglia di disabilitare lo swap, perché l'uso dello swap sotto carico degrada gravemente le prestazioni dell'istanza. Questi sono i valori pubblicati ad agosto 2026. Nel corso degli anni sono aumentati, quindi consulta nuovamente la pagina prima di dimensionare il server.

Con questa dotazione ottieni funzionalità concrete: un container registry, un package registry, autorizzazioni dettagliate, funzioni di compliance e audit e una CI testata su larga scala. Se nessuno nel tuo team sa indicare una funzionalità di quell'elenco che gli serve nel trimestre corrente, stai pagando un VPS più grande senza ottenere alcun vantaggio.

Il modello di accesso SSH: un utente Git e molte chiavi

Ogni livello autentica gli utenti nello stesso modo. Esiste un account Unix denominato git e tutte le chiavi pubbliche vengono inserite nel file ~/.ssh/authorized_keys di quell'account. L'autenticazione si basa sulla chiave. L'autorizzazione dipende dalle opzioni scritte davanti alla chiave sulla stessa riga.

Una riga contenente una chiave senza opzioni concede al titolare tutte le operazioni consentite a quell'account. Un comando forzato limita l'accesso a Git:

restrict,command="git-shell -c \"$SSH_ORIGINAL_COMMAND\"" ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIexamplekeyhere alice@laptop

restrict, disponibile da OpenSSH 7.2, disabilita con una sola parola il port forwarding, l'agent forwarding, X11 e l'allocazione di una PTY (pseudo-terminal). command= sostituisce qualsiasi comando richiesto dal client con quello specificato e Git continua a funzionare perché invia la propria richiesta in $SSH_ORIGINAL_COMMAND.

Una forge scrive automaticamente quel file. Questa è la differenza sostanziale tra il livello 0 e il livello 2. Forgejo e Gitea riscrivono authorized_keys con una riga per ogni chiave registrata. Ogni riga contiene un comando forzato che identifica la chiave tramite il relativo ID nel database:

command="/usr/local/bin/forgejo --config=/etc/forgejo/app.ini serv key-3",no-port-forwarding,no-x11-forwarding,no-agent-forwarding,no-pty ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIexamplekeyhere alice

Il comando forzato consente di trasformare un unico account Unix in autorizzazioni distinte per ogni utente: key-3 comunica alla forge quale utente si sta connettendo e la forge verifica che l'utente abbia accesso al repository prima del trasferimento di qualsiasi oggetto. Non modificare manualmente quel file su un server gestito dalla forge, perché viene riscritto a partire dal database e la riga aggiunta scompare. Le deploy key usano lo stesso meccanismo: una deploy key è una normale chiave SSH registrata per un singolo repository, in genere in sola lettura. Il controllo viene eseguito dalla forge anziché da sshd.

Due pratiche sono più importanti di tutte le configurazioni precedenti. Assegna una chiave a ogni persona o macchina e non usare mai una chiave condivisa, perché revocare una chiave condivisa significa sostituirla per tutti contemporaneamente. Rimuovi le chiavi il giorno in cui una persona lascia l'organizzazione, perché una vecchia chiave presente in quel file crea un accesso permanente che nessuno monitora. Una buona gestione delle chiavi SSH su un server illustra i tipi di chiave e le passphrase; tutto il contenuto si applica anche in questo caso senza modifiche. Se il server è nuovo, i primi dieci minuti su un nuovo VPS sono il passaggio corretto da eseguire prima di pubblicarvi i repository.

Posso eseguire GitHub Actions sul mio server Git?

Puoi eseguire workflow scritti con la sintassi di GitHub Actions. Non puoi eseguire GitHub. Forgejo Actions è abilitato per impostazione predefinita da Forgejo v1.21 e legge i file dei workflow da .forgejo/workflows in ogni repository. Gitea Actions funziona allo stesso modo e legge .gitea/workflows. Entrambi richiedono un secondo programma, il runner, che deve essere installato e registrato sulla tua istanza usando un token disponibile nelle impostazioni di amministrazione. Molte action pubblicate funzionano senza modifiche; non funzionano invece quelle che chiamano l'API di GitHub o che si aspettano un'infrastruttura fornita da GitHub.

Considera due conseguenze. Il runner avvia un container per ogni job, quindi richiede un container engine e una quantità di memoria dedicata. Per questo non va eseguito sullo stesso server da 1 GB che ospita il forge. Inoltre, il runner esegue qualsiasi istruzione contenuta nel file del workflow. La documentazione di Forgejo lo afferma esplicitamente: il runner esegue codice da remoto. Quando possibile, assegnagli un host dedicato. In alternativa, usa almeno un utente non privilegiato dedicato e un token di registrazione limitato a un solo repository.

Se i tuoi repository restano su GitHub e vuoi usare soltanto risorse di calcolo su hardware sotto il tuo controllo, la configurazione è diversa e richiede passaggi diversi: un runner self-hosted di GitHub Actions si collega a un repository GitHub e non richiede nulla di tutto questo. Se stai ancora valutando il costo dell'abbandono, cosa offre realmente GitHub separa il servizio di hosting Git dalla rete che lo circonda.

Backup: i repository contengono solo metà dello stato

Un repository bare è una directory, quindi copiarlo significa copiare tutto il suo contenuto. Un clone mirror da un'altra macchina è un backup effettivo e viene aggiornato sul posto:

git clone --mirror git@vps.example.com:/srv/git/project.git
cd project.git && git remote update

Questo comando recupera ogni ref e ogni oggetto. Non recupera gli hook lato server né il file description, quindi, se usi gli hook, conserva anche una copia a livello di file della directory.

Un forge conserva nel database issue, pull request, utenti, chiavi e autorizzazioni. Una copia dei soli repository elimina tutti questi dati. Entrambi i progetti forniscono un comando dump che scrive database, repository, configurazione e allegati in un unico archivio:

sudo -u git forgejo dump -c /etc/forgejo/app.ini -f /var/backups/forgejo-dump.zip

Con Docker, lo stesso comando viene eseguito all'interno del container. Il percorso della configurazione dipende dall'immagine, quindi verificalo prima di digitare il comando:

docker compose exec server ls /data/gitea/conf
docker compose exec -u git server forgejo dump -c /data/gitea/conf/app.ini

Eseguilo con l'utente proprietario dei dati e scrivi l'archivio in una directory su cui quell'utente può scrivere. Poi copia l'archivio fuori dal server, perché un backup che esiste soltanto sulla macchina di cui viene eseguito il backup non è un backup. Il ripristino è il passaggio che spesso viene saltato: estrai ora un dump su una macchina di riserva, così impari la procedura in un momento tranquillo anziché durante un'interruzione del servizio.

Scegli in base allo scenario

Una persona con un laptop e un VPS, senza necessità di navigare nel codice: repository bare tramite SSH. Non è necessario eseguire servizi aggiuntivi e non c'è nulla da aggiornare.

Lo stesso scenario, con la necessità di leggere il codice in un browser e condividere link al codice: aggiungi cgit. Non serve ancora un database e non rimane in esecuzione alcun servizio aggiuntivo.

Un team che revisiona il codice degli altri membri e tiene traccia delle issue: Forgejo o Gitea, con almeno 2 GB di RAM. Sposta il runner CI su una seconda macchina quando i job diventano impegnativi.

Un'organizzazione che ha bisogno di un container registry e di audit trail, con 16 GB di RAM disponibili sul server: GitLab. Se il budget è inferiore, non avviarlo.

Passare ai primi tre livelli è semplice, perché in tutti i casi i repository sono normali directory Git su disco. Inizia dal livello più basso che soddisfa le esigenze. Se stai valutando quali altri servizi meritano spazio sullo stesso server, la selezione dei servizi che vale la pena eseguire in self-hosting colloca un server Git accanto agli altri servizi che competono per la stessa RAM.

FAQ

È possibile eseguire Forgejo o Gitea su un VPS con 1 GB?

Sì, per un team piccolo, usando SQLite e senza altri carichi pesanti sul server. La documentazione di Gitea indica 1 GB di RAM e 2 core CPU come risorse normalmente sufficienti per team e progetti piccoli; Forgejo è un fork di Gitea con requisiti analoghi. Non installare PostgreSQL o un runner CI su quella macchina. Se il servizio scompare senza errori nel proprio log, esegui sudo dmesg -T | grep -i oom: una riga che indica il processo terminato significa che è intervenuto l'out-of-memory killer del kernel. In questo caso serve un piano più grande, non un flag di ottimizzazione.

Qual è la differenza tra Forgejo e Gitea?

Condividono la storia del codice e la maggior parte delle funzionalità. Gitea è nato come fork di Gogs nel 2016, mentre Forgejo è nato come fork di Gitea alla fine del 2022, dopo che il controllo del marchio Gitea è passato a un'azienda. Forgejo è pubblicato da Codeberg e.V., un'organizzazione non profit tedesca, con licenza GPLv3; Gitea mantiene la licenza MIT e ha il supporto di un'azienda. La differenza pratica riguarda il percorso di migrazione. Forgejo v10.0, pubblicato a gennaio 2025, è stata l'ultima release in grado di usare direttamente un database Gitea, e solo a partire da Gitea v1.22 o versioni precedenti. Un'istanza Gitea aggiornata non può quindi essere convertita sul posto tramite una procedura supportata.

Posso eseguire workflow GitHub Actions su un server Git self-hosted?

Forgejo Actions e Gitea Actions eseguono entrambi workflow scritti con la sintassi YAML di GitHub Actions, letti da .forgejo/workflows e .gitea/workflows. Devi installare un programma runner separato e registrarlo presso la tua istanza. Molte action pubblicate funzionano senza modifiche, mentre tutto ciò che chiama l'API GitHub non funziona. Il runner esegue codice arbitrario proveniente dai tuoi repository e avvia un container per ogni job. Assegna quindi al runner un host dedicato oppure almeno un utente non privilegiato dedicato, e non installarlo su un server con 1 GB che esegue già il forge.

Come si esegue il backup di un server Git self-hosted?

Per i repository bare, git clone --mirror eseguito da un'altra macchina copia ogni ref e oggetto, mentre git remote update all'interno di quel mirror lo aggiorna. Con Forgejo o Gitea, i repository rappresentano solo una parte dello stato, perché issue, pull request, utenti e chiavi sono memorizzati nel database. Usa il dump integrato, sudo -u git forgejo dump -c /etc/forgejo/app.ini, oppure esegui lo stesso comando all'interno del container in un'installazione Docker. Copia l'archivio fuori dal server e ripristinalo una volta su una macchina di riserva, per verificare che la procedura funzioni.

#git#self-hosting#forgejo#gitea#ssh