Sign up

Simple VPS with Terraform

The scamp_vps resource of our provider makes and changes Simple VPS from a plan. It works through the same API as the Cloud Panel and scli.

Setup

The provider is on the Terraform Registry, so terraform init downloads it. Simple VPS needs version 1.5 or newer:

terraform {
  required_providers {
    scamp = {
      source  = "serverscamp/scamp"
      version = "~> 1.5"
    }
  }
}

Give it an API token from the Cloud Panel, Settings, API tokens. In CI keep it in your secrets:

export SCAMP_TOKEN=sc_your_token_here

The provider also manages Cloud VMs, disks and networks; that is on the Terraform provider page.

A first server

Save your public key and make the server with it. terraform apply returns when the server is running, usually within half a minute.

resource "scamp_ssh_key" "laptop" {
  key_name   = "laptop"
  public_key = file("~/.ssh/id_ed25519.pub")
}

resource "scamp_vps" "web" {
  name        = "web-1"
  plan        = "vps2-1"
  image       = "debian-13"
  ssh_key_ids = [scamp_ssh_key.laptop.id]
  backups     = true
}

output "web_ipv4" {
  value = scamp_vps.web.ipv4
}

scamp_vps

Set ssh_key_ids, password or both, so that you can sign in.

ArgumentWhat it is
nameRequired. 3 to 24 lowercase letters, digits and hyphens, starting with a letter. Changing it replaces the server
planRequired. Plan id such as vps2-1. Changed in place to a bigger plan; a smaller one is refused because the disk cannot shrink
imageRequired. Image id such as debian-13. Changing it replaces the server
ssh_key_idsIDs of scamp_ssh_key keys. The keys go onto the server once, when it is made; changing the list replaces the server
passwordRoot password, 8 to 128 characters. Sensitive. Changing it replaces the server
backupsDaily backups, false by default. Turning them off deletes every backup of the server
reverse_dnsReverse DNS of the IPv4 and IPv6 addresses. Without it the default name is used
AttributeWhat it is
idUUID of the server
ipv4Public IPv4 address
ipv6_prefixThe IPv6 /56 of the server
hostnameThe default name, NAME-xxxxxx.vm.scamp.pro
statusrunning, stopped, paused, unpaid or error
cpu_modelCPU model of the host
created_atWhen the server was made

Plans and images

The value for plan is the id in the first column. Prices are a month; the server is billed by the hour from your balance.

planvCPUMemoryDiskTraffic a monthPrice
vps2-0751768 MB10 GB500 GB€1.00
vps2-111 GB20 GB1 TB€1.50
vps2-222 GB40 GB2 TB€3.00
vps2-424 GB80 GB3 TB€6.00
vps2-848 GB160 GB4 TB€12.00
vps2-16816 GB320 GB5 TB€24.00
vps2-32832 GB640 GB6 TB€48.00

The value for image:

imageSystem
ubuntu-26.04Ubuntu 26.04 LTS
debian-13Debian 13
rocky-10Rocky Linux 10
almalinux-10AlmaLinux 10

Daily backups add a share of the plan price; the price list has it per plan.

Root password

A root password is optional when you give SSH keys, and needed when you do not. Keep it out of the code: pass it in a sensitive variable, so Terraform never prints it in a plan.

variable "root_password" {
  type      = string
  sensitive = true
}

resource "scamp_vps" "db" {
  name     = "db-1"
  plan     = "vps2-2"
  image    = "debian-13"
  password = var.root_password
}

Set the variable from the environment, for example from your CI secrets. Give the same value every time: a new password is a change, and a change of the password replaces the server.

export TF_VAR_root_password="$ROOT_PASSWORD"   # the same value on every run
terraform apply

Or let Terraform make one with the random provider and read it later with terraform output -raw root_password:

resource "random_password" "root" {
  length = 24
}

resource "scamp_vps" "db" {
  # ...
  password = random_password.root.result
}

output "root_password" {
  value     = random_password.root.result
  sensitive = true
}

The password is 8 to 128 characters. It ends up in the Terraform state like any value a resource gets, so keep the state somewhere only you can read: an encrypted backend, not a public repository. Changing the password replaces the server; to change it on a running server, use passwd on the server and leave the config as it is.

What a change does

plan, backups and reverse_dns change on the running server. A bigger plan restarts it, and the apply returns once it has started on the new plan; the system inside may need a few more seconds before SSH answers.

name, image, ssh_key_ids and password replace the server: the old one and its disk are deleted and a new one is made, with new addresses. The plan shows this as must be replaced before anything happens.

SSH keys go onto the server once, when it is made, as with other clouds. If editing a key should not replace the server, tell Terraform to leave the list alone:

resource "scamp_vps" "web" {
  # ...
  lifecycle {
    ignore_changes = [ssh_key_ids]
  }
}

Before it makes a server, the provider checks that every key in ssh_key_ids is in your account and stops if one is not.

Plans in code

The same lists are in two data sources, for a config that picks a plan by its resources or shows the prices: scamp_vps_plans and scamp_vps_images.

data "scamp_vps_plans" "all" {}
data "scamp_vps_images" "all" {}

output "plans" {
  value = { for p in data.scamp_vps_plans.all.items : p.id => "${p.vcpu} vCPU, ${p.ram_gb} GB, ${p.disk_gb} GB disk, EUR ${p.price_monthly}" }
}

output "images" {
  value = [for i in data.scamp_vps_images.all.items : i.id]
}

SSH after apply

The apply returns when the server is running, before the system has finished booting. To run commands on it from Terraform, give the connection a timeout; Terraform tries again until SSH answers:

resource "scamp_vps" "web" {
  # ...
  connection {
    host        = self.ipv4
    user        = "root"
    private_key = file("~/.ssh/id_ed25519")
    timeout     = "5m"
  }

  provisioner "remote-exec" {
    inline = ["apt-get update", "apt-get install -y nginx"]
  }
}

Import

A server made in the Cloud Panel or with scli comes under Terraform by its id, shown on the server's page and by scli vps ls:

terraform import scamp_vps.web 12d46f8c-d66e-4163-99aa-1245d5b06e5b

Write the config as the server is before the first apply. If it has daily backups, set backups = true: without it the apply turns them off and deletes every backup. The API never returns the password or the SSH keys, so after an import a value you set for them is only recorded and does not replace the server.