[{"data":1,"prerenderedAt":146},["ShallowReactive",2],{"seo-verification":3,"blog-opentofu-manage-your-vps-fleet-with-infrastructure-as-code-en":6},{"google":4,"bing":5},"EycwPY2XMyTkVzas3n1ygeNJFGAH513qrMjfDljzsMQ","",{"id":7,"slug":8,"slugs":9,"title":12,"excerpt":13,"readTime":14,"views":15,"isPinned":16,"publishedAt":17,"category":18,"categories":23,"featuredImage":25,"bgImage":26,"posterImage":27,"relatedSolution":25,"intro":28,"sections":29,"ctaTitle":94,"ctaBody":95,"ctaButton":96,"ctaUrl":97,"relatedPosts":98},297,"opentofu-manage-your-vps-fleet-with-infrastructure-as-code",{"fr":10,"en":8,"ar":11},"opentofu-terraform-vps-automatiser-infrastructure","opentofu-إدارة-أسطول-vps-الخاص-بك-بالبنية-التحتية-كبيانات","OpenTofu: manage your VPS fleet with Infrastructure as Code","OpenTofu, the CNCF fork of Terraform, lets you describe your VPS fleet in HCL and provision it with one command. A practical guide for agencies and developers.",10,0,false,"2026-08-23T00:00:00+00:00",{"id":19,"name":20,"slug":21,"color":22,"icon":21},2,"Automation","automatisation","bg-brand-action\u002F10 text-brand-action",[24],{"id":19,"name":20,"slug":21,"color":22,"icon":21},null,"\u002Fblog\u002Fcovers\u002Fbg.svg","\u002Fblog\u002Fcovers\u002Fopentofu-terraform-vps-automatiser-infrastructure-poster.svg","You manage ten VPS, then twenty, and each new server requires the same manual checklist: order, network, Docker, reverse proxy, certificate. One step missed in the sequence, and a delivery fails. OpenTofu, the open source fork of Terraform hosted under the CNCF, reverses this logic: you describe the target state of your infrastructure in HCL, run `tofu apply`, and the tool calculates the delta between what exists and what should exist. This guide covers VPS provisioning — the creation, update and destruction of servers — as a complement to the system configuration that Ansible already handles.",[30,34,45,48,73,77,80,83,91],{"type":31,"title":32,"body":33},"h2","Why OpenTofu instead of continuing with manual SSH","Up to five or six clients, manual management holds: you remember what runs where, and the setup checklist is memorized. Beyond that, memory becomes an operational risk. A manually provisioned VPS has no readable state: without connecting, you do not know whether the firewall is configured, whether Docker has the expected version, or whether this server is still part of the rotation.\n\nOpenTofu solves a different problem than Ansible. Ansible manages the *configuration* of a server that already exists — what runs on it, which files are present, which services are started. OpenTofu manages the *lifecycle* of the server itself: creation, attribute updates, destruction. It maintains a state file (`terraform.tfstate`) that knows exactly what is provisioned and what is not. The two tools are complementary: OpenTofu provisions, Ansible configures.\n\nIn August 2023, HashiCorp changed the Terraform license from MPL-2.0 to BUSL-1.1, a non-free license that restricts certain commercial uses. The community responded by creating OpenTofu, an HCL-compatible fork for versions prior to Terraform 1.6, now hosted under the CNCF (Cloud Native Computing Foundation) and the Linux Foundation. The command is `tofu`, not `terraform`, but `.tf` files and the logic are identical.",{"type":35,"title":36,"items":37},"ul","What you gain with a declarative state",[38,39,40,41,42,43,44],"**Reliable inventory** — the `terraform.tfstate` file tells you exactly which servers exist, with which IPs and attributes, without connecting to each one.","**Reproducibility** — a new client or project gets the same VPS configured the same way, from the same `.tf` file versioned in Git.","**Clean destruction** — `tofu destroy` removes resources in the right order, without leaving orphaned servers that keep being billed.","**Readable diff** — `tofu plan` shows exactly what will be created, modified or destroyed before acting, like a `git diff` of your infrastructure.","**Ansible complementarity** — OpenTofu creates the VPS and sets metadata (SSH key, name, network); Ansible takes over to deploy Docker, Nginx and your applications.","**Versionable and auditable** — your infrastructure becomes a Git repository with commits, code reviews and a history of changes.","**Sustainable open source license** — OpenTofu is under MPL-2.0, without commercial restrictions, with community governance under the CNCF.",{"type":31,"title":46,"body":47},"Prerequisites before you start","This guide assumes you have a VPS with root access and a dedicated IPv4 to run your workloads, and a development workstation (macOS, Linux or Windows with WSL2) from which you run `tofu`. OpenTofu itself does not run on the target VPS: it executes locally and talks to the provider API or the server's Docker daemon.\n\nTo follow the examples, you need: OpenTofu installed locally (see \u003Ca href=\"https:\u002F\u002Fopentofu.org\u002Fdocs\u002Fintro\u002Finstall\u002F\">opentofu.org\u002Fdocs\u002Fintro\u002Finstall\u003C\u002Fa>), API access or SSH credentials to the target VPS, and Git to version your `.tf` files. No additional dependencies are required — OpenTofu downloads providers itself at `tofu init`.\n\nTo control Docker on a remote VPS via OpenTofu, the server's Docker daemon must listen on its Unix socket (default) or on a secured TCP port. The `kreuzwerker\u002Fdocker` provider connects to this socket via SSH or TCP.",{"type":49,"title":50,"steps":51},"steps","Setting up OpenTofu for a VPS fleet",[52,55,58,61,64,67,70],{"title":53,"body":54},"Install OpenTofu locally","Visit \u003Ca href=\"https:\u002F\u002Fopentofu.org\u002Fdocs\u002Fintro\u002Finstall\u002F\">opentofu.org\u002Fdocs\u002Fintro\u002Finstall\u003C\u002Fa> for instructions based on your OS. On macOS with Homebrew: `brew install opentofu`. On Debian\u002FUbuntu: the official OpenTofu repository provides the `opentofu` package. Verify the installation with `tofu version` — the command should return the installed version.",{"title":56,"body":57},"Structure your Infrastructure as Code project","Create a dedicated directory and four standard files.\n\n```hcl\n# main.tf — main resources\n# variables.tf — variable declarations\n# outputs.tf — values exposed after apply\n# terraform.tfvars — concrete values (gitignore if secrets)\n```\n\nThis structure is not required by OpenTofu, but it is the widely adopted convention: `main.tf` holds the resources, `variables.tf` declares types and default values, `outputs.tf` exposes what Ansible or another tool needs to read after provisioning (server IP, hostname…), and `terraform.tfvars` contains the concrete values you do not want to hard-code in `main.tf`.",{"title":59,"body":60},"Declare the provider and generate an SSH key","OpenTofu installs required providers at `tofu init`. The `hashicorp\u002Ftls` provider generates an SSH key pair locally, eliminating manual key management.\n\n```hcl\nterraform {\n  required_providers {\n    tls = {\n      source  = \"hashicorp\u002Ftls\"\n      version = \"~> 4.0\"\n    }\n  }\n}\n\nresource \"tls_private_key\" \"vps_key\" {\n  algorithm = \"ED25519\"\n}\n\noutput \"private_key_pem\" {\n  value     = tls_private_key.vps_key.private_key_pem\n  sensitive = true\n}\n```\n\nRun `tofu init` to download the provider, then `tofu apply` to generate the key. Retrieve it with `tofu output -raw private_key_pem > ~\u002F.ssh\u002Fvps_key && chmod 600 ~\u002F.ssh\u002Fvps_key`.",{"title":62,"body":63},"Provision the VPS with null and local-exec","If your VPS provider does not have an official OpenTofu provider, the `hashicorp\u002Fnull` provider with a `local-exec` allows you to run a local command (a curl API call, a shell script) and model the resource in the state.\n\n```hcl\nresource \"null_resource\" \"vps_provision\" {\n  triggers = {\n    server_name = var.server_name\n  }\n\n  provisioner \"local-exec\" {\n    command = \u003C\u003CEOT\n      curl -s -X POST https:\u002F\u002Fapi.your-provider.com\u002Fv1\u002Fservers \\\n        -H \"Authorization: Bearer ${var.api_token}\" \\\n        -d '{\"name\": \"${var.server_name}\", \"image\": \"ubuntu-22.04\"}'\n    EOT\n  }\n}\n```\n\nThis pattern suits a transitional phase. If your provider exposes a REST API, a shell script called via `local-exec` is sufficient to create the server and write its IP to a file that `outputs.tf` will then expose.",{"title":65,"body":66},"Control Docker on the VPS with the kreuzwerker provider","Once the VPS is provisioned and Docker installed (by Ansible, typically), the `kreuzwerker\u002Fdocker` provider lets you declare containers, networks and volumes in OpenTofu.\n\n```hcl\nterraform {\n  required_providers {\n    docker = {\n      source  = \"kreuzwerker\u002Fdocker\"\n      version = \"~> 3.0\"\n    }\n  }\n}\n\nprovider \"docker\" {\n  host = \"ssh:\u002F\u002Froot@${var.server_ip}:22\"\n}\n\nresource \"docker_container\" \"app\" {\n  name  = \"my-app\"\n  image = docker_image.app.image_id\n}\n\nresource \"docker_image\" \"app\" {\n  name = \"nginx:alpine\"\n}\n```\n\nThe provider connects to the VPS Docker daemon via SSH — no additional TCP port to open. The stable provider version is available on the \u003Ca href=\"https:\u002F\u002Fregistry.terraform.io\u002Fproviders\u002Fkreuzwerker\u002Fdocker\u002Flatest\u002Fdocs\">Terraform Registry\u003C\u002Fa>.",{"title":68,"body":69},"Version the state for a team","For solo development, the local state (`terraform.tfstate`) is sufficient. In a team or CI\u002FCD context, two developers running `tofu apply` simultaneously corrupt the state. The solution is a remote backend with locking.\n\nOpenTofu natively supports S3 (AWS, self-hosted MinIO) and GitLab Managed Terraform State. With a MinIO bucket on a VPS:\n\n```hcl\nterraform {\n  backend \"s3\" {\n    bucket                      = \"tofu-state\"\n    key                         = \"production\u002Fterraform.tfstate\"\n    region                      = \"eu-west-1\"\n    endpoint                    = \"https:\u002F\u002Fminio.your-domain.com\"\n    skip_credentials_validation = true\n    skip_metadata_api_check     = true\n    skip_region_validation      = true\n    force_path_style            = true\n  }\n}\n```\n\nLocking is automatic: if a `tofu apply` is running, a second one is rejected until the first completes.",{"title":71,"body":72},"Integrate OpenTofu into your CI\u002FCD pipeline","A typical Woodpecker CI or Forgejo Actions pipeline runs `tofu plan` on every pull request (for human review of the infrastructure diff) and `tofu apply` on merge to the main branch.\n\n```yaml\nsteps:\n  - name: tofu-plan\n    image: ghcr.io\u002Fopentofu\u002Fopentofu:latest\n    commands:\n      - tofu init\n      - tofu plan -out=tfplan\n\n  - name: tofu-apply\n    image: ghcr.io\u002Fopentofu\u002Fopentofu:latest\n    commands:\n      - tofu apply tfplan\n    when:\n      branch: main\n      event: push\n```\n\nThe remote state (previous step) is essential here: the CI runner does not have access to the local state on your workstation.",{"type":74,"title":75,"body":76},"tip","Separate workspaces per environment","OpenTofu offers `workspaces` to isolate multiple states in the same backend: `tofu workspace new staging` creates an isolated space, `tofu workspace select production` switches to production. This is lighter than duplicating `.tf` directories. In practice, one workspace per client or environment (staging, prod) prevents a `tofu destroy` in staging from touching production. Name your resources with `${terraform.workspace}` to keep them distinct in the state.",{"type":31,"title":78,"body":79},"Ansible and OpenTofu: the boundary between the two","The question comes up often: Ansible already does the job, why add a tool? The boundary is clear once you draw it explicitly.\n\nOpenTofu answers \"what exists?\": it creates the server, assigns an IP, sets an SSH key, records its state. If you remove it from your `.tf` file and run `tofu apply`, the server disappears — OpenTofu owns the lifecycle.\n\nAnsible answers \"what state is what exists in?\": it installs Docker, configures Nginx, drops an `.env` file, restarts a service. If the server is already there, Ansible makes it what you ask — but if it is not there, Ansible cannot create it.\n\nThe natural flow for an agency: OpenTofu provisions the VPS and exposes its IP as `output`, an Ansible playbook consumes that `output` via a dynamic inventory, configures the server and deploys applications. The article \u003Ca href=\"\u002Fblog\u002Fansible-automatiser-serveurs-vps\">Ansible: automate the configuration of your VPS servers\u003C\u002Fa> covers the configuration side in detail.",{"type":31,"title":81,"body":82},"Troubleshooting: common errors","Three situations come up regularly when adopting OpenTofu on an existing fleet.",{"type":35,"title":84,"items":85},"Common errors and their fix",[86,87,88,89,90],"**`Error acquiring the state lock`** — a crash during `apply` leaves a `.terraform.tfstate.lock.info` file on the backend. OpenTofu refuses to proceed while the lock exists. After verifying no other `apply` is running, remove the lock with `tofu force-unlock \u003CLOCK_ID>` (the ID appears in the error message). On an S3\u002FMinIO backend, the file is visible in the bucket.","**`tofu plan` shows unexpected `destroy + create`** — some resource attributes force a recreation (`force new resource`) when changed: server name, OS image type, region. If you modify one of these attributes, OpenTofu cannot do an in-place update — it destroys and recreates. Read the plan carefully before applying and use `tofu plan -target=resource.name` to narrow the scope.","**Lost or desynchronized state** — if the state file is lost and the servers still exist, `tofu state list` lists what OpenTofu believes is provisioned, and `tofu import \u003Cresource.type.name> \u003Cexternal-id>` imports an existing resource into the state without recreating it. This is the recovery tool when reality and state have diverged.","**Provider not found after `tofu init`** — verify that the provider `source` is exact (e.g. `kreuzwerker\u002Fdocker` not `docker\u002Fdocker`) and that you have internet access from the machine running `tofu init`. In an air-gapped environment, pre-download providers and use `plugin_cache_dir`.","**`Error: No valid credential sources found`** — OpenTofu cannot find credentials for the provider. Check the environment variables expected by the provider (often `TF_VAR_api_token` or a provider-specific credentials file) and that `terraform.tfvars` is being read (it must be in the same directory as `main.tf`).",{"type":31,"title":92,"body":93},"Your infrastructure becomes versioned code","OpenTofu does not replace SSH, it makes SSH exceptional. The daily flow becomes: modify a `.tf` file, run `tofu plan` to review the diff, validate, run `tofu apply`. Servers you no longer need to manage are destroyed by `tofu destroy` and disappear from billing.\n\nFor an agency managing more than a dozen clients, this is the lever that turns infrastructure management from a memory exercise into a code discipline. A ServOrbit Cloud VPS with root access, dedicated IPv4 and choice of OS is the base unit that your `.tf` files provision and destroy on demand. Your infrastructure becomes versioned code.","A VPS Cloud ready for OpenTofu","Root access, dedicated IPv4 and choice of OS: a ServOrbit Cloud VPS is the base unit that your `.tf` files provision and destroy on demand.","Start my Cloud VPS","\u002Fsolutions\u002Fdeveloppeurs",[99,114,132],{"id":100,"slug":101,"slugs":102,"title":105,"excerpt":106,"readTime":107,"views":108,"isPinned":16,"publishedAt":109,"category":110,"categories":111,"featuredImage":25,"bgImage":26,"posterImage":113,"relatedSolution":25},236,"automating-vps-server-management-with-ansible",{"fr":103,"en":101,"ar":104},"ansible-automatiser-serveurs-vps","أتمتة-إدارة-خوادم-vps-باستخدام-ansible","Automating VPS Server Management with Ansible","Learn how to automate VPS fleet management with Ansible: inventory, playbooks, roles and Vault for a reproducible, auditable infrastructure.",11,1,"2026-08-08T00:00:00+00:00",{"id":19,"name":20,"slug":21,"color":22,"icon":21},[112],{"id":19,"name":20,"slug":21,"color":22,"icon":21},"\u002Fblog\u002Fcovers\u002Fansible-automatiser-serveurs-vps-poster.svg",{"id":115,"slug":116,"slugs":117,"title":120,"excerpt":121,"readTime":122,"views":15,"isPinned":16,"publishedAt":123,"category":124,"categories":129,"featuredImage":25,"bgImage":26,"posterImage":131,"relatedSolution":25},229,"docker-compose-in-production-10-point-checklist",{"fr":118,"en":116,"ar":119},"docker-compose-production-checklist","docker-compose-في-الإنتاج-قائمة-التحقق-من-10-نقاط","Docker Compose in Production: 10-Point Checklist","10 Docker Compose settings to verify before any production deployment: restart, healthchecks, limits, secrets and logs.",14,"2026-08-06T00:00:00+00:00",{"id":125,"name":126,"slug":127,"color":128,"icon":127},3,"Deployment","deploiement","bg-success\u002F10 text-success",[130],{"id":125,"name":126,"slug":127,"color":128,"icon":127},"\u002Fblog\u002Fcovers\u002Fdocker-compose-production-checklist-poster.svg",{"id":133,"slug":134,"slugs":135,"title":138,"excerpt":139,"readTime":140,"views":108,"isPinned":16,"publishedAt":141,"category":142,"categories":143,"featuredImage":25,"bgImage":26,"posterImage":145,"relatedSolution":25},212,"woodpecker-ci-and-forgejo-cicd-pipeline-on-a-vps",{"fr":136,"en":134,"ar":137},"woodpecker-ci-pipeline-vps-forgejo","woodpecker-ci-وforgejo-خط-أنابيب-cicd-على-خادم-vps","Woodpecker CI and Forgejo: CI\u002FCD pipeline on a VPS","Deploy Woodpecker CI with Forgejo on your VPS for a self-hosted, lightweight and sovereign open source CI\u002FCD pipeline. Step-by-step Docker guide.",4,"2026-08-02T00:00:00+00:00",{"id":19,"name":20,"slug":21,"color":22,"icon":21},[144],{"id":19,"name":20,"slug":21,"color":22,"icon":21},"\u002Fblog\u002Fcovers\u002Fwoodpecker-ci-pipeline-vps-forgejo-poster.svg",1787581065225]