[{"data":1,"prerenderedAt":155},["ShallowReactive",2],{"seo-verification":3,"blog-self-hosted-ai-stack-gitea-runners-and-ollama-on-a-vps-en":6},{"google":4,"bing":5},"EycwPY2XMyTkVzas3n1ygeNJFGAH513qrMjfDljzsMQ","",{"key":7,"data":8},"blog-self-hosted-ai-stack-gitea-runners-and-ollama-on-a-vps-en",{"id":9,"slug":10,"slugs":11,"title":15,"excerpt":16,"readTime":17,"views":18,"isPinned":19,"publishedAt":20,"category":21,"categories":27,"featuredImage":29,"bgImage":30,"posterImage":31,"relatedSolution":32,"intro":35,"sections":36,"ctaTitle":97,"ctaBody":98,"ctaButton":99,"ctaUrl":100,"relatedPosts":101},335,"self-hosted-ai-stack-gitea-runners-and-ollama-on-a-vps",{"fr":12,"en":10,"ar":13,"es":14},"deployer-stack-ia-self-hosted-gitea-ollama-vps","مكدس-ذكاء-اصطناعي-ذاتي-gitea-وollama-على-خادم-vps","stack-de-ia-self-hosted-gitea-runners-y-ollama-en-un-vps","Self-hosted AI stack: Gitea, runners and Ollama on a VPS","Deploy a complete AI pipeline on your VPS: Gitea Git forge, CI runners and LLM inference with Ollama. No code or prompt leaves your server.",9,0,false,"2026-09-06T00:00:00+00:00",{"id":22,"name":23,"slug":24,"color":25,"icon":26},1,"Artificial Intelligence","intelligence-artificielle","bg-purple-500\u002F10 text-purple-400","ia",[28],{"id":22,"name":23,"slug":24,"color":25,"icon":26},null,"\u002Fblog\u002Fcovers\u002Fbg.svg","\u002Fblog\u002Fcovers\u002Fdeployer-stack-ia-self-hosted-gitea-ollama-vps-poster.svg",{"categorySlug":33,"appSlug":34},"artificial-intelligence","ollama","GitHub Actions bills compute time, cloud LLM APIs send your code to the provider's servers. For a developer handling confidential client code, both external dependencies are non-starters. This guide shows how to assemble, on a single VPS, a Gitea forge with its actions engine, a Docker-based runner and an Ollama inference server — a complete pipeline where neither a line of code nor a prompt leaves your infrastructure. This approach gathered 119 points on Hacker News on August 21, 2026 (discussion: https:\u002F\u002Fnews.ycombinator.com\u002Fitem?id=49390463), a clear signal that appetite for sovereign AI pipelines is real.",[37,41,51,54,62,87,91,94],{"type":38,"title":39,"body":40},"h2","Why assemble this stack on a VPS","GitHub Actions and cloud LLM APIs share the same flaw: they offload processing. In the first case, your source code runs on shared runners; in the second, the context of your requests is sent to a third party. For an agency or freelancer managing client code under NDA, fixing one without fixing the other is not enough.\n\nGitea has a built-in actions engine compatible with GitHub Actions since version 1.19. Ollama exposes a local REST API on the Docker internal network. An `act_runner` container reads your `.yml` workflows exactly as GitHub would — and calls Ollama instead of a cloud API. The entire stack runs with `docker compose up -d` and generates no outbound traffic to major model APIs.",{"type":42,"title":43,"items":44},"ul","Concrete benefits of this architecture",[45,46,47,48,49,50],"**Total code confidentiality**: runners execute on your VPS, cloned code never leaves your Docker internal network.","**Zero tokens sent externally**: Ollama serves inference locally; no request reaches openai.com or anthropic.com.","**Fixed, predictable cost**: no per-workflow-run billing, no per-token billing — one monthly budget line, regardless of load.","**Reusable GitHub Actions workflows**: Gitea Actions is compatible with `.github\u002Fworkflows\u002F` syntax; your pipelines migrate without rewriting.","**Freely swappable models**: Qwen2.5-Coder, DeepSeek-Coder, Llama 3.1 or Mistral — one `ollama pull` command to change model, without touching the pipeline.","**Complete audit and traceability**: runner logs, model load history and nginx journals stay on your infrastructure and belong to you.",{"type":38,"title":52,"body":53},"Hardware and software prerequisites","The limiting constraint is RAM: the model must fit entirely in memory for inference to remain responsive. A 7B model quantized in Q4_K_M format requires around 5 to 6 GB of RAM; adding Gitea (less than 100 MB at rest) and the runner, count **8 GB minimum** for a 7B model and **16 GB recommended** for a 13B model.\n\nFor CPU, two vCPUs are enough for Gitea and runners; CPU-only inference on a 7B model takes a few seconds per response, which is acceptable for automated code review. A dedicated GPU reduces this to under one second, but is not required for CI pipeline use.\n\nRequired software on the VPS: **Docker Engine** and **Docker Compose v2**, a domain name pointing to your server (for Gitea TLS certificates), and ports 3000 (Gitea) and 11434 (Ollama, internal network only) available.",{"type":42,"title":55,"items":56},"Recommended models by available RAM",[57,58,59,60,61],"**Qwen2.5-Coder:7B** (Q4_K_M format, ~5 GB RAM) — solid quality\u002Fresource ratio for code reviews in 2026; extended context understanding and style convention awareness.","**DeepSeek-Coder:6.7B** (Q4 format, ~4.5 GB RAM) — compact alternative when RAM is tight; precise on Python, JavaScript and diffs under 200 lines.","**Llama 3.1:8B** (Q4_K_M format, ~5.5 GB RAM) — multilingual generalist, useful when projects mix code and documentation in multiple languages.","**Mistral:7B** (Q4_K_M format, ~4.5 GB RAM) — short, direct responses, ideal for a diff summary rather than a detailed analysis.","**Upgrading to a 13B model** — on a VPS with 16 GB or more, `codellama:13b` or `qwen2.5-coder:14b` improve reviews on large diffs; CPU inference time goes from ~5 s to ~15 s per call.",{"type":63,"title":64,"steps":65},"steps","Deploy the Gitea + Ollama + runner stack",[66,69,72,75,78,81,84],{"title":67,"body":68},"Create the file structure","Create a project directory and a `docker-compose.yml` file that declares three services: `gitea`, `ollama` and `runner`. Place all volumes in a `data\u002F` subfolder to simplify backups.\n\n```bash\nmkdir -p ~\u002Fgitea-stack\u002Fdata\u002F{gitea,ollama,runner}\ncd ~\u002Fgitea-stack\n```",{"title":70,"body":71},"Write the docker-compose.yml","The file declares an internal `ai-net` network on which all three services communicate. Ollama is not exposed on the host: only the runner can reach it via the Docker network.\n\n```bash\ncat > docker-compose.yml \u003C\u003C 'EOF'\nversion: \"3.8\"\n\nnetworks:\n  ai-net:\n    driver: bridge\n\nvolumes:\n  gitea-data:\n  ollama-data:\n  runner-data:\n\nservices:\n  gitea:\n    image: gitea\u002Fgitea:latest\n    restart: unless-stopped\n    networks: [ai-net]\n    ports:\n      - \"3000:3000\"\n      - \"2222:22\"\n    volumes:\n      - gitea-data:\u002Fdata\n    environment:\n      - GITEA__server__DOMAIN=git.yourdomain.com\n      - GITEA__server__ROOT_URL=https:\u002F\u002Fgit.yourdomain.com\n      - GITEA__actions__ENABLED=true\n\n  ollama:\n    image: ollama\u002Follama:latest\n    restart: unless-stopped\n    networks: [ai-net]\n    volumes:\n      - ollama-data:\u002Froot\u002F.ollama\n\n  runner:\n    image: gitea\u002Fact_runner:latest\n    restart: unless-stopped\n    networks: [ai-net]\n    volumes:\n      - runner-data:\u002Fdata\n      - \u002Fvar\u002Frun\u002Fdocker.sock:\u002Fvar\u002Frun\u002Fdocker.sock\n    environment:\n      - GITEA_INSTANCE_URL=http:\u002F\u002Fgitea:3000\n      - GITEA_RUNNER_REGISTRATION_TOKEN=${RUNNER_TOKEN}\n      - GITEA_RUNNER_NAME=local-runner\n      - OLLAMA_URL=http:\u002F\u002Follama:11434\nEOF\n```",{"title":73,"body":74},"Start Gitea and retrieve the runner token","Start only Gitea first to complete the initial setup and generate the runner registration token.\n\n```bash\ndocker compose up -d gitea\n```\n\nOpen `http:\u002F\u002F\u003Cvps-ip>:3000` in your browser, complete the setup wizard, then go to **Site Administration → Actions → Runners** to create a registration token. Note this token — you will need it in the next step.",{"title":76,"body":77},"Start Ollama and download a model","Start Ollama and download your model. For a code review pipeline, `qwen2.5-coder:7b` or `deepseek-coder:6.7b` offer a good quality\u002Fresource ratio.\n\n```bash\ndocker compose up -d ollama\n# download a code model\ndocker exec gitea-stack-ollama-1 ollama pull qwen2.5-coder:7b\n# verify the API responds on the internal network\ndocker run --rm --network gitea-stack_ai-net curlimages\u002Fcurl \\\n  http:\u002F\u002Follama:11434\u002Fapi\u002Ftags\n```\n\nThe JSON response lists available models — confirmation that the Ollama API is reachable from within the Docker internal network.",{"title":79,"body":80},"Register the runner and start the full stack","Create a `.env` file with the token retrieved in step 3, then start the runner.\n\n```bash\necho \"RUNNER_TOKEN=your_token_here\" > .env\ndocker compose up -d runner\n# verify the runner registered successfully\ndocker compose logs runner | tail -20\n```\n\nCheck in **Site Administration → Actions → Runners** that your runner appears with **Active** status.",{"title":82,"body":83},"Create a workflow that calls Ollama","In a Gitea repository, create `.gitea\u002Fworkflows\u002Freview.yml`. The workflow clones the code, calls Ollama's `\u002Fapi\u002Fchat` endpoint via `curl` and posts the result as a pull request comment.\n\n```bash\ncat > .gitea\u002Fworkflows\u002Freview.yml \u003C\u003C 'EOF'\nname: AI Code Review\non:\n  pull_request:\n    types: [opened, synchronize]\n\njobs:\n  review:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions\u002Fcheckout@v4\n      - name: Code review via Ollama\n        run: |\n          DIFF=$(git diff HEAD~1 --unified=5 | head -200)\n          curl -s ${OLLAMA_URL:-http:\u002F\u002Follama:11434}\u002Fapi\u002Fchat \\\n            -H 'Content-Type: application\u002Fjson' \\\n            -d \"{\\\"model\\\": \\\"qwen2.5-coder:7b\\\", \\\"stream\\\": false,\n                 \\\"messages\\\": [{\\\"role\\\": \\\"user\\\",\n                 \\\"content\\\": \\\"Review this Git diff: $DIFF\\\"}]}\" \\\n            | jq -r '.message.content'\nEOF\n```\n\nPush this file to your Gitea forge — the runner detects it and runs the job on the internal network, without any Internet access.",{"title":85,"body":86},"Put Gitea behind an HTTPS reverse proxy","Place Gitea behind nginx or Caddy with a Let's Encrypt certificate to expose the forge on `git.yourdomain.com`. Port 3000 should no longer be directly accessible from outside.\n\n```bash\n# minimal nginx example\ncat > \u002Fetc\u002Fnginx\u002Fsites-available\u002Fgitea.conf \u003C\u003C 'EOF'\nserver {\n    listen 443 ssl;\n    server_name git.yourdomain.com;\n    ssl_certificate \u002Fetc\u002Fletsencrypt\u002Flive\u002Fgit.yourdomain.com\u002Ffullchain.pem;\n    ssl_certificate_key \u002Fetc\u002Fletsencrypt\u002Flive\u002Fgit.yourdomain.com\u002Fprivkey.pem;\n\n    location \u002F {\n        proxy_pass http:\u002F\u002F127.0.0.1:3000;\n        proxy_set_header Host $host;\n        proxy_set_header X-Real-IP $remote_addr;\n    }\n}\nEOF\nnginx -t && systemctl reload nginx\n```\n\nOllama remains exclusively on the Docker internal network and is never exposed on the Internet.",{"type":88,"title":89,"body":90},"tip","Verify no token leaves your server","To confirm that LLM calls stay on your VPS, capture outbound network traffic during a workflow run: `tcpdump -i eth0 -n 'dst port 443 and dst host openai.com'`. Zero packets captured validates the isolation. You can also inspect Ollama logs (`docker compose logs ollama`): each inference request is logged with its source address — it should always be a Docker internal network IP, never an external one.",{"type":38,"title":92,"body":93},"Cost comparison: self-hosted VPS vs cloud API","A 7B model processes around 30,000 to 50,000 tokens per minute on two vCPUs. A 150-line diff review consumes about 800 tokens (prompt + response). Over 1,000 monthly reviews — a realistic volume for a team of five developers — that's 800,000 tokens.\n\nOn the cloud side, gpt-4o-mini charges $0.15 per million input tokens and $0.60 per million output tokens (OpenAI pricing, September 2026). For 800,000 tokens: roughly $0.70 per month. Claude Haiku is in the same range. The financial argument is therefore weak at moderate volumes.\n\nThe argument that holds is **confidentiality**: client code diffs, API keys appearing in error messages, business variable names — all of this leaves your network with every external API call. On a ServOrbit VPS with 8 GB of RAM (around €15 per month), local inference adds no extra cost and no token ever leaves your server.",{"type":38,"title":95,"body":96},"Going further: observing and enriching the pipeline","Once the base stack is running, two extensions are natural. The first is adding **Langfuse** as a fifth service: it traces every LLM call (model, duration, tokens consumed, result), allowing you to measure review quality, detect model regressions and compare results on your real corpus before switching versions. The second is parallelizing runners: a second `act_runner` with a different label (for example `gpu`) can target a node with GPU access for heavy inference jobs, while lightweight jobs (lint, unit tests) continue on the base CPU runner.\n\nFor backups, the `gitea-data` and `ollama-data` volumes contain repositories and downloaded models respectively. A daily snapshot of these two volumes is enough to restore the full stack in under ten minutes. Since Ollama models can be re-downloaded on demand from the public registry, only `gitea-data` is truly critical for service continuity and preserving Git history.","Your VPS for this AI stack","A ServOrbit VPS with root access, pre-installed Docker and a dedicated IPv4 is the foundation on which Gitea, runners and Ollama run without shared resources or forced outbound traffic.","View VPS plans","\u002Fsolutions\u002Fdeveloppeurs",[102,125,140],{"id":103,"slug":104,"slugs":105,"title":109,"excerpt":110,"readTime":111,"views":18,"isPinned":19,"publishedAt":112,"category":113,"categories":119,"featuredImage":29,"bgImage":30,"posterImage":121,"relatedSolution":122},74,"host-gitea-on-your-own-vps",{"fr":106,"en":104,"ar":107,"es":108},"heberger-gitea","استضافة-gitea-على-خادم-vps-الخاص-بك","alojar-gitea-en-un-vps","Host Gitea on your own VPS in 2026","Deploy Gitea on a VPS with Docker and SSL. Full guide: installation, migration to Forgejo v16 and governance comparison.",8,"2026-04-07T00:00:00+00:00",{"id":114,"name":115,"slug":116,"color":117,"icon":118},7,"Self-hosting","self-hosting","bg-indigo-500\u002F10 text-indigo-400","cloud",[120],{"id":114,"name":115,"slug":116,"color":117,"icon":118},"\u002Fblog\u002Fcovers\u002Fheberger-gitea-poster.svg",{"categorySlug":123,"appSlug":124},"development","gitea",{"id":126,"slug":127,"slugs":128,"title":132,"excerpt":133,"readTime":17,"views":18,"isPinned":19,"publishedAt":134,"category":135,"categories":136,"featuredImage":29,"bgImage":30,"posterImage":138,"relatedSolution":139},11,"how-to-host-ollama-on-a-vps",{"fr":129,"en":127,"ar":130,"es":131},"heberger-ollama-vps","كيفية-استضافة-ollama-على-خادم-vps","como-alojar-ollama-en-un-vps","Hosting Ollama on a VPS: advanced operational guide","Advanced Ollama VPS setup: model management, nginx reverse proxy, API security, Q4_K_M\u002FQ8 quantization, CVE-2026-45672 Open WebUI and secure coupling.","2026-06-09T00:00:00+00:00",{"id":22,"name":23,"slug":24,"color":25,"icon":26},[137],{"id":22,"name":23,"slug":24,"color":25,"icon":26},"\u002Fblog\u002Fcovers\u002Fheberger-ollama-vps-poster.svg",{"categorySlug":33,"appSlug":34},{"id":141,"slug":142,"slugs":143,"title":147,"excerpt":148,"readTime":111,"views":18,"isPinned":19,"publishedAt":149,"category":150,"categories":151,"featuredImage":29,"bgImage":30,"posterImage":153,"relatedSolution":154},271,"github-copilot-ai-credits-take-back-control-with-a-vps",{"fr":144,"en":142,"ar":145,"es":146},"github-copilot-ai-credits-alternative-vps","github-copilot-ai-credits-استعد-السيطرة-مع-خادم-vps","github-copilot-ai-credits-recuperar-control-con-un-vps","GitHub Copilot AI Credits: take back control with a VPS","Since June 2026, Copilot bills in AI Credits and predictability is gone. Host Ollama or Tabby on a VPS for a fixed-cost coding assistant with no prompt leakage.","2026-08-16T00:00:00+00:00",{"id":22,"name":23,"slug":24,"color":25,"icon":26},[152],{"id":22,"name":23,"slug":24,"color":25,"icon":26},"\u002Fblog\u002Fcovers\u002Fgithub-copilot-ai-credits-alternative-vps-poster.svg",{"categorySlug":33,"appSlug":34},1789046185269]