If you're managing more than 3-4 servers, you should be using infrastructure as code. The benefits compound: reproducible deployments, version-controlled infrastructure, peer-reviewed changes, and the ability to spin up identical environments for staging or testing in minutes. This post walks through getting started with the FranceVPS Terraform provider, from zero to a multi-region deployment.
Install Terraform
On macOS:
brew install terraform
On Ubuntu/Debian:
wget -O- https://apt.releases.hashicorp.com/gpg | sudo gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list
sudo apt update && sudo apt install terraform
Verify with terraform -version. Terraform 1.6 or later is required for the FranceVPS provider's current features.
Get an API token
In the FranceVPS dashboard, go to Settings → API Tokens → Create new token. Give it a descriptive name ("terraform-prod" or similar) and minimum required scopes — typically compute:read, compute:write, networking:read, networking:write. Copy the token; it's shown only once.
Store it securely. Don't commit it to git. The cleanest pattern is environment variables:
export FRANCEVPS_TOKEN="fra_..."
Or, for production, use a secrets manager (Vault, AWS Secrets Manager, 1Password CLI).
Your first Terraform config
Create a directory for your project:
mkdir my-infra && cd my-infra
Create main.tf:
terraform {
required_providers {
francevps = {
source = "francevps/francevps"
version = "~> 1.0"
}
}
}
provider "francevps" {
# Token comes from FRANCEVPS_TOKEN env var
}
resource "francevps_ssh_key" "deploy" {
name = "deploy-key"
public_key = file("~/.ssh/id_ed25519.pub")
}
resource "francevps_vps" "web" {
name = "web-01"
plan = "cloud-hp-2" # 2 vCPU, 4 GB RAM, 60 GB NVMe
image = "ubuntu-24.04"
region = "fr-par-1"
ssh_keys = [francevps_ssh_key.deploy.id]
tags = {
environment = "production"
role = "web"
}
}
output "web_ip" {
value = francevps_vps.web.ipv4_address
}
Initialize and apply
terraform init
terraform plan
terraform apply
Terraform downloads the provider, plans the changes (showing you what it'll create), and applies on confirmation. About 60 seconds later, you have a running VPS with the IP printed as output.
Beyond a single server
The real power shows up with multiple resources. Let's add a load balancer in front of three web servers:
resource "francevps_vps" "web" {
count = 3
name = "web-${count.index + 1}"
plan = "cloud-hp-2"
image = "ubuntu-24.04"
region = "fr-par-1"
ssh_keys = [francevps_ssh_key.deploy.id]
tags = {
environment = "production"
role = "web"
}
}
resource "francevps_load_balancer" "main" {
name = "web-lb"
region = "fr-par-1"
algorithm = "round_robin"
forwarding_rule {
entry_protocol = "https"
entry_port = 443
target_protocol = "http"
target_port = 8080
certificate = francevps_certificate.main.id
}
health_check {
protocol = "http"
port = 8080
path = "/health"
interval = 30
timeout = 5
}
targets = francevps_vps.web[*].id
}
resource "francevps_certificate" "main" {
name = "main-cert"
type = "lets_encrypt"
domains = ["app.example.com"]
}
One terraform apply creates: 3 VPS instances, a load balancer with round-robin, an automatic Let's Encrypt certificate for HTTPS termination, and the load balancer health checks targeting all three VPS.
Multi-region deployment
Add a Marseille mirror:
resource "francevps_vps" "web_mrs" {
count = 2
name = "web-mrs-${count.index + 1}"
plan = "cloud-hp-2"
image = "ubuntu-24.04"
region = "fr-mrs-1"
ssh_keys = [francevps_ssh_key.deploy.id]
}
Now you have 5 VPS across 2 regions. Pair this with cross-region database replication and DNS-based failover, and you have Tier 2 disaster recovery in code.
Modules for reusability
As your infrastructure grows, copy-paste becomes a liability. Modules let you define a "web tier" pattern once and instantiate it multiple times:
# modules/web-tier/main.tf
variable "name" {}
variable "region" {}
variable "count" { default = 2 }
variable "ssh_key_id" {}
resource "francevps_vps" "this" {
count = var.count
name = "${var.name}-${count.index + 1}"
plan = "cloud-hp-2"
image = "ubuntu-24.04"
region = var.region
ssh_keys = [var.ssh_key_id]
}
output "ips" {
value = francevps_vps.this[*].ipv4_address
}
Use it:
module "web_paris" {
source = "./modules/web-tier"
name = "web-par"
region = "fr-par-1"
count = 3
ssh_key_id = francevps_ssh_key.deploy.id
}
module "web_marseille" {
source = "./modules/web-tier"
name = "web-mrs"
region = "fr-mrs-1"
count = 2
ssh_key_id = francevps_ssh_key.deploy.id
}
State management
Terraform tracks the deployed state in a state file. By default it's local (terraform.tfstate), but for team use, store it remotely:
terraform {
backend "s3" {
endpoint = "https://s3.fr-par-1.fra-vps.com"
bucket = "my-infra-state"
key = "production.tfstate"
region = "fr-par-1"
}
}
S3-compatible object storage (FranceVPS has its own, or use Scaleway / OVH) with versioning enabled gives you remote state, history, and collaborative locking.
What's next
Once you have a baseline working:
- Add user_data to your VPS resources for cloud-init bootstrap (install your app, configure systemd services)
- Use the
cloudinit_configdata source to compose multi-part cloud-init configurations - Add monitoring via
francevps_alertresources - Integrate with CI — Terraform Cloud, Atlantis, or simple GitHub Actions runners
The goal is that "infrastructure changes" become git commits, reviewed via pull request, applied via CI. Same workflow as application code, with the same benefits.