An open API service indexing awesome lists of open source software.

https://github.com/tf-scaleway-modules/terraform-scaleway-compute

A Terraform module for creating and managing Scaleway Compute infrastructure
https://github.com/tf-scaleway-modules/terraform-scaleway-compute

compute modules scaleway terraform

Last synced: 2 months ago
JSON representation

A Terraform module for creating and managing Scaleway Compute infrastructure

Awesome Lists containing this project

README

          

# Scaleway Compute Terraform Module

[![Apache 2.0][apache-shield]][apache]
[![Terraform][terraform-badge]][terraform-url]
[![Scaleway Provider][scaleway-badge]][scaleway-url]
[![Latest Release][release-badge]][release-url]

A production-ready Terraform module for creating and managing **Scaleway** Compute infrastructure.

## Features

- **Multi-Instance Groups**: Define multiple groups (backend, frontend, database) with different configurations
- **Two-Tier Security Groups**: Shared security group for all instances + per-group security groups with merged rules
- **Multiple Private Networks**: Connect instances to multiple VPC private networks simultaneously
- **SBS Block Storage**: Additional SBS volumes per instance with configurable IOPS (5k/15k)
- **External Volume Attachment**: Attach externally created volumes to instances via IDs
- **Placement Groups**: High availability or low latency configurations
- **Cloud-Init**: Full cloud-init and user data support per group
- **SSH Keys**: Automatic SSH key management
- **Backup Snapshots**: Optional snapshot creation per instance group
- **Reserved IPs**: Static public IP allocation per instance

## Usage Examples

Comprehensive examples are available in the [`examples/`](examples/) directory:

- **[Minimal](examples/minimal/)** - Simplest configuration for quick start
- **[Complete](examples/complete/)** - Full-featured production setup

### Quick Start

```hcl
module "compute" {
source = "git::https://gitlab.com/leminnov/terraform/modules/scaleway-compute.git"

organization_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
project_name = "myproject"
name = "myapp" # Used for resource naming: myapp-web-00, myapp-shared-sg, etc.

instances = {
web = {
count = 1
instance_type = "DEV1-S"
}
}

# Global inbound rules (apply to shared security group)
inbound_rules = [
{ protocol = "TCP", port = 22 }
]
}
```

### Production Setup with Multiple Instance Groups

```hcl
# Create VPC private networks
resource "scaleway_vpc_private_network" "main" {
name = "main-network"
}

resource "scaleway_vpc_private_network" "data" {
name = "data-network"
}

module "compute" {
source = "git::https://gitlab.com/leminnov/terraform/modules/scaleway-compute.git"

organization_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
project_name = "ecommerce"
name = "ecommerce-prod"
zone = "fr-par-1"

tags = ["production"]

# Default private network for all instances
private_networks = [
{ id = scaleway_vpc_private_network.main.id },
]

# Multiple instance groups with different configurations
instances = {
backend = {
count = 3
instance_type = "GP1-S"
image = "ubuntu_noble"
root_volume_size_gb = 50
tags = ["backend", "api"]

cloud_init = <<-EOF
#cloud-config
packages: [docker.io]
EOF
}

frontend = {
count = 2
instance_type = "DEV1-M"
image = "ubuntu_noble"
root_volume_size_gb = 30
tags = ["frontend", "web"]
}

database = {
count = 1
instance_type = "GP1-M"
enable_backup_snapshot = true
create_public_ip = false

# SBS volumes with configurable IOPS
additional_volumes = [
{ size_gb = 200, type = "sbs_5k" }, # 5000 IOPS
{ size_gb = 100, type = "sbs_15k" }, # 15000 IOPS
]

# Database connects to both networks
private_networks = [
{ id = scaleway_vpc_private_network.main.id },
{ id = scaleway_vpc_private_network.data.id },
]

# Per-group security rules (MERGED with global rules)
# This group gets: SSH (global) + PostgreSQL (group-specific)
inbound_rules = [
{ protocol = "TCP", port = 5432, ip_range = "10.0.0.0/8" },
]
}
}

# HA placement
create_placement_group = true
placement_group_policy_type = "max_availability"

# Global security rules (apply to shared SG + merged into per-group SGs)
inbound_rules = [
{ protocol = "TCP", port = 22, ip_range = "10.0.0.0/8" },
{ protocol = "TCP", port = 443 },
]
}
```

## Security Group Architecture

This module uses a **two-tier security group architecture**:

```
┌─────────────────────────────────────────────────────────────────┐
│ Security Group Architecture │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ SHARED SECURITY GROUP (name-shared-sg) │ │
│ │ │ │
│ │ Contains: Global inbound_rules + outbound_rules │ │
│ │ Applied to: All instances WITHOUT custom rules │ │
│ │ │ │
│ │ Examples: backend, frontend groups │ │
│ └──────────────────────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ PER-GROUP SECURITY GROUPS (name-{group}-sg) │ │
│ │ │ │
│ │ Contains: Global rules + Group-specific rules (MERGED) │ │
│ │ Created when: Group specifies custom inbound/outbound │ │
│ │ │ │
│ │ Example: database group with PostgreSQL rules │ │
│ └──────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
```

### How it works

| Instance Group | Has Custom Rules? | Security Group Used | Rules Applied |
|---------------|-------------------|---------------------|---------------|
| `backend` | No | `name-shared-sg` | Global rules only |
| `frontend` | No | `name-shared-sg` | Global rules only |
| `database` | Yes | `name-database-sg` | Global + PostgreSQL rules |
| `cache` | Yes | `name-cache-sg` | Global + Redis rules |

### Outputs

```hcl
# Get the shared security group ID
module.compute.shared_security_group_id

# Get per-group security group IDs (only groups with custom rules)
module.compute.group_security_group_ids["database"]
module.compute.group_security_group_ids["cache"]

# Get all security groups with details
module.compute.security_groups
```

## Requirements

| Name | Version |
| ---- | ------- |
| [terraform](#requirement\_terraform) | >= 1.10.7 |
| [scaleway](#requirement\_scaleway) | ~> 2.73 |

## Providers

| Name | Version |
| ---- | ------- |
| [scaleway](#provider\_scaleway) | 2.73.0 |

## Modules

No modules.

## Resources

| Name | Type |
| ---- | ---- |
| [scaleway_block_volume.this](https://registry.terraform.io/providers/scaleway/scaleway/latest/docs/resources/block_volume) | resource |
| [scaleway_iam_ssh_key.this](https://registry.terraform.io/providers/scaleway/scaleway/latest/docs/resources/iam_ssh_key) | resource |
| [scaleway_instance_ip.this](https://registry.terraform.io/providers/scaleway/scaleway/latest/docs/resources/instance_ip) | resource |
| [scaleway_instance_placement_group.this](https://registry.terraform.io/providers/scaleway/scaleway/latest/docs/resources/instance_placement_group) | resource |
| [scaleway_instance_security_group.group](https://registry.terraform.io/providers/scaleway/scaleway/latest/docs/resources/instance_security_group) | resource |
| [scaleway_instance_security_group.shared](https://registry.terraform.io/providers/scaleway/scaleway/latest/docs/resources/instance_security_group) | resource |
| [scaleway_instance_security_group_rules.group](https://registry.terraform.io/providers/scaleway/scaleway/latest/docs/resources/instance_security_group_rules) | resource |
| [scaleway_instance_security_group_rules.shared](https://registry.terraform.io/providers/scaleway/scaleway/latest/docs/resources/instance_security_group_rules) | resource |
| [scaleway_instance_server.this](https://registry.terraform.io/providers/scaleway/scaleway/latest/docs/resources/instance_server) | resource |
| [scaleway_instance_snapshot.this](https://registry.terraform.io/providers/scaleway/scaleway/latest/docs/resources/instance_snapshot) | resource |
| [scaleway_instance_volume.this](https://registry.terraform.io/providers/scaleway/scaleway/latest/docs/resources/instance_volume) | resource |
| [scaleway_account_project.project](https://registry.terraform.io/providers/scaleway/scaleway/latest/docs/data-sources/account_project) | data source |

## Inputs

| Name | Description | Type | Default | Required |
| ---- | ----------- | ---- | ------- | :------: |
| [create\_placement\_group](#input\_create\_placement\_group) | Create a shared placement group. | `bool` | `false` | no |
| [create\_security\_group](#input\_create\_security\_group) | Create a shared security group for all instances. | `bool` | `true` | no |
| [create\_ssh\_key](#input\_create\_ssh\_key) | Create and upload an SSH key. | `bool` | `false` | no |
| [inbound\_default\_policy](#input\_inbound\_default\_policy) | Default inbound policy: accept or drop. | `string` | `"drop"` | no |
| [inbound\_rules](#input\_inbound\_rules) | Inbound security group rules. |

list(object({
action = optional(string, "accept")
protocol = optional(string, "TCP")
port = optional(number)
port_range = optional(string)
ip_range = optional(string, "0.0.0.0/0")
}))
|
[
{
"ip_range": "0.0.0.0/0",
"port": 22,
"protocol": "TCP"
}
]
| no |
| [instances](#input\_instances) | Map of instance groups to create. Each group can have different count, type, image, etc. |
map(object({
count = number
instance_type = string
image = optional(string, "ubuntu_noble")
root_volume_size_gb = optional(number, 20)
root_volume_type = optional(string, "l_ssd")
state = optional(string, "started")
tags = optional(list(string), [])
cloud_init = optional(string)
user_data = optional(map(string), {})
create_public_ip = optional(bool, true)
private_networks = optional(list(object({
id = string
ip_address = optional(string)
})), [])
security_group_id = optional(string)
create_security_group = optional(bool)
inbound_default_policy = optional(string)
outbound_default_policy = optional(string)
stateful = optional(bool)
inbound_rules = optional(list(object({
action = optional(string, "accept")
protocol = optional(string, "TCP")
port = optional(number)
port_range = optional(string)
ip_range = optional(string, "0.0.0.0/0")
})))
outbound_rules = optional(list(object({
action = optional(string, "accept")
protocol = optional(string, "TCP")
port = optional(number)
port_range = optional(string)
ip_range = optional(string, "0.0.0.0/0")
})))
placement_group_id = optional(string)
enable_backup_snapshot = optional(bool, false)
additional_volumes = optional(list(object({
size_gb = number
type = optional(string, "sbs_5k")
iops = optional(number)
})), [])
# Block volumes can only be attached to ONE instance at a time, so external_volume_ids
# is only valid when count <= 1. Enforced by precondition on scaleway_instance_server.this.
external_volume_ids = optional(list(string), [])
}))
| n/a | yes |
| [name](#input\_name) | Workload name used as a prefix for resource naming and tagging. | `string` | n/a | yes |
| [organization\_id](#input\_organization\_id) | Scaleway Organization ID. | `string` | n/a | yes |
| [outbound\_default\_policy](#input\_outbound\_default\_policy) | Default outbound policy: accept or drop. | `string` | `"accept"` | no |
| [outbound\_rules](#input\_outbound\_rules) | Outbound security group rules. |
list(object({
action = optional(string, "accept")
protocol = optional(string, "TCP")
port = optional(number)
port_range = optional(string)
ip_range = optional(string, "0.0.0.0/0")
}))
| `[]` | no |
| [placement\_group\_id](#input\_placement\_group\_id) | ID of existing placement group. | `string` | `null` | no |
| [placement\_group\_policy\_mode](#input\_placement\_group\_policy\_mode) | Placement mode: optional or enforced. | `string` | `"optional"` | no |
| [placement\_group\_policy\_type](#input\_placement\_group\_policy\_type) | Placement policy: low\_latency or max\_availability. | `string` | `"max_availability"` | no |
| [private\_networks](#input\_private\_networks) | Default private networks for all instances. Each network can have an optional static IP. |
list(object({
id = string
ip_address = optional(string)
}))
| `[]` | no |
| [project\_name](#input\_project\_name) | Scaleway Project name where all resources will be created. | `string` | n/a | yes |
| [public\_ip\_type](#input\_public\_ip\_type) | Type of public IP: routed\_ipv4, routed\_ipv6, or nat. | `string` | `"routed_ipv4"` | no |
| [security\_group\_id](#input\_security\_group\_id) | ID of existing security group (when create\_security\_group is false). | `string` | `null` | no |
| [ssh\_public\_key](#input\_ssh\_public\_key) | SSH public key content. | `string` | `null` | no |
| [ssh\_public\_key\_file](#input\_ssh\_public\_key\_file) | Path to SSH public key file. | `string` | `null` | no |
| [stateful](#input\_stateful) | Enable stateful security group. | `bool` | `true` | no |
| [tags](#input\_tags) | Global tags applied to all resources. | `list(string)` | `[]` | no |
| [timeouts](#input\_timeouts) | Resource operation timeouts for create, update, and delete operations. |
object({
create = optional(string, "10m")
update = optional(string, "10m")
delete = optional(string, "10m")
})
| `{}` | no |
| [zone](#input\_zone) | Scaleway zone (e.g., fr-par-1, nl-ams-1). | `string` | `"fr-par-1"` | no |

## Outputs

| Name | Description |
| ---- | ----------- |
| [block\_volumes](#output\_block\_volumes) | Map of additional SBS block volumes keyed by volume key. |
| [group\_security\_group\_ids](#output\_group\_security\_group\_ids) | Map of instance group names to their dedicated security group IDs (only groups with custom rules). |
| [instances](#output\_instances) | Map of all instances keyed by '-' with id, name, type, state, zone, public\_ip, private\_ip. |
| [instances\_by\_group](#output\_instances\_by\_group) | Instances grouped by their instance group name. |
| [placement\_group\_id](#output\_placement\_group\_id) | ID of the shared placement group, or var.placement\_group\_id when create\_placement\_group is false. |
| [private\_ips](#output\_private\_ips) | Map of instance keys to private IP addresses. |
| [project\_id](#output\_project\_id) | Scaleway Project ID resolved from project\_name via the account data source. |
| [public\_ips](#output\_public\_ips) | Map of instance keys to public IP addresses. |
| [security\_groups](#output\_security\_groups) | All security groups (shared + per-group) keyed by name with id and full name. |
| [shared\_security\_group\_id](#output\_shared\_security\_group\_id) | ID of the shared security group, or var.security\_group\_id when create\_security\_group is false. |
| [ssh\_key\_id](#output\_ssh\_key\_id) | ID of the SSH key created by the module (null when create\_ssh\_key is false). |
| [volumes](#output\_volumes) | Map of additional local (l\_ssd) volumes keyed by volume key. |

## Security Considerations

- Default inbound policy is `drop` - only explicitly allowed traffic is permitted
- SSH access is restricted by default - consider limiting to specific IP ranges
- Enable `stateful` security groups for automatic return traffic handling
- Use private networks for inter-instance communication
- Groups with custom rules get a dedicated security group with merged rules (global + group-specific)
- Use `security_group_id` at group level to attach an external security group instead of creating one

## Resource Timeouts

Configure operation timeouts using the `timeouts` variable:

```hcl
module "compute" {
source = "..."

timeouts = {
create = "20m" # Instance creation (default: 10m)
update = "15m" # Instance updates (default: 10m)
delete = "10m" # Instance deletion (default: 10m)
}

# ... other configuration
}
```

Timeouts apply to instances, block volumes, and security groups.

## Lifecycle Protection

To protect critical resources from accidental deletion, use Terraform's `prevent_destroy` in your root module:

```hcl
# In your root module, after the module call:
resource "null_resource" "protect_database" {
# Reference the database instances to create a dependency
triggers = {
instance_ids = join(",", [
for k, v in module.compute.instances : v.id
if startswith(k, "database-")
])
}

lifecycle {
prevent_destroy = true
}
}
```

Alternatively, for direct resource protection, fork the module and add:

```hcl
# In scaleway_instance_server.this resource:
lifecycle {
prevent_destroy = true # Add this line
}
```

### Ignoring External Tag Changes

Set `ignore_tags_changes = true` to prevent Terraform from detecting tag drift caused by external systems. Note: Due to Terraform limitations, this requires modifying the module source to add `ignore_changes = [tags]` to the lifecycle block.

## License

Licensed under the Apache License, Version 2.0. See [LICENSE](LICENSE) for full details.

Copyright 2025 - This module is independently maintained and not affiliated with Scaleway.

## Disclaimer

This module is provided "as is" without warranty of any kind. Use at your own risk.

[apache]: https://opensource.org/licenses/Apache-2.0
[apache-shield]: https://img.shields.io/badge/License-Apache%202.0-blue.svg
[terraform-badge]: https://img.shields.io/badge/Terraform-%3E%3D1.10-623CE4
[terraform-url]: https://www.terraform.io
[scaleway-badge]: https://img.shields.io/badge/Scaleway%20Provider-%3E%3D2.64-4f0599
[scaleway-url]: https://registry.terraform.io/providers/scaleway/scaleway/
[release-badge]: https://img.shields.io/gitlab/v/release/leminnov/terraform/modules/scaleway-compute?include_prereleases&sort=semver
[release-url]: https://gitlab.com/leminnov/terraform/modules/scaleway-compute/-/releases