Guía de despliegue

Stack de IA self-hosted: Gitea, runners y Ollama en un VPS

Desplegar en un VPS Cloud →

Tutorial

Stack de IA self-hosted: Gitea, runners y Ollama en un VPS

Inteligencia Artificial9 min de lectura7 pasos

GitHub Actions factura el tiempo de cómputo, las API LLM en la nube envían tu código a los servidores del proveedor. Para un desarrollador que gestiona código confidencial de clientes, ambas dependencias externas son inaceptables. Esta guía explica cómo montar, en un único VPS, una forge Gitea con su motor de acciones, un runner en contenedor Docker y un servidor de inferencia Ollama — un pipeline completo donde ni una línea de código ni un prompt sale de tu infraestructura. Este enfoque reunió 119 puntos en Hacker News el 21 de agosto de 2026 (discusión: https://news.ycombinator.com/item?id=49390463), señal de que el apetito por los pipelines de IA soberanos no decae.

Contenido· Por qué montar este stack en un VPS1/8
  1. 01Por qué montar este stack en un VPS
  2. 02Beneficios concretos de esta arquitectura
  3. 03Requisitos previos de hardware y software
  4. 04Modelos recomendados según la RAM disponible
  5. 05Desplegar el stack Gitea + Ollama + runner
  6. 06Verificar que ningún token sale de tu servidor
  7. 07Comparativa de coste: VPS self-hosted vs API cloud
  8. 08Ir más lejos: observar y enriquecer el pipeline

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 pull para 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:13b o qwen2.5-coder:14b mejoran 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

  1. Crear la estructura de archivos

    Crea un directorio de proyecto y un archivo docker-compose.yml que declare tres servicios: gitea, ollama y runner. Coloca todos los volúmenes en un subdirectorio data/ para simplificar las copias de seguridad.

    mkdir -p ~/gitea-stack/data/{gitea,ollama,runner}
    cd ~/gitea-stack
  2. Escribir el docker-compose.yml

    El archivo declara una red interna ai-net por 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
    EOF
  3. Arrancar 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 gitea

    Abre http://<ip-del-vps>:3000 en 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.

  4. Arrancar Ollama y descargar un modelo

    Arranca Ollama y descarga tu modelo. Para un pipeline de revisión de código, qwen2.5-coder:7b o deepseek-coder:6.7b ofrecen 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/tags

    La respuesta JSON lista los modelos disponibles — confirmación de que la API de Ollama es accesible desde la red interna de Docker.

  5. Registrar el runner y arrancar el stack completo

    Crea un archivo .env con 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 -20

    Verifica en Administración del sitio → Acciones → Runners que tu runner aparece con estado Activo.

  6. 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/chat de Ollama mediante curl y 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'
    EOF

    Sube este archivo a tu forge Gitea — el runner lo detecta y ejecuta el job en la red interna, sin ningún acceso a Internet.

  7. 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 nginx

    Ollama 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.

Tu VPS para este stack de IA

Un VPS ServOrbit con acceso root, Docker preinstalado e IPv4 dedicada es la base sobre la que Gitea, los runners y Ollama funcionan sin compartir recursos ni tráfico saliente impuesto.

¿Necesita ayuda?

Consulte nuestro centro de ayuda y nuestra FAQ, o contacte con nuestro equipo: llamada, WhatsApp o correo electrónico. Soporte en francés, inglés y árabe.

Escribir por WhatsAppse abre en una pestaña nueva