Por qué montar este stack en un VPS
GitHub Actions y las API LLM en la nube comparten el mismo defecto: externalizan el procesamiento. En el primer caso, tu código fuente se ejecuta en runners compartidos; en el segundo, el contexto de tus peticiones se envía a un tercero. Para una agencia o freelance que gestiona código de clientes bajo NDA, corregir uno sin el otro no es suficiente.
Gitea incorpora nativamente un motor de acciones compatible con GitHub Actions desde su versión 1.19. Ollama expone una API REST local en la red interna de Docker. Un contenedor act_runner lee tus workflows .yml exactamente como lo haría GitHub — y llama a Ollama en lugar de una API en la nube. Todo el stack funciona con docker compose up -d y no genera tráfico saliente hacia las API de grandes modelos.
Beneficios concretos de esta arquitectura
- Confidencialidad total del código: los runners se ejecutan en tu VPS, el código clonado nunca sale de tu red interna de Docker.
- Cero tokens enviados al exterior: Ollama sirve la inferencia localmente; ninguna petición llega a openai.com ni a anthropic.com.
- Coste fijo y predecible: sin facturación por ejecución de workflow, sin facturación por token — una sola línea de presupuesto mensual, independientemente de la carga.
- Workflows de GitHub Actions reutilizables: Gitea Actions es compatible con la sintaxis
.github/workflows/; tus pipelines migran sin reescritura. - Modelos intercambiables libremente: Qwen2.5-Coder, DeepSeek-Coder, Llama 3.1 o Mistral — un comando
ollama pullpara cambiar de modelo sin tocar el pipeline. - Auditoría y trazabilidad completas: logs de runners, historial de modelos cargados y registros de nginx permanecen en tu infraestructura y te pertenecen.
Requisitos previos de hardware y software
La restricción determinante es la memoria RAM: el modelo debe caber completamente en memoria para que la inferencia sea ágil. Un modelo 7B cuantizado en formato Q4_K_M requiere entre 5 y 6 GB de RAM; sumando Gitea (menos de 100 MB en reposo) y el runner, cuenta con 8 GB mínimo para un modelo 7B y 16 GB recomendados para un modelo 13B.
Para CPU, dos vCPU son suficientes para Gitea y los runners; la inferencia solo CPU en un modelo 7B tarda unos pocos segundos por respuesta, aceptable para revisión de código automatizada. Una GPU dedicada reduce esto a menos de un segundo, pero no es necesaria para uso en pipelines CI.
Software necesario en el VPS: Docker Engine y Docker Compose v2, un nombre de dominio apuntando a tu servidor (para los certificados TLS de Gitea), y los puertos 3000 (Gitea) y 11434 (Ollama, solo red interna) disponibles.
Modelos recomendados según la RAM disponible
- Qwen2.5-Coder:7B (formato Q4_K_M, ~5 GB de RAM) — relación calidad/recursos equilibrada para revisiones de código en 2026; comprende el contexto extendido y las convenciones de estilo.
- DeepSeek-Coder:6.7B (formato Q4, ~4,5 GB de RAM) — alternativa compacta cuando la RAM es escasa; preciso en Python, JavaScript y diffs de menos de 200 líneas.
- Llama 3.1:8B (formato Q4_K_M, ~5,5 GB de RAM) — generalista multilingüe, útil cuando los proyectos mezclan código y documentación en varios idiomas.
- Mistral:7B (formato Q4_K_M, ~4,5 GB de RAM) — respuestas cortas y directas, ideal para un resumen del diff más que para un análisis detallado.
- Pasar a un modelo 13B — en un VPS con 16 GB o más,
codellama:13boqwen2.5-coder:14bmejoran las revisiones en diffs grandes; el tiempo de inferencia CPU pasa de ~5 s a ~15 s por llamada.
Desplegar el stack Gitea + Ollama + runner
Crear la estructura de archivos
Crea un directorio de proyecto y un archivo
docker-compose.ymlque declare tres servicios:gitea,ollamayrunner. Coloca todos los volúmenes en un subdirectoriodata/para simplificar las copias de seguridad.mkdir -p ~/gitea-stack/data/{gitea,ollama,runner} cd ~/gitea-stackEscribir el docker-compose.yml
El archivo declara una red interna
ai-netpor la que se comunican los tres servicios. Ollama no se expone en el host: solo el runner puede acceder a él a través de la red 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.yourdomain.com - GITEA__server__ROOT_URL=https://git.yourdomain.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 EOFArrancar Gitea y obtener el token del runner
Arranca solo Gitea primero para completar la instalación inicial y generar el token de registro del runner.
docker compose up -d giteaAbre
http://<ip-del-vps>:3000en tu navegador, completa el asistente de instalación, luego ve a Administración del sitio → Acciones → Runners para crear un token de registro. Anota este token — lo necesitarás en el siguiente paso.Arrancar Ollama y descargar un modelo
Arranca Ollama y descarga tu modelo. Para un pipeline de revisión de código,
qwen2.5-coder:7bodeepseek-coder:6.7bofrecen una buena relación calidad/recursos.docker compose up -d ollama # descargar un modelo de código docker exec gitea-stack-ollama-1 ollama pull qwen2.5-coder:7b # verificar que la API responde en la red interna docker run --rm --network gitea-stack_ai-net curlimages/curl \ http://ollama:11434/api/tagsLa respuesta JSON lista los modelos disponibles — confirmación de que la API de Ollama es accesible desde la red interna de Docker.
Registrar el runner y arrancar el stack completo
Crea un archivo
.envcon el token obtenido en el paso 3, luego arranca el runner.echo "RUNNER_TOKEN=tu_token_aqui" > .env docker compose up -d runner # verificar que el runner se registró correctamente docker compose logs runner | tail -20Verifica en Administración del sitio → Acciones → Runners que tu runner aparece con estado Activo.
Crear un workflow que llame a Ollama
En un repositorio de Gitea, crea
.gitea/workflows/review.yml. El workflow clona el código, llama al endpoint/api/chatde Ollama mediantecurly publica el resultado como comentario en 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: Revisión de código con 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\": \"Revisa este diff de Git: $DIFF\"}]}" \ | jq -r '.message.content' EOFSube este archivo a tu forge Gitea — el runner lo detecta y ejecuta el job en la red interna, sin ningún acceso a Internet.
Poner Gitea detrás de un proxy inverso HTTPS
Coloca Gitea detrás de nginx o Caddy con un certificado Let's Encrypt para exponer la forge en
git.yourdomain.com. El puerto 3000 no debe ser accesible directamente desde el exterior.# ejemplo nginx mínimo cat > /etc/nginx/sites-available/gitea.conf << 'EOF' server { listen 443 ssl; server_name git.yourdomain.com; ssl_certificate /etc/letsencrypt/live/git.yourdomain.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/git.yourdomain.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 permanece exclusivamente en la red interna de Docker y nunca se expone en Internet.
Verificar que ningún token sale de tu servidor
Para confirmar que las llamadas LLM permanecen en tu VPS, captura el tráfico de red saliente durante la ejecución de un workflow: tcpdump -i eth0 -n 'dst port 443 and dst host openai.com'. Cero paquetes capturados valida el aislamiento. También puedes inspeccionar los logs de Ollama (docker compose logs ollama): cada petición de inferencia se registra con su dirección de origen — siempre debe ser una IP de la red interna de Docker, nunca una IP externa.
Comparativa de coste: VPS self-hosted vs API cloud
Un modelo 7B procesa entre 30 000 y 50 000 tokens por minuto con dos vCPU. Una revisión de diff de 150 líneas consume unos 800 tokens (prompt + respuesta). Con 1 000 revisiones mensuales — un volumen realista para un equipo de cinco desarrolladores — el total es 800 000 tokens.
En la nube, gpt-4o-mini factura 0,15 $ por millón de tokens de entrada y 0,60 $ por millón de salida (precios de OpenAI en septiembre de 2026). Para 800 000 tokens: aproximadamente 0,70 $ al mes. Claude Haiku está en el mismo rango. El argumento financiero es por tanto débil a volúmenes moderados.
El argumento que se sostiene es la confidencialidad: los diffs del código de clientes, las claves de API que aparecen en mensajes de error, los nombres de variables de negocio — todo ello sale de tu red con cada llamada a una API externa. En un VPS ServOrbit con 8 GB de RAM (unos 15 € al mes), la inferencia local no añade coste adicional y ningún token sale nunca de tu servidor.
Ir más lejos: observar y enriquecer el pipeline
Una vez el stack base está en marcha, dos extensiones son naturales. La primera es añadir Langfuse como quinto servicio: rastrea cada llamada LLM (modelo, duración, tokens consumidos, resultado), permitiéndote medir la calidad de las revisiones, detectar regresiones de modelo y comparar resultados en tu corpus real antes de cambiar de versión. La segunda es paralelizar los runners: un segundo act_runner con una etiqueta diferente (por ejemplo gpu) puede apuntar a un nodo con acceso GPU para trabajos de inferencia pesada, mientras que los trabajos ligeros (lint, tests unitarios) continúan en el runner CPU base.
Para las copias de seguridad, los volúmenes gitea-data y ollama-data contienen respectivamente los repositorios y los modelos descargados. Un snapshot diario de estos dos volúmenes es suficiente para restaurar el stack completo en menos de diez minutos. Como los modelos de Ollama pueden volver a descargarse bajo demanda desde el registro público, solo gitea-data es realmente crítico para la continuidad del servicio y la preservación del historial Git.