Pourquoi assembler cette stack sur un VPS
GitHub Actions et les API LLM cloud partagent le même défaut : ils déportent le traitement. Dans le premier cas, c'est votre code source qui tourne sur des runners mutualisés ; dans le second, c'est le contexte de vos requêtes qui est envoyé à un tiers. Pour une agence ou un indépendant qui gère du code client sous NDA, l'un ne peut pas aller sans corriger l'autre.
Gitea intègre nativement un moteur d'actions compatible GitHub Actions depuis sa version 1.19. Ollama expose une API REST locale sur le réseau Docker interne. Un runner act_runner en conteneur lit vos workflows .yml comme GitHub les lirait — et appelle Ollama à la place d'une API cloud. La stack entière fonctionne avec docker compose up -d et ne génère aucun trafic sortant vers les API de grands modèles.
Bénéfices concrets de cette architecture
- Confidentialité totale du code : les runners s'exécutent sur votre VPS, le code cloné ne quitte jamais votre réseau Docker interne.
- Zéro token envoyé à l'extérieur : Ollama sert l'inférence localement ; aucune requête ne part vers openai.com ou anthropic.com.
- Coût fixe et prévisible : pas de facturation à l'exécution de workflow, pas de facturation au token — une ligne de budget mensuelle, quelle que soit la charge.
- Workflows GitHub Actions réutilisables : Gitea Actions est compatible avec la syntaxe
.github/workflows/; vos pipelines migrent sans réécriture. - Modèles librement échangeables : Qwen2.5-Coder, DeepSeek-Coder, Llama 3.1 ou Mistral — une commande
ollama pullpour changer de modèle, sans toucher au pipeline. - Audit et traçabilité complets : logs de runners, historique des modèles chargés et journaux nginx restent sur votre infrastructure et vous appartiennent.
Prérequis matériels et logiciels
La contrainte dimensionnante est la mémoire vive : le modèle doit tenir entièrement en RAM pour que l'inférence reste réactive. Un modèle 7B quantisé au format Q4_K_M demande environ 5 à 6 Go de RAM ; en ajoutant Gitea (moins de 100 Mo au repos) et le runner, comptez 8 Go minimum pour un modèle 7B et 16 Go recommandés pour un modèle 13B.
Pour le CPU, deux vCPU suffisent pour Gitea et les runners ; l'inférence CPU-only sur un modèle 7B prend quelques secondes par réponse, ce qui est acceptable pour une revue de code automatisée. Un GPU dédié réduit ce délai à moins d'une seconde, mais n'est pas requis pour un usage pipeline CI.
Logiciels requis sur le VPS : Docker Engine et Docker Compose v2, un nom de domaine pointant vers votre serveur (pour les certificats TLS de Gitea), et les ports 3000 (Gitea) et 11434 (Ollama, réseau interne uniquement) disponibles.
Modèles recommandés selon la RAM disponible
- Qwen2.5-Coder:7B (format Q4_K_M, ~5 Go de RAM) — rapport qualité/ressources équilibré pour les revues de code en 2026 ; compréhension du contexte étendu et des conventions de style.
- DeepSeek-Coder:6.7B (format Q4, ~4,5 Go de RAM) — alternative compacte si la RAM est serrée ; précis sur le Python, le JavaScript et les diffs de moins de 200 lignes.
- Llama 3.1:8B (format Q4_K_M, ~5,5 Go de RAM) — généraliste multilingue, utile si vos projets mélangent le code et de la documentation en plusieurs langues.
- Mistral:7B (format Q4_K_M, ~4,5 Go de RAM) — réponses courtes et directes, idéal pour un résumé de diff plutôt qu'une analyse détaillée.
- Passage à un modèle 13B — sur un VPS de 16 Go ou plus,
codellama:13bouqwen2.5-coder:14baméliorent les revues sur des diffs larges ; le temps d'inférence CPU passe de ~5 s à ~15 s par appel.
Déployer la stack Gitea + Ollama + runner
Créer la structure de fichiers
Créez un répertoire de projet et un fichier docker-compose.yml qui déclare trois services : gitea, ollama et runner. Placez tous les volumes dans un sous-dossier data/ pour faciliter les sauvegardes.
mkdir -p ~/gitea-stack/data/{gitea,ollama,runner}
cd ~/gitea-stackÉcrire le docker-compose.yml
Le fichier déclare un réseau interne ai-net sur lequel les trois services communiquent. Ollama n'est pas exposé sur l'hôte : seul le runner peut l'atteindre via le réseau Docker.
cat > docker-compose.yml << 'EOF'
version: "3.8"
networks:
ai-net:
driver: bridge
volumes:
gitea-data:
ollama-data:
runner-data:
services:
gitea:
image: gitea/gitea:latest
restart: unless-stopped
networks: [ai-net]
ports:
- "3000:3000"
- "2222:22"
volumes:
- gitea-data:/data
environment:
- GITEA__server__DOMAIN=git.votredomaine.com
- GITEA__server__ROOT_URL=https://git.votredomaine.com
- GITEA__actions__ENABLED=true
ollama:
image: ollama/ollama:latest
restart: unless-stopped
networks: [ai-net]
volumes:
- ollama-data:/root/.ollama
runner:
image: gitea/act_runner:latest
restart: unless-stopped
networks: [ai-net]
volumes:
- runner-data:/data
- /var/run/docker.sock:/var/run/docker.sock
environment:
- GITEA_INSTANCE_URL=http://gitea:3000
- GITEA_RUNNER_REGISTRATION_TOKEN=${RUNNER_TOKEN}
- GITEA_RUNNER_NAME=local-runner
- OLLAMA_URL=http://ollama:11434
EOFDémarrer Gitea et récupérer le token runner
Démarrez uniquement Gitea en premier pour terminer l'installation initiale et générer le token d'enregistrement du runner.
docker compose up -d giteaOuvrez http://<ip-du-vps>:3000 dans votre navigateur, terminez l'assistant d'installation, puis allez dans Paramètres du site → Actions → Runners pour créer un token d'enregistrement. Notez ce token — vous en aurez besoin à l'étape suivante.
Démarrer Ollama et télécharger un modèle
Démarrez Ollama et téléchargez votre modèle. Pour un pipeline de revue de code, qwen2.5-coder:7b ou deepseek-coder:6.7b offrent un bon rapport qualité/ressources.
docker compose up -d ollama
# télécharger un modèle de code
docker exec gitea-stack-ollama-1 ollama pull qwen2.5-coder:7b
# vérifier que l'API répond sur le réseau interne
docker run --rm --network gitea-stack_ai-net curlimages/curl \
http://ollama:11434/api/tagsLa réponse JSON liste les modèles disponibles — confirmation que l'API Ollama est accessible depuis le réseau Docker interne.
Enregistrer le runner et démarrer la stack complète
Créez un fichier .env avec le token récupéré à l'étape 3, puis démarrez le runner.
echo "RUNNER_TOKEN=votre_token_ici" > .env
docker compose up -d runner
# vérifier que le runner est bien enregistré
docker compose logs runner | tail -20Vérifiez dans Paramètres du site → Actions → Runners que votre runner apparaît avec le statut Actif.
Créer un workflow qui appelle Ollama
Dans un dépôt Gitea, créez .gitea/workflows/review.yml. Le workflow clone le code, appelle l'endpoint /api/chat d'Ollama via curl et poste le résultat en commentaire de la pull request.
cat > .gitea/workflows/review.yml << 'EOF'
name: AI Code Review
on:
pull_request:
types: [opened, synchronize]
jobs:
review:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Revue de code via Ollama
run: |
DIFF=$(git diff HEAD~1 --unified=5 | head -200)
curl -s ${OLLAMA_URL:-http://ollama:11434}/api/chat \
-H 'Content-Type: application/json' \
-d "{\"model\": \"qwen2.5-coder:7b\", \"stream\": false,
\"messages\": [{\"role\": \"user\",
\"content\": \"Revue ce diff Git en français : $DIFF\"}]}" \
| jq -r '.message.content'
EOFPoussez ce fichier sur votre forge Gitea — le runner le détecte et lance le job sur le réseau interne, sans aucun accès à Internet.
Mettre Gitea derrière un reverse proxy HTTPS
Placez Gitea derrière nginx ou Caddy avec un certificat Let's Encrypt pour exposer la forge sur git.votredomaine.com. Le port 3000 ne doit plus être accessible directement depuis l'extérieur.
# exemple nginx minimal
cat > /etc/nginx/sites-available/gitea.conf << 'EOF'
server {
listen 443 ssl;
server_name git.votredomaine.com;
ssl_certificate /etc/letsencrypt/live/git.votredomaine.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/git.votredomaine.com/privkey.pem;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
EOF
nginx -t && systemctl reload nginxOllama reste uniquement sur le réseau Docker interne et n'est jamais exposé sur Internet.
Vérifier qu'aucun token ne part vers l'extérieur
Pour confirmer que les appels LLM restent bien sur votre VPS, capturez le trafic réseau sortant pendant l'exécution d'un workflow : tcpdump -i eth0 -n 'dst port 443 and dst host openai.com'. Zéro paquet capturé valide l'isolation. Vous pouvez aussi inspecter les logs Ollama (docker compose logs ollama) : chaque requête d'inférence y est tracée avec l'adresse source — elle doit toujours être l'IP du réseau Docker interne, jamais une IP externe.
Coût comparé : VPS self-hosted vs API cloud
Un modèle 7B traite environ 30 000 à 50 000 tokens par minute sur deux vCPU. Une revue de diff de 150 lignes consomme environ 800 tokens (prompt + réponse). Sur 1 000 revues mensuelles — un volume réaliste pour une équipe de cinq développeurs — cela représente 800 000 tokens.
Côté cloud, gpt-4o-mini facture 0,15 $ pour 1 million de tokens en entrée et 0,60 $ en sortie (tarifs OpenAI de septembre 2026). Pour 800 000 tokens : environ 0,70 $ par mois. Claude Haiku est dans la même fourchette. L'argument financier est donc faible pour des volumes modérés.
L'argument qui tient est la confidentialité : les diffs de code client, les clés d'API présentes dans les messages d'erreur, les noms de variables métier — tout cela sort du réseau avec chaque appel à une API externe. Sur un VPS ServOrbit à 8 Go de RAM (autour de 15 € par mois), l'inférence locale ne coûte rien de plus et aucun token ne part vers l'extérieur.
Aller plus loin : observer et enrichir le pipeline
Une fois la stack de base opérationnelle, deux extensions sont naturelles. La première est d'ajouter Langfuse en cinquième service : il trace chaque appel LLM (modèle, durée, tokens consommés, résultat), ce qui permet de mesurer la qualité des revues, de détecter les régressions de modèle et de comparer les résultats sur votre corpus réel avant de changer de version. La seconde est de paralléliser les runners : un second act_runner avec un label différent (par exemple gpu) peut cibler un nœud avec accès GPU pour les jobs d'inférence lourds, tandis que les jobs légers (lint, tests unitaires) continuent sur le runner CPU de base.
Pour la sauvegarde, les volumes gitea-data et ollama-data contiennent respectivement les dépôts et les modèles téléchargés. Un snapshot quotidien de ces deux volumes suffit à restaurer la stack complète en moins de dix minutes. Les modèles Ollama pouvant être retéléchargés à la demande depuis le registre public, seul gitea-data est réellement critique pour la continuité de service et la préservation de l'historique Git.