UpCloud Server Stack
Servers on SDN private network with storage, router, and firewall rules.
Verification
Plan-validatedPassed: module logic verified on a mocked plan — inputs, validation rules, conditional creation and outputs resolve (no real provider, no cloud).
Conformance
- Static validation (fmt · validate · tflint)
- No applicable security policies for this provider
- Plan tests (mocked: validation rules · outputs)
Provenance
- SHA-256 checksum
- Signature (pending)
Functional
- Live test pending (no cloud run yet)
Last verified 2026-06-28 · how we verify
Documentation
upcloud-server-stack
N identical UpCloud cloud servers on a dedicated SDN private network (with
router), encrypted boot + data storage, SSH-key-only login, and a
deny-by-default per-server firewall. One module call gives you a private
east-west plane your servers share, with public exposure strictly opt-in. Works
with Terraform and OpenTofu (>= 1.6), UpCloud provider >= 5.0, < 6.0.
EU/Nordic-sovereign infrastructure, MIT-licensed provider.
Status: static-validated, live-test pending. Validated with
tofu validate+tflint+checkovagainst theUpCloudLtd/upcloudprovider. Not yet applied against a live UpCloud account (no cloud sandbox yet), so it ships under live-test quarantine.
Design & secure defaults
- Private by default. Every server gets an SDN private NIC; the public IPv4
interface is opt-in (
public_interface = false). Private-only servers are reached through a bastion or load balancer. - Deny-by-default firewall. The per-server firewall (
firewall = true) carries explicit allow rules first, then a catch-all drop for all remaining inbound IPv4 and IPv6. SSH (22/tcp) opens only to the CIDRs inssh_source_ranges— empty means SSH is closed. - Encryption at rest on (
encrypt_storage = true) for the boot disk and any data disks. - SSH-key-only login.
ssh_keysis required;create_password = false, so no password logins are provisioned. - Source IP filtering on every interface (anti-spoofing).
- Dedicated router + SDN network per stack, with the DHCP default route left off so private-only servers do not silently gain an unexpected egress path.
planxorcpu+memis enforced by a precondition; a custom-sized server requires bothcpuandmem.
Usage
module "server_stack" {
source = "./upcloud-server-stack"
name = "web"
zone = "de-fra1"
server_count = 2
plan = "2xCPU-4GB"
ssh_keys = ["ssh-ed25519 AAAA... user@host"]
public_interface = true
ssh_source_ranges = ["203.0.113.0/24"] # bastion / office CIDR
inbound_rules = [
{
protocol = "tcp"
port_start = "443"
port_end = "443"
source_address_start = "0.0.0.0"
source_address_end = "255.255.255.255"
comment = "HTTPS"
},
]
data_disk_size_gb = 50
labels = { environment = "prod" }
}
Inputs
| Name | Type | Default | Description |
|---|---|---|---|
name | string | — | Base name for all resources (required) |
zone | string | — | UpCloud zone slug, e.g. de-fra1 (required) |
ssh_keys | list(string) | — | SSH public keys for the login user (required, non-empty) |
server_count | number | 1 | Number of identical servers (1–20) |
plan | string | 1xCPU-2GB | Server plan slug; set null to use cpu+mem |
cpu | number | null | vCPUs for a custom-sized server (when plan = null) |
mem | number | null | Memory in MB for a custom-sized server (when plan = null) |
template | string | Ubuntu Server 24.04 LTS (Noble Numbat) | OS template UUID or public template name |
os_disk_size_gb | number | 25 | Boot disk size in GiB (10–4096) |
encrypt_storage | bool | true | Encrypt boot + data storage at rest |
login_user | string | deploy | Username created on first boot, granted the SSH keys |
user_data | string | null | Cloud-init bootstrap (body or URL) |
metadata | bool | true | Enable the UpCloud metadata service |
create_network | bool | true | Create a dedicated SDN network + router |
private_network_uuid | string | null | Existing SDN network UUID (when create_network = false) |
private_network_cidr | string | 10.10.0.0/24 | CIDR for the created SDN network |
public_interface | bool | false | Attach a public IPv4 interface to each server |
ssh_source_ranges | list(string) | [] | CIDRs allowed to reach SSH; empty = SSH closed |
inbound_rules | list(object) | [] | Extra inbound allow rules on the public interface |
data_disk_size_gb | number | 0 | Data disk per server in GiB; 0 = disabled |
simple_backup | object | null | Managed backup schedule {plan, time} |
labels | map(string) | {} | Labels on servers, storage, and the network |
tags | list(string) | [] | Access-control tags on the servers |
Outputs
| Name | Description |
|---|---|
server_ids | Map of server name => server UUID |
server_titles | Map of server name => hostname |
public_ipv4 | Map of server name => public IPv4 (empty when private-only) |
private_ipv4 | Map of server name => SDN private IPv4 |
private_network_uuid | UUID of the SDN network (created or passed in) |
router_id | Router UUID (null when create_network = false) |
data_disk_ids | Map of server name => data-disk UUID |
firewall_rule_set_ids | Map of server name => firewall ruleset ID |
Requirements
| Requirement | Version |
|---|---|
| Terraform / OpenTofu | >= 1.6 |
UpCloudLtd/upcloud | >= 5.0, < 6.0 |
License
Commercial — LicenseRef-IaCBazaar-Commercial. See the IaC Bazaar terms.