Docs / Make changes safely
The site runner
Planned design. TractorStack is in development. This page describes how it is designed to work; nothing here is available to install yet. See the roadmap for the status of each part.
Planned Committed, and built after the first stage. Everything below is the planned design.
Somewhere on each site, something has to run the commands that change infrastructure. The on-site runner for OpenTofu and Ansible is that machine: a small VM or server that runs GitLab Runner, picks up jobs from your self-hosted GitLab, and does the work in short-lived containers. Its main job beyond running commands is secrets management in the plainest sense: the credentials for your hypervisor, switches and servers live on this machine and nowhere else.
The on-site runner for OpenTofu and Ansible
The site runner is planned as a dedicated, hardened VM with GitLab Runner installed and registered to the environment’s project or group. When a merge request pipeline starts, GitLab hands the job to the runner, and the runner starts a fresh container from a pinned image that contains OpenTofu, Ansible and the collections the environment needs. When the job ends, the container is removed. Nothing from one job is left behind for the next, apart from what was written on purpose to state or artifacts.
A runner usually serves one site. A site with separate test and production environments can have one runner for each, so a test job never holds production credentials.
What it talks to
| Direction | To | Why |
|---|---|---|
| Outbound | Your GitLab server | Pick up jobs, send logs and results |
| Outbound | Proxmox VE API, or your other hypervisor’s API | OpenTofu creates and changes VMs, storage and networks |
| Outbound | Managed hosts over SSH | Ansible configures them |
| Outbound | PostgreSQL | Read and lock OpenTofu state |
| Outbound | Local registry and mirrors | Pull job images, providers and packages |
| Inbound | Nothing | GitLab Runner polls for work, so no port needs to be open towards it |
Proxmox VE is the primary target. On vSphere or another hypervisor the runner talks to that platform’s API instead, and on bare-metal sites it may only need SSH to existing machines. Either way, the firewall rules are narrow and written in the repository like everything else.
Credentials stay on site
- Hypervisor tokens, SSH keys and database passwords are stored on the runner or in an on-site secret store it can reach, never in GitLab CI variables that leave the site.
- Jobs receive only the credentials their environment needs, for the length of the job.
- The runner itself is managed by Ansible from the same repository, so a lost runner can be rebuilt from code. Its credentials are then issued again, not copied from a backup.
Even if your GitLab server sits in another building, it holds code and approvals, not the keys to the hypervisor. See secrets and credentials for how keys are rotated.
Planned job image
An illustrative .gitlab-ci.yml fragment, not from a release:
# Planned example only
plan:
image: registry.plant-a.internal/tractorstack/runner:1.0.3
tags: [plant-a-prod]
script:
- tofu -chdir=tofu init
- tofu -chdir=tofu plan -out=plan.bin
- ansible-playbook ansible/site.yml --check --diff
artifacts:
paths: [tofu/plan.bin]
expire_in: 1 day
The tags line makes sure only this site’s runner picks up the job. On offline sites, the image and everything in it come from local mirrors.
Runner sizing, the secret store and the container executor are still being chosen. The rules above (short-lived jobs, no inbound ports, credentials on site) are the parts we expect to keep.
Last updated 25 October 2026 · Questions or corrections: sales@infratractor.com