From 73518fb324015e8f295f2957cd973349cb88e961 Mon Sep 17 00:00:00 2001 From: Sven Schmidt Date: Mon, 22 Jun 2026 14:06:50 +0200 Subject: [PATCH 1/5] examples: add iaas-image-upload showcase --- examples/iaas-image-upload/.gitignore | 22 ++ examples/iaas-image-upload/.terraform-version | 1 + .../iaas-image-upload/.terraform.lock.hcl | 24 +++ examples/iaas-image-upload/00-provider.tf | 28 +++ examples/iaas-image-upload/01-variables.tf | 104 ++++++++++ examples/iaas-image-upload/02-image.tf | 30 +++ examples/iaas-image-upload/03-outputs.tf | 38 ++++ examples/iaas-image-upload/MAINTAINERS.md | 9 + examples/iaas-image-upload/README.md | 191 ++++++++++++++++++ .../examples/terraform.tfvars.example | 36 ++++ examples/iaas-image-upload/images/.gitkeep | 0 11 files changed, 483 insertions(+) create mode 100644 examples/iaas-image-upload/.gitignore create mode 100644 examples/iaas-image-upload/.terraform-version create mode 100644 examples/iaas-image-upload/.terraform.lock.hcl create mode 100644 examples/iaas-image-upload/00-provider.tf create mode 100644 examples/iaas-image-upload/01-variables.tf create mode 100644 examples/iaas-image-upload/02-image.tf create mode 100644 examples/iaas-image-upload/03-outputs.tf create mode 100644 examples/iaas-image-upload/MAINTAINERS.md create mode 100644 examples/iaas-image-upload/README.md create mode 100644 examples/iaas-image-upload/examples/terraform.tfvars.example create mode 100644 examples/iaas-image-upload/images/.gitkeep diff --git a/examples/iaas-image-upload/.gitignore b/examples/iaas-image-upload/.gitignore new file mode 100644 index 0000000..61146d6 --- /dev/null +++ b/examples/iaas-image-upload/.gitignore @@ -0,0 +1,22 @@ +**/.terraform/* +*.tfstate +*.tfstate.* +*.tfvars +*.tfvars.json +.terraform.tfstate.lock.info +crash.log +crash.*.log +override.tf +override.tf.json +*_override.tf +*_override.tf.json +.terraformrc +terraform.rc +keys/* +.DS_Store +*.bkp +.idea + +# Image files must not be committed — place them locally in images/ +images/* +!images/.gitkeep diff --git a/examples/iaas-image-upload/.terraform-version b/examples/iaas-image-upload/.terraform-version new file mode 100644 index 0000000..22708fe --- /dev/null +++ b/examples/iaas-image-upload/.terraform-version @@ -0,0 +1 @@ +v1.5.7 diff --git a/examples/iaas-image-upload/.terraform.lock.hcl b/examples/iaas-image-upload/.terraform.lock.hcl new file mode 100644 index 0000000..ac34bd3 --- /dev/null +++ b/examples/iaas-image-upload/.terraform.lock.hcl @@ -0,0 +1,24 @@ +# This file is maintained automatically by "terraform init". +# Manual edits may be lost in future updates. + +provider "registry.terraform.io/stackitcloud/stackit" { + version = "0.99.0" + constraints = ">= 0.99.0" + hashes = [ + "h1:a9z0j1z/8GmGjz+VygIhgyBbMqxx7jlXGqCvWBDD1NY=", + "zh:0dde99e7b343fa01f8eefc378171fb8621bedb20f59157d6cc8e3d46c738105f", + "zh:396c0392b9ef5ec7f8613c29a64e183545cc16dda0ceb876393fc003dba71c73", + "zh:40d86a1fb1c9ed4579583acb8ecc219edca44f9ee5221bfdcbc1bee2ce6654e7", + "zh:4ccbbecc3575737d87195ad13448d06071be9925760a2da5b7e5e8b91517f876", + "zh:506d786647c4566a82487fc3ffe0792f37a63ec8d6b54821aa3c7485e5ed6760", + "zh:848f638c500f1928f8593ae189472add1a0871c1e056d7df06871652ddee3409", + "zh:9ed739aec2c60cdfae3a33e4f349fa630fd0fd0ab50fcec5745774d42a6d6e70", + "zh:c0ac883dd73bd886e419d912c28ec29bb90a611b023cf4ae1b0534945cce1694", + "zh:df28663578694b25453b9d0a1cd7633a0f7fb1c113870cd3c133e9dc05d35946", + "zh:eaacb4a4512f41d44e46f82f042a19ab96c9d90d470890e2fd82c6cafb33bf0e", + "zh:ef9dd9b10571804f3a4dd6062405d0e473df270d75f05f897901c54d7d6c3d9d", + "zh:f40add9cd4fd4a7cda53f4a418c5f47a220b5ba5c4fc2377f60b1e16368f87d9", + "zh:f65deb30c1e3e8018a888d1aed56e895ea1e26b880f22a5772771e9836c9b5a4", + "zh:f8d14feddfd9d785d3ee6469937234a631998758ea5e8c16ecf61cdb94b07564", + ] +} diff --git a/examples/iaas-image-upload/00-provider.tf b/examples/iaas-image-upload/00-provider.tf new file mode 100644 index 0000000..4a26c40 --- /dev/null +++ b/examples/iaas-image-upload/00-provider.tf @@ -0,0 +1,28 @@ +# Copyright 2026 Schwarz Digits Cloud GmbH & Co. KG +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +terraform { + required_version = ">= 1.5.7" + required_providers { + stackit = { + source = "stackitcloud/stackit" + version = ">= 0.99.0" + } + } +} + +provider "stackit" { + default_region = var.stackit_region + service_account_key_path = var.stackit_service_account_key_path +} diff --git a/examples/iaas-image-upload/01-variables.tf b/examples/iaas-image-upload/01-variables.tf new file mode 100644 index 0000000..124ad7f --- /dev/null +++ b/examples/iaas-image-upload/01-variables.tf @@ -0,0 +1,104 @@ +# Copyright 2026 Schwarz Digits Cloud GmbH & Co. KG +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# ─── Provider ───────────────────────────────────────────────────────────────── + +variable "stackit_region" { + description = "STACKIT region, e.g. eu01" + type = string + default = "eu01" +} + +variable "stackit_service_account_key_path" { + description = "Path to the STACKIT service account key JSON file" + type = string + default = "keys/sa-key.json" +} + +# ─── Project ────────────────────────────────────────────────────────────────── + +variable "project_id" { + description = "STACKIT project ID to which the image is uploaded — find it in the Portal or via: stackit project list" + type = string +} + +# ─── Image ──────────────────────────────────────────────────────────────────── + +variable "image_name" { + description = "Name of the custom image as it will appear in STACKIT" + type = string +} + +variable "image_file_path" { + description = "Local path to the image file to upload, e.g. ./images/custom-image.qcow2 — the file must exist before running terraform apply" + type = string +} + +variable "disk_format" { + description = "Disk format of the image. Supported values: qcow2, raw, iso" + type = string + default = "qcow2" + + validation { + condition = contains(["qcow2", "raw", "iso"], var.disk_format) + error_message = "disk_format must be one of: qcow2, raw, iso." + } +} + +variable "min_disk_size" { + description = "Minimum disk size required to boot instances from this image, in GB" + type = number + default = 20 + + validation { + condition = var.min_disk_size >= 1 + error_message = "min_disk_size must be at least 1 GB." + } +} + +variable "min_ram" { + description = "Minimum RAM required to boot instances from this image, in MB" + type = number + default = 2048 + + validation { + condition = var.min_ram >= 1 + error_message = "min_ram must be at least 1 MB." + } +} + +# ─── Image Config ───────────────────────────────────────────────────────────── + +variable "uefi" { + description = "Enable UEFI boot for instances created from this image. Set to false for BIOS-only images." + type = bool + default = true +} + +variable "secure_boot" { + description = "Enable Secure Boot for instances created from this image. Requires UEFI (uefi = true)." + type = bool + default = false +} + +# ─── Labels ─────────────────────────────────────────────────────────────────── + +variable "labels" { + description = "Key-value labels to attach to the image resource" + type = map(string) + default = { + managed-by = "terraform" + example = "image-upload" + } +} diff --git a/examples/iaas-image-upload/02-image.tf b/examples/iaas-image-upload/02-image.tf new file mode 100644 index 0000000..f270f91 --- /dev/null +++ b/examples/iaas-image-upload/02-image.tf @@ -0,0 +1,30 @@ +# Copyright 2026 Schwarz Digits Cloud GmbH & Co. KG +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +resource "stackit_image" "custom_image" { + project_id = var.project_id + name = var.image_name + disk_format = var.disk_format + local_file_path = var.image_file_path + + min_disk_size = var.min_disk_size + min_ram = var.min_ram + + labels = var.labels + + config = { + uefi = var.uefi + secure_boot = var.secure_boot + } +} diff --git a/examples/iaas-image-upload/03-outputs.tf b/examples/iaas-image-upload/03-outputs.tf new file mode 100644 index 0000000..016b3f9 --- /dev/null +++ b/examples/iaas-image-upload/03-outputs.tf @@ -0,0 +1,38 @@ +# Copyright 2026 Schwarz Digits Cloud GmbH & Co. KG +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +output "image_id" { + description = "ID of the uploaded custom image — use this UUID when creating servers or volumes" + value = stackit_image.custom_image.image_id +} + +output "image_name" { + description = "Name of the uploaded custom image" + value = stackit_image.custom_image.name +} + +output "image_scope" { + description = "Scope of the image (private or public)" + value = stackit_image.custom_image.scope +} + +output "checksum_algorithm" { + description = "Algorithm used for the image checksum" + value = stackit_image.custom_image.checksum.algorithm +} + +output "checksum_digest" { + description = "Checksum digest of the uploaded image — verify this against your local file" + value = stackit_image.custom_image.checksum.digest +} diff --git a/examples/iaas-image-upload/MAINTAINERS.md b/examples/iaas-image-upload/MAINTAINERS.md new file mode 100644 index 0000000..345a653 --- /dev/null +++ b/examples/iaas-image-upload/MAINTAINERS.md @@ -0,0 +1,9 @@ +# Maintainers + +General maintainers: + +- Sven Schmidt (sven.schmidt@digits.schwarz) + +This example is actively maintained. The owner is responsible for reviewing and updating dependencies and functionalities on a monthly basis. +For questions, issues, or feature requests, please email general maintainers. +Please include the BP name and version in your request. We will track your request as an issue. diff --git a/examples/iaas-image-upload/README.md b/examples/iaas-image-upload/README.md new file mode 100644 index 0000000..8dba286 --- /dev/null +++ b/examples/iaas-image-upload/README.md @@ -0,0 +1,191 @@ +# iaas-image-upload + +Upload a custom VM image to STACKIT using Terraform. + +This example provisions a single `stackit_image` resource from a local image file. It covers the minimal configuration needed to make a custom image available in a STACKIT project, including disk format, boot requirements, and UEFI/Secure Boot settings. + +--- + +## Overview + +| Component | Description | +| ------------ | -------------------------------------------------------- | +| Image upload | Uploads a local image file to the STACKIT Image Service | +| Boot config | Configures UEFI and Secure Boot flags via `config` block | +| Labels | Attaches key-value labels for resource management | + +| In this example | Not in this example | +| ----------------------------- | -------------------------------------- | +| Upload a custom image | Creating VMs from the uploaded image | +| UEFI and Secure Boot settings | Network, storage, or compute resources | +| Image labels | Image sharing across projects | + +--- + +## Prerequisites + +| Tool | Version | +| --------------- | -------------- | +| Terraform | >= 1.5.7 | +| STACKIT CLI | latest | +| STACKIT account | Project access | + +### Required STACKIT permissions + +| Service | Role | +| --------------- | -------- | +| Compute / Image | `editor` | + +### Image file + +The image file must exist locally before running `terraform apply`. It is **not** included in this repository and must never be committed. + +Place your image file in the `images/` directory: + +``` +images/ +└── custom-image.qcow2 ← your local image file (gitignored) +``` + +Supported formats: `qcow2`, `raw`, `iso` + +The most common format for Linux-based virtual machine images is `qcow2`. + +### Service account + +Create a service account and download its key: + +```bash +stackit iam service-account create \ + --project-id \ + --name "tf-image-upload-sa" + +mkdir -p keys +stackit iam service-account key create \ + --project-id \ + --service-account-email \ + --output-format json > keys/sa-key.json +``` + +--- + +## Deployment + +### 1. Place the image file + +```bash +cp /path/to/your/image.qcow2 images/custom-image.qcow2 +``` + +### 2. Configure variables + +```bash +cp examples/terraform.tfvars.example terraform.tfvars +# Fill in: project_id, image_name, image_file_path +``` + +Find your project ID: + +```bash +stackit project list +``` + +### 3. Deploy + +```bash +terraform init +terraform plan +terraform apply +``` + +Duration: depends on image size and upload speed. A 2 GB image typically takes 1–3 minutes. + +### 4. Outputs + +```bash +terraform output +``` + +--- + +## Validation + +After a successful `terraform apply`, verify the image in STACKIT: + +```bash +# List images in your project +stackit image list --project-id + +# Show details for the uploaded image +stackit image show --project-id --image-id $(terraform output -raw image_id) +``` + +The `checksum_digest` output can be used to verify the uploaded image matches your local file: + +```bash +# qcow2 image checksum (SHA-256) +sha256sum images/custom-image.qcow2 +terraform output checksum_digest +``` + +--- + +## File Structure + +``` +iaas-image-upload/ +├── .terraform-version # Terraform version pin (v1.5.7) +├── .gitignore +├── 00-provider.tf # stackitcloud/stackit provider +├── 01-variables.tf # All variables with descriptions and defaults +├── 02-image.tf # stackit_image resource +├── 03-outputs.tf # Image ID, name, scope, checksum +├── examples/ +│ └── terraform.tfvars.example # Example variable values (safe to commit) +├── images/ # Place your image file here (gitignored) +│ └── .gitkeep +└── keys/ # SA key JSON — gitignored +``` + +--- + +## UEFI and Secure Boot + +| Variable | Default | Description | +| ------------- | ------- | -------------------------------------------------------- | +| `uefi` | `true` | Enables UEFI boot; set to `false` for legacy BIOS images | +| `secure_boot` | `false` | Enables Secure Boot; requires `uefi = true` | + +Most modern Linux distributions support UEFI. If your image was built for BIOS boot only, set `uefi = false` and `secure_boot = false`. + +Secure Boot requires a signed bootloader. Enable it only if your image explicitly supports it. + +--- + +## Security + +| File | Git status | Contains | +| ------------------ | ---------- | ---------------------------- | +| `terraform.tfvars` | gitignored | Project ID, sensitive config | +| `keys/` | gitignored | Service account JSON key | +| `images/` | gitignored | Local image files | + +Never commit image files, service account keys, or `terraform.tfvars` to the repository. + +--- + +## Cleanup + +```bash +terraform destroy +``` + +Removes the uploaded image from STACKIT. This does not affect any running instances that were created from the image. + +--- + +## References + +- [STACKIT Terraform Provider — stackit_image](https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/resources/image) +- [STACKIT Developer Documentation](https://docs.stackit.cloud) +- [STACKIT CLI](https://github.com/stackitcloud/stackit-cli) diff --git a/examples/iaas-image-upload/examples/terraform.tfvars.example b/examples/iaas-image-upload/examples/terraform.tfvars.example new file mode 100644 index 0000000..f9aabed --- /dev/null +++ b/examples/iaas-image-upload/examples/terraform.tfvars.example @@ -0,0 +1,36 @@ +# Copy this file to terraform.tfvars and fill in the required values. +# terraform.tfvars is gitignored — never commit real project IDs or credentials. + +# ─── Provider ───────────────────────────────────────────────────────────────── + +stackit_region = "eu01" +stackit_service_account_key_path = "keys/sa-key.json" + +# ─── Project ────────────────────────────────────────────────────────────────── + +# Find your project ID in the STACKIT Portal or via: stackit project list +project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" + +# ─── Image ──────────────────────────────────────────────────────────────────── + +image_name = "custom-image-v1" + +# Path to the local image file — this file must exist before running terraform apply. +# The images/ directory is gitignored; place your image file there. +image_file_path = "./images/custom-image.qcow2" + +disk_format = "qcow2" # supported: qcow2, raw, iso +min_disk_size = 20 +min_ram = 2048 + +# ─── Image Config ───────────────────────────────────────────────────────────── + +uefi = true +secure_boot = false + +# ─── Labels ─────────────────────────────────────────────────────────────────── + +labels = { + managed-by = "terraform" + example = "image-upload" +} diff --git a/examples/iaas-image-upload/images/.gitkeep b/examples/iaas-image-upload/images/.gitkeep new file mode 100644 index 0000000..e69de29 -- 2.49.1 From b6307119405a8ac7f0c4fed6ad0d0094211e1c6d Mon Sep 17 00:00:00 2001 From: Sven Schmidt Date: Mon, 22 Jun 2026 14:10:33 +0200 Subject: [PATCH 2/5] examples: enhance iaas-image-upload README with architecture diagram and details --- examples/iaas-image-upload/README.md | 36 ++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/examples/iaas-image-upload/README.md b/examples/iaas-image-upload/README.md index 8dba286..62adf24 100644 --- a/examples/iaas-image-upload/README.md +++ b/examples/iaas-image-upload/README.md @@ -6,6 +6,42 @@ This example provisions a single `stackit_image` resource from a local image fil --- +## Architecture + +```mermaid +sequenceDiagram + participant User + participant FS as Local Filesystem + participant TF as Terraform / STACKIT API + participant IMG as STACKIT Image Service + + Note over User,IMG: terraform apply + + User->>FS: read image file (local_file_path) + FS-->>TF: binary image data + + User->>TF: create image metadata (name, format, min_disk, min_ram, config) + TF-->>IMG: POST /v1/projects/{project_id}/images + IMG-->>TF: image_id · status: uploading + + TF-->>IMG: PUT upload binary image data + IMG-->>IMG: compute checksum · validate format + IMG-->>TF: status: active · checksum_digest + + TF-->>User: ✅ image_id · image_name · checksum_digest +``` + +``` +STACKIT Project +└── Image: custom-image-v1 (scope: private) + ├── Format: qcow2 + ├── Min disk: 20 GB + ├── Min RAM: 2048 MB + └── Config: uefi=true · secure_boot=false +``` + +--- + ## Overview | Component | Description | -- 2.49.1 From 342ee0ccfb3130857ea927f6b8488fe9ff2d638c Mon Sep 17 00:00:00 2001 From: Sven Schmidt Date: Tue, 23 Jun 2026 08:52:42 +0200 Subject: [PATCH 3/5] feat: add server configuration and outputs for custom image deployment --- examples/iaas-image-upload/.gitignore | 22 ------ examples/iaas-image-upload/01-variables.tf | 55 +++++++++++++ examples/iaas-image-upload/03-outputs.tf | 5 ++ examples/iaas-image-upload/04-server.tf | 51 ++++++++++++ examples/iaas-image-upload/README.md | 77 +++++++++++++++---- .../examples/terraform.tfvars.example | 12 +++ 6 files changed, 184 insertions(+), 38 deletions(-) delete mode 100644 examples/iaas-image-upload/.gitignore create mode 100644 examples/iaas-image-upload/04-server.tf diff --git a/examples/iaas-image-upload/.gitignore b/examples/iaas-image-upload/.gitignore deleted file mode 100644 index 61146d6..0000000 --- a/examples/iaas-image-upload/.gitignore +++ /dev/null @@ -1,22 +0,0 @@ -**/.terraform/* -*.tfstate -*.tfstate.* -*.tfvars -*.tfvars.json -.terraform.tfstate.lock.info -crash.log -crash.*.log -override.tf -override.tf.json -*_override.tf -*_override.tf.json -.terraformrc -terraform.rc -keys/* -.DS_Store -*.bkp -.idea - -# Image files must not be committed — place them locally in images/ -images/* -!images/.gitkeep diff --git a/examples/iaas-image-upload/01-variables.tf b/examples/iaas-image-upload/01-variables.tf index 124ad7f..3e2d6ba 100644 --- a/examples/iaas-image-upload/01-variables.tf +++ b/examples/iaas-image-upload/01-variables.tf @@ -92,6 +92,61 @@ variable "secure_boot" { default = false } +# ─── Server ─────────────────────────────────────────────────────────────────── + +variable "server_name" { + description = "Name of the server to create from the uploaded image" + type = string + default = "custom-image-server" +} + +variable "machine_type" { + description = "STACKIT machine type for the server — list available: stackit server machine-type list --project-id " + type = string + default = "g1.1" +} + +variable "availability_zone" { + description = "Availability zone for the server, e.g. eu01-1, eu01-2, eu01-3" + type = string + default = "eu01-1" +} + +variable "boot_volume_size_gb" { + description = "Boot volume size in GB — must be >= min_disk_size of the image" + type = number + default = 20 + + validation { + condition = var.boot_volume_size_gb >= 1 + error_message = "boot_volume_size_gb must be at least 1 GB." + } +} + +variable "network_cidr" { + description = "IPv4 CIDR block for the server's private network" + type = string + default = "10.10.0.0/24" + + validation { + condition = can(cidrnetmask(var.network_cidr)) + error_message = "network_cidr must be a valid CIDR, e.g. 10.10.0.0/24." + } +} + +variable "keypair_name" { + description = "Name of the SSH key pair to register in STACKIT" + type = string + default = "custom-image-key" +} + +variable "ssh_public_key" { + description = "SSH public key string (ssh-ed25519 AAAA... or ssh-rsa AAAA...) — only required when deploy_server = true" + type = string + sensitive = true + default = "" +} + # ─── Labels ─────────────────────────────────────────────────────────────────── variable "labels" { diff --git a/examples/iaas-image-upload/03-outputs.tf b/examples/iaas-image-upload/03-outputs.tf index 016b3f9..c2ad9cb 100644 --- a/examples/iaas-image-upload/03-outputs.tf +++ b/examples/iaas-image-upload/03-outputs.tf @@ -36,3 +36,8 @@ output "checksum_digest" { description = "Checksum digest of the uploaded image — verify this against your local file" value = stackit_image.custom_image.checksum.digest } + +output "server_id" { + description = "ID of the server created from the custom image" + value = stackit_server.from_custom_image.server_id +} diff --git a/examples/iaas-image-upload/04-server.tf b/examples/iaas-image-upload/04-server.tf new file mode 100644 index 0000000..a24f5b8 --- /dev/null +++ b/examples/iaas-image-upload/04-server.tf @@ -0,0 +1,51 @@ +# Copyright 2026 Schwarz Digits Cloud GmbH & Co. KG +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Example: boot a server from the uploaded custom image. +# The image_id is referenced directly from the stackit_image resource above. + +resource "stackit_network" "main" { + project_id = var.project_id + name = "${var.server_name}-network" + ipv4_prefix_length = tonumber(split("/", var.network_cidr)[1]) +} + +resource "stackit_network_interface" "server" { + project_id = var.project_id + network_id = stackit_network.main.network_id + name = "${var.server_name}-nic" +} + +resource "stackit_key_pair" "server" { + name = var.keypair_name + public_key = chomp(var.ssh_public_key) +} + +resource "stackit_server" "from_custom_image" { + project_id = var.project_id + name = var.server_name + machine_type = var.machine_type + availability_zone = var.availability_zone + keypair_name = stackit_key_pair.server.name + + boot_volume = { + source_type = "image" + source_id = stackit_image.custom_image.image_id + size = var.boot_volume_size_gb + } + + network_interfaces = [stackit_network_interface.server.network_interface_id] + + labels = var.labels +} diff --git a/examples/iaas-image-upload/README.md b/examples/iaas-image-upload/README.md index 62adf24..1fa42df 100644 --- a/examples/iaas-image-upload/README.md +++ b/examples/iaas-image-upload/README.md @@ -14,8 +14,9 @@ sequenceDiagram participant FS as Local Filesystem participant TF as Terraform / STACKIT API participant IMG as STACKIT Image Service + participant SRV as STACKIT Compute - Note over User,IMG: terraform apply + Note over User,SRV: terraform apply User->>FS: read image file (local_file_path) FS-->>TF: binary image data @@ -28,16 +29,29 @@ sequenceDiagram IMG-->>IMG: compute checksum · validate format IMG-->>TF: status: active · checksum_digest - TF-->>User: ✅ image_id · image_name · checksum_digest + TF-->>User: ✅ image_id · checksum_digest + + Note over User,SRV: 04-server.tf + + User->>TF: create Key Pair + Server (source_id = image_id) + TF-->>SRV: POST /v1/projects/{project_id}/servers + SRV-->>TF: server_id + + TF-->>User: ✅ server_id ``` ``` STACKIT Project -└── Image: custom-image-v1 (scope: private) - ├── Format: qcow2 - ├── Min disk: 20 GB - ├── Min RAM: 2048 MB - └── Config: uefi=true · secure_boot=false +├── Image: custom-image-v1 (scope: private) +│ ├── Format: qcow2 +│ ├── Min disk: 20 GB +│ ├── Min RAM: 2048 MB +│ └── Config: uefi=true · secure_boot=false +│ +└── Server: custom-image-server + ├── Machine: g1.1 + ├── Zone: eu01-1 + └── Boot: source_type=image · source_id= ``` --- @@ -50,11 +64,12 @@ STACKIT Project | Boot config | Configures UEFI and Secure Boot flags via `config` block | | Labels | Attaches key-value labels for resource management | -| In this example | Not in this example | -| ----------------------------- | -------------------------------------- | -| Upload a custom image | Creating VMs from the uploaded image | -| UEFI and Secure Boot settings | Network, storage, or compute resources | -| Image labels | Image sharing across projects | +| In this example | Not in this example | +| ----------------------------- | ----------------------------- | +| Upload a custom image | Image sharing across projects | +| UEFI and Secure Boot settings | Auto-scaling / multiple VMs | +| Boot a server from the image | Storage encryption | +| Image labels | DNS / Load Balancer | --- @@ -68,9 +83,21 @@ STACKIT Project ### Required STACKIT permissions -| Service | Role | -| --------------- | -------- | -| Compute / Image | `editor` | +The service account used by Terraform must have the following roles assigned in the target project: + +| Service | Role | Required for | +| --------------- | -------- | ----------------------------------- | +| Compute / Image | `editor` | Upload and manage custom images | +| Compute | `editor` | Create servers, networks, key pairs | + +Assign roles via the STACKIT Portal (Project → Access → Service Accounts) or the CLI: + +```bash +stackit project role assign \ + --project-id \ + --role compute.editor \ + --service-account-email +``` ### Image file @@ -175,7 +202,8 @@ iaas-image-upload/ ├── 00-provider.tf # stackitcloud/stackit provider ├── 01-variables.tf # All variables with descriptions and defaults ├── 02-image.tf # stackit_image resource -├── 03-outputs.tf # Image ID, name, scope, checksum +├── 03-outputs.tf # Image ID, name, scope, checksum, server outputs +├── 04-server.tf # Optional: server + network from the uploaded image ├── examples/ │ └── terraform.tfvars.example # Example variable values (safe to commit) ├── images/ # Place your image file here (gitignored) @@ -198,6 +226,23 @@ Secure Boot requires a signed bootloader. Enable it only if your image explicitl --- +## Deploy a server from the uploaded image + +`04-server.tf` creates a server that boots directly from the uploaded image. +The `source_id` is wired to `stackit_image.custom_image.image_id` — no manual copy-paste of the image UUID needed. + +Provide your SSH public key in `terraform.tfvars`: + +```hcl +ssh_public_key = "ssh-ed25519 AAAA... your-key-comment" +``` + +Extend `04-server.tf` with `stackit_network` and `stackit_network_interface` resources if network connectivity is required. + +The default SSH user depends on the operating system of your image (e.g. `debian`, `ubuntu`, `root`). + +--- + ## Security | File | Git status | Contains | diff --git a/examples/iaas-image-upload/examples/terraform.tfvars.example b/examples/iaas-image-upload/examples/terraform.tfvars.example index f9aabed..d6c7187 100644 --- a/examples/iaas-image-upload/examples/terraform.tfvars.example +++ b/examples/iaas-image-upload/examples/terraform.tfvars.example @@ -28,6 +28,18 @@ min_ram = 2048 uefi = true secure_boot = false +# ─── Server ─────────────────────────────────────────────────────────────────── + +server_name = "custom-image-server" +machine_type = "g1.1" +availability_zone = "eu01-1" +boot_volume_size_gb = 20 +network_cidr = "10.10.0.0/24" +keypair_name = "custom-image-key" + +# Paste your SSH public key here (never the private key) +ssh_public_key = "ssh-ed25519 AAAA... your-key-comment" + # ─── Labels ─────────────────────────────────────────────────────────────────── labels = { -- 2.49.1 From 93d90d9488427da2d2f31feb07ba106ad2c36f78 Mon Sep 17 00:00:00 2001 From: Sven Schmidt Date: Tue, 23 Jun 2026 09:49:39 +0200 Subject: [PATCH 4/5] fix: correct pattern for ignoring Terraform local directories in .gitignore --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index bea598b..c2a7c6a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,6 @@ .### Terraform template # Local .terraform directories -**/.terraform/* +**/.terraform/** # .tfstate files *.tfstate -- 2.49.1 From cfa42490897bb29224120f98a846770314b1de84 Mon Sep 17 00:00:00 2001 From: Mauritz Uphoff Date: Fri, 26 Jun 2026 09:04:01 +0200 Subject: [PATCH 5/5] fix: file names --- .../{00-provider.tf => 000-provider.tf} | 0 .../{01-variables.tf => 010-variables.tf} | 0 .../iaas-image-upload/{02-image.tf => 020-image.tf} | 0 .../{03-outputs.tf => 030-outputs.tf} | 0 .../iaas-image-upload/{04-server.tf => 040-server.tf} | 0 examples/iaas-image-upload/README.md | 10 +++++----- 6 files changed, 5 insertions(+), 5 deletions(-) rename examples/iaas-image-upload/{00-provider.tf => 000-provider.tf} (100%) rename examples/iaas-image-upload/{01-variables.tf => 010-variables.tf} (100%) rename examples/iaas-image-upload/{02-image.tf => 020-image.tf} (100%) rename examples/iaas-image-upload/{03-outputs.tf => 030-outputs.tf} (100%) rename examples/iaas-image-upload/{04-server.tf => 040-server.tf} (100%) diff --git a/examples/iaas-image-upload/00-provider.tf b/examples/iaas-image-upload/000-provider.tf similarity index 100% rename from examples/iaas-image-upload/00-provider.tf rename to examples/iaas-image-upload/000-provider.tf diff --git a/examples/iaas-image-upload/01-variables.tf b/examples/iaas-image-upload/010-variables.tf similarity index 100% rename from examples/iaas-image-upload/01-variables.tf rename to examples/iaas-image-upload/010-variables.tf diff --git a/examples/iaas-image-upload/02-image.tf b/examples/iaas-image-upload/020-image.tf similarity index 100% rename from examples/iaas-image-upload/02-image.tf rename to examples/iaas-image-upload/020-image.tf diff --git a/examples/iaas-image-upload/03-outputs.tf b/examples/iaas-image-upload/030-outputs.tf similarity index 100% rename from examples/iaas-image-upload/03-outputs.tf rename to examples/iaas-image-upload/030-outputs.tf diff --git a/examples/iaas-image-upload/04-server.tf b/examples/iaas-image-upload/040-server.tf similarity index 100% rename from examples/iaas-image-upload/04-server.tf rename to examples/iaas-image-upload/040-server.tf diff --git a/examples/iaas-image-upload/README.md b/examples/iaas-image-upload/README.md index 1fa42df..1bd0e10 100644 --- a/examples/iaas-image-upload/README.md +++ b/examples/iaas-image-upload/README.md @@ -199,11 +199,11 @@ terraform output checksum_digest iaas-image-upload/ ├── .terraform-version # Terraform version pin (v1.5.7) ├── .gitignore -├── 00-provider.tf # stackitcloud/stackit provider -├── 01-variables.tf # All variables with descriptions and defaults -├── 02-image.tf # stackit_image resource -├── 03-outputs.tf # Image ID, name, scope, checksum, server outputs -├── 04-server.tf # Optional: server + network from the uploaded image +├── 000-provider.tf # stackitcloud/stackit provider +├── 010-variables.tf # All variables with descriptions and defaults +├── 020-image.tf # stackit_image resource +├── 030-outputs.tf # Image ID, name, scope, checksum, server outputs +├── 040-server.tf # Optional: server + network from the uploaded image ├── examples/ │ └── terraform.tfvars.example # Example variable values (safe to commit) ├── images/ # Place your image file here (gitignored) -- 2.49.1