TractorStackdocs
Docs menu

Docs / Make changes safely

Delivery pipelines

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, but not built yet. Everything below describes the planned design.

TractorStack is planned to run CI/CD pipelines for infrastructure changes on a self-hosted GitLab inside your network, not on a service we host. A change starts as a merge request, the pipeline shows exactly what it would do, a named person approves it, and only then does anything touch a real machine. The same steps run for a firewall rule, a new VM on Proxmox VE or a Compose stack update.

Self-hosted GitLab and runners on site

TractorStack is built around GitLab: merge requests for proposing changes, GitLab merge request approvals for sign-off, .gitlab-ci.yml for the pipeline, and GitLab Runners installed on your own machines. Other Git servers could work, but GitLab is what the design targets and what we test against. The jobs that talk to hypervisors and servers run on the site runner, so credentials for Proxmox VE, vSphere or SSH stay on site.

On an offline or air-gapped network the pipeline still works, provided the runner can reach the local mirrors described in Offline and air-gapped.

CI/CD pipelines for infrastructure changes: the stages

  1. Lint and validate. tofu fmt -check and tofu validate, ansible-lint, docker compose config for Compose files, and a YAML check for Helm values. Cheap and fast, so mistakes fail in seconds.
  2. Plan and check. tofu plan saved to a file, and ansible-playbook --check --diff against the target hosts. The output is attached to the merge request so reviewers read the real difference, not just the code diff.
  3. Guardrail checks. The saved plan goes through guardrail checks. A blocking rule stops the pipeline here; a warning is shown to the reviewer.
  4. Approval. A person with the right role approves the merge request. Pipelines and automation tokens can start runs but cannot approve them. See change requests and sign-off.
  5. Apply. The approved plan file is applied, not a fresh plan. If the infrastructure moved since the plan was made, the apply stops and asks for a new plan.
  6. Post-apply checks. Health endpoints answer, services are up, a quick tofu plan shows no remaining difference, and the result goes into the audit trail.

A planned outline

An illustrative sketch in the shape of a .gitlab-ci.yml, not a file from a release. Job names and images will change.

# planned example: .gitlab-ci.yml outline
stages: [validate, plan, guardrails, apply, verify]

validate:
  stage: validate
  script:
    - tofu fmt -check
    - tofu validate
    - ansible-lint

plan:
  stage: plan
  script:
    - tofu plan -out=change.plan
    - tofu show -json change.plan > plan.json
  artifacts:
    paths: [change.plan, plan.json]

guardrails:
  stage: guardrails
  script:
    - conftest test plan.json

apply:
  stage: apply
  tags: [site-runner]
  rules:
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
  script:
    - tofu apply change.plan

verify:
  stage: verify
  script:
    - tofu plan -detailed-exitcode

Why the plan file matters

Reviewers approve what the plan says will happen. Applying a new plan after approval would mean applying something nobody looked at. Keeping the saved plan as the thing that gets applied closes that gap, and the plan is kept with the run record. State for these plans lives in PostgreSQL on site, as described in where state lives.

For changes that go to many sites at once, the same pipeline is planned to run in rollout waves instead of everywhere at the same moment. On Kubernetes from stage 3, application deployments follow Git as the source of truth with Argo CD.

Last updated 25 October 2026 · Questions or corrections: sales@infratractor.com