Compare commits

..

22 commits

Author SHA1 Message Date
Mauritz Uphoff
87b89f9125 ci: remove double gitleaks
All checks were successful
Default CI / Check for Open TODOs (push) Successful in 34s
Mirror to Public GitHub / Sync Repository (push) Successful in 32s
Default CI / Pre-Commit Hooks (push) Successful in 2m54s
2026-06-30 08:30:31 +02:00
9d2fe8ceb0
Merge pull request 'ci: switch to gitleaks' (#46) from ci/switch-gitleaks into main
All checks were successful
Default CI / Check for Open TODOs (push) Successful in 28s
Mirror to Public GitHub / Sync Repository (push) Successful in 45s
Default CI / Pre-Commit Hooks (push) Successful in 1m53s
Reviewed-on: #46
2026-06-29 09:27:12 +00:00
Mauritz Uphoff
1bc2b0e694 ci: switch to gitleaks
All checks were successful
Default CI / Check for Open TODOs (pull_request) Successful in 31s
Default CI / Pre-Commit Hooks (pull_request) Successful in 2m15s
2026-06-29 11:22:36 +02:00
d86a06fa51
chore(terraform pg backend): renamed example to 'terraform-pg-backend-state-locking' (#44)
Some checks failed
Default CI / Check for Open TODOs (push) Successful in 42s
Mirror to Public GitHub / Sync Repository (push) Successful in 26s
Default CI / Secret Scanner (TruffleHog) (push) Failing after 1m11s
Default CI / Pre-Commit Hooks (push) Successful in 1m59s
## Description

Renamed example to `terraform-pg-backend-state-locking`

## Checklist

- [ ] The CI pipeline passed successfully.

Co-authored-by: Tim Reibe <tim.reibe@stackit.cloud>
Reviewed-on: #44
2026-06-26 09:51:10 +00:00
8f43343c9b
Merge pull request 'fix(trufflehog): ignore pg example connection string' (#43) from fix/trufflehog-ignore into main
Some checks failed
Default CI / Check for Open TODOs (push) Successful in 28s
Default CI / Secret Scanner (TruffleHog) (push) Failing after 1m6s
Mirror to Public GitHub / Sync Repository (push) Successful in 1m29s
Default CI / Pre-Commit Hooks (push) Successful in 2m55s
Reviewed-on: #43
Reviewed-by: Tim_Reibe <tim.reibe@digits.schwarz>
2026-06-26 09:42:34 +00:00
Tim Reibe
038928da3c
updated project name in tfvars example
All checks were successful
Default CI / Check for Open TODOs (pull_request) Successful in 41s
Default CI / Secret Scanner (TruffleHog) (pull_request) Successful in 1m5s
Default CI / Pre-Commit Hooks (pull_request) Successful in 1m32s
2026-06-26 11:36:01 +02:00
Mauritz Uphoff
22ac855f60 fix(tf-state readme): add warning for psql acl
All checks were successful
Default CI / Check for Open TODOs (pull_request) Successful in 41s
Default CI / Secret Scanner (TruffleHog) (pull_request) Successful in 1m4s
Default CI / Pre-Commit Hooks (pull_request) Successful in 1m39s
2026-06-26 11:32:54 +02:00
Mauritz Uphoff
52476cff80 fix(trufflehog): ignore pg example connection string
All checks were successful
Default CI / Check for Open TODOs (pull_request) Successful in 42s
Default CI / Secret Scanner (TruffleHog) (pull_request) Successful in 1m5s
Default CI / Pre-Commit Hooks (pull_request) Successful in 1m31s
2026-06-26 11:27:37 +02:00
0b5fcaea9d
Merge pull request 'fix(gitignore): added DS_Store and backend.conf to .gitignore' (#40) from fix/gitignore into main
Some checks failed
Default CI / Check for Open TODOs (push) Successful in 30s
Mirror to Public GitHub / Sync Repository (push) Successful in 38s
Default CI / Secret Scanner (TruffleHog) (push) Failing after 1m10s
Default CI / Pre-Commit Hooks (push) Successful in 1m46s
Reviewed-on: #40
2026-06-26 09:20:58 +00:00
4890a120c5
Merge pull request 'examples(tf-pg-backend): added terraform pg backend with state lock' (#39) from examples/terraform-pg-state-locking into main
Some checks failed
Default CI / Secret Scanner (TruffleHog) (push) Has been cancelled
Default CI / Pre-Commit Hooks (push) Has been cancelled
Default CI / Check for Open TODOs (push) Has been cancelled
Mirror to Public GitHub / Sync Repository (push) Has been cancelled
Reviewed-on: #39
2026-06-26 09:20:06 +00:00
Tim Reibe
11e39b5950
added license header and .terraform.lock.hcl files
All checks were successful
Default CI / Check for Open TODOs (pull_request) Successful in 41s
Default CI / Secret Scanner (TruffleHog) (pull_request) Successful in 1m2s
Default CI / Pre-Commit Hooks (pull_request) Successful in 1m32s
2026-06-26 11:11:39 +02:00
Tim Reibe
34407d936b
added MAINTAINERS.md
Some checks failed
Default CI / Secret Scanner (TruffleHog) (pull_request) Successful in 50s
Default CI / Check for Open TODOs (pull_request) Successful in 1m3s
Default CI / Pre-Commit Hooks (pull_request) Failing after 2m50s
2026-06-26 10:50:24 +02:00
Tim Reibe
8605e2d651
Merge branch 'main' into examples/terraform-pg-state-locking
Some checks failed
Default CI / Check for Open TODOs (pull_request) Successful in 27s
Default CI / Secret Scanner (TruffleHog) (pull_request) Successful in 1m8s
Default CI / Pre-Commit Hooks (pull_request) Failing after 1m46s
2026-06-26 10:48:18 +02:00
Tim Reibe
48c04829e4
added newline
All checks were successful
Default CI / Check for Open TODOs (pull_request) Successful in 25s
Default CI / Secret Scanner (TruffleHog) (pull_request) Successful in 49s
Default CI / Pre-Commit Hooks (pull_request) Successful in 1m33s
2026-06-26 10:46:17 +02:00
6ddeb9ccbf
Merge pull request 'fix(cloud-init): remove license header for cloud-init files' (#42) from feat/ignore-license-header-for-cloud-init-files into main
All checks were successful
Default CI / Secret Scanner (TruffleHog) (push) Successful in 44s
Default CI / Check for Open TODOs (push) Successful in 43s
Mirror to Public GitHub / Sync Repository (push) Successful in 41s
Default CI / Pre-Commit Hooks (push) Successful in 2m0s
Reviewed-on: #42
Reviewed-by: Tim_Reibe <tim.reibe@digits.schwarz>
2026-06-26 08:41:37 +00:00
Tim Reibe
06bfd10337
Restore .terraform.lock.hcl files
Some checks failed
Default CI / Check for Open TODOs (pull_request) Successful in 43s
Default CI / Secret Scanner (TruffleHog) (pull_request) Successful in 1m6s
Default CI / Pre-Commit Hooks (pull_request) Failing after 1m38s
2026-06-26 10:36:11 +02:00
89f18bbba0
examples/iaas-image-upload (#34)
All checks were successful
Default CI / Check for Open TODOs (push) Successful in 36s
Mirror to Public GitHub / Sync Repository (push) Successful in 51s
Default CI / Secret Scanner (TruffleHog) (push) Successful in 55s
Default CI / Pre-Commit Hooks (push) Successful in 1m50s
## Summary

- Adds a new self-contained Terraform example under `examples/iaas-image-upload/`
- Demonstrates how to upload a custom VM image to STACKIT using the `stackit_image` resource
- Follows existing repository conventions (numbered file prefixes, license headers, section dividers, `examples/terraform.tfvars.example` subfolder)

## What's included

- `00-provider.tf` — stackitcloud/stackit >= 0.99.0
- `01-variables.tf` — all variables with descriptions, defaults, and input validation
- `02-image.tf` — `stackit_image` resource with UEFI/Secure Boot config and labels
- `03-outputs.tf` — image ID, name, scope, and checksum
- `examples/terraform.tfvars.example` — safe-to-commit placeholder values
- `README.md` — prerequisites, deployment steps, validation, cleanup
- `.gitignore` — excludes `images/`, `keys/`, and `*.tfvars`

## Notes

- Supported disk formats restricted to `qcow2`, `raw`, `iso`
- Image files are gitignored via `images/*` — users place their file locally before `terraform apply`
- `terraform validate` and `pre-commit run --all-files` both pass clean

Co-authored-by: Mauritz Uphoff <mauritz.uphoff@stackit.cloud>
Reviewed-on: #34
Reviewed-by: Mauritz_Uphoff <mauritz.uphoff@digits.schwarz>
Co-authored-by: Sven Schmidt <sven.schmidt@digits.schwarz>
Co-committed-by: Sven Schmidt <sven.schmidt@digits.schwarz>
2026-06-26 07:08:32 +00:00
Mauritz Uphoff
91efc3cd88 fix(cloud-init): remove license header for cloud-init files
All checks were successful
Default CI / Check for Open TODOs (pull_request) Successful in 26s
Default CI / Secret Scanner (TruffleHog) (pull_request) Successful in 47s
Default CI / Pre-Commit Hooks (pull_request) Successful in 2m28s
2026-06-26 08:56:36 +02:00
13b24a2867
fix(hub-and-spoke): added empty opnsense.qcow2 image file (#38)
All checks were successful
Default CI / Check for Open TODOs (push) Successful in 33s
Default CI / Secret Scanner (TruffleHog) (push) Successful in 56s
Mirror to Public GitHub / Sync Repository (push) Successful in 36s
Default CI / Pre-Commit Hooks (push) Successful in 1m24s
Co-authored-by: Tim_Reibe <tim.reibe@digits.schwarz>
Co-committed-by: Tim_Reibe <tim.reibe@digits.schwarz>
2026-06-26 06:37:56 +00:00
Tim Reibe
2f7df9bfc6
removed .terraform.lock.hcl from all examples
Some checks failed
Default CI / Check for Open TODOs (pull_request) Successful in 26s
Default CI / Secret Scanner (TruffleHog) (pull_request) Successful in 49s
Default CI / Pre-Commit Hooks (pull_request) Failing after 1m43s
2026-06-25 18:47:53 +02:00
Tim Reibe
1a6b35f7db
added DS_Store, backend.conf and .terraform.lock.hcl to .gitignore 2026-06-25 18:47:41 +02:00
Tim Reibe
796719240a
examples(tf-pg-backend): added terraform pg backend with state lock
Some checks failed
Default CI / Check for Open TODOs (pull_request) Successful in 42s
Default CI / Secret Scanner (TruffleHog) (pull_request) Successful in 1m5s
Default CI / Pre-Commit Hooks (pull_request) Failing after 1m29s
2026-06-25 18:44:47 +02:00
36 changed files with 1212 additions and 20 deletions

View file

@ -21,16 +21,6 @@ on:
branches: ["main"]
jobs:
secret-scan:
name: "Secret Scanner (TruffleHog)"
runs-on: ${{ github.server_url == 'https://github.com' && 'ubuntu-latest' || 'stackit-docker' }}
steps:
- name: Checkout code
uses: actions/checkout@v7
- name: TruffleHog Scan
uses: edplato/trufflehog-actions-scan@master
todo-check:
name: "Check for Open TODOs"
runs-on: ${{ github.server_url == 'https://github.com' && 'ubuntu-latest' || 'stackit-ubuntu-22' }}

7
.gitignore vendored
View file

@ -1,6 +1,6 @@
.### Terraform template
# Local .terraform directories
**/.terraform/*
**/.terraform/**
# .tfstate files
*.tfstate
@ -72,3 +72,8 @@ keys
### K8s
.kubeconfig
/examples/telemetry-router-hub-spoke-setup/scripts/downloads/
*.DS_Store
# ignore backend.conf files, but keep backend.conf.example
*backend.conf

View file

@ -30,11 +30,6 @@ repos:
args: [--allow-multiple-documents]
- id: check-added-large-files
- repo: https://github.com/gitleaks/gitleaks
rev: v8.18.2
hooks:
- id: gitleaks
- repo: https://github.com/antonbabenko/pre-commit-terraform
rev: v1.88.0
hooks:
@ -58,13 +53,18 @@ repos:
- id: prettier
types_or: [javascript, yaml, json, markdown]
- repo: https://github.com/gitleaks/gitleaks
rev: v8.24.2
hooks:
- id: gitleaks
- repo: local
hooks:
# Requires `addlicense` to be installed locally (go install github.com/google/addlicense@latest)
- id: addlicense
name: Add License Headers
description: Ensures all files have the Apache 2.0 license header
entry: addlicense -c "Schwarz Digits Cloud GmbH & Co. KG" -l apache
description: Ensures all files have the Apache 2.0 license header (skips files starting with #cloud-config)
entry: bash -c 'for f in "$@"; do head -n 1 "$f" | grep -q "^#cloud-config" || addlicense -c "Schwarz Digits Cloud GmbH & Co. KG" -l apache "$f"; done' --
language: system
types_or: [terraform, python, go, javascript, yaml, json, html, css]
pass_filenames: true

View file

@ -59,7 +59,7 @@ To maintain a clean and secure codebase, we enforce a strict CI pipeline on all
```
- **Terraform file naming:** All `.tf` files in examples **must** be prefixed with exactly 3 digits to enforce consistent ordering (e.g., `010-provider.tf`, `020-variables.tf`, `030-resources.tf`, `100-outputs.tf`). Files inside `modules/` directories are exempt from this rule. This check is enforced automatically by pre-commit.
- **Scan for Secrets:** Never commit credentials. We use `trufflehog` in the CI pipeline. Ensure you have no hardcoded tokens or passwords in your code.
- **Scan for Secrets:** Never commit credentials. We use `gitleaks` in the CI pipeline. Ensure you have no hardcoded tokens or passwords in your code.
### Repository structure

View file

@ -30,7 +30,7 @@ To ensure high standards and security, we follow a strict contribution process:
- **Automated Validation:** Every Pull Request must pass the CI pipeline, which includes:
- Linting and formatting checks.
- License header verification (Apache 2.0).
- Secret scanning (Trufflehog).
- Secret scanning (GitLeaks).
- **Best Effort Policy:** While we strive for quality, the content is provided "as-is." Use in production environments requires independent validation by the user.
## 4. Mirroring Process

View file

@ -0,0 +1 @@
v1.5.7

View file

@ -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",
]
}

View file

@ -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
}

View file

@ -0,0 +1,159 @@
# 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
}
# 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 <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" {
description = "Key-value labels to attach to the image resource"
type = map(string)
default = {
managed-by = "terraform"
example = "image-upload"
}
}

View file

@ -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
}
}

View file

@ -0,0 +1,43 @@
# 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
}
output "server_id" {
description = "ID of the server created from the custom image"
value = stackit_server.from_custom_image.server_id
}

View file

@ -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
}

View file

@ -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.

View file

@ -0,0 +1,272 @@
# 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.
---
## Architecture
```mermaid
sequenceDiagram
participant User
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,SRV: 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 · 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
└── Server: custom-image-server
├── Machine: g1.1
├── Zone: eu01-1
└── Boot: source_type=image · source_id=<image_id>
```
---
## 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 | 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 |
---
## Prerequisites
| Tool | Version |
| --------------- | -------------- |
| Terraform | >= 1.5.7 |
| STACKIT CLI | latest |
| STACKIT account | Project access |
### Required STACKIT permissions
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 <project-id> \
--role compute.editor \
--service-account-email <sa-email>
```
### 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 <project-id> \
--name "tf-image-upload-sa"
mkdir -p keys
stackit iam service-account key create \
--project-id <project-id> \
--service-account-email <sa-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 13 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 <project-id>
# Show details for the uploaded image
stackit image show --project-id <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
├── 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)
│ └── .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.
---
## 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 |
| ------------------ | ---------- | ---------------------------- |
| `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)

View file

@ -0,0 +1,48 @@
# 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
# ─── 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 = {
managed-by = "terraform"
example = "image-upload"
}

View file

@ -0,0 +1 @@
.gitkeep

View file

@ -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",
]
}

View file

@ -0,0 +1,43 @@
# 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.
variable "stackit_service_account_key_path" {
description = "Path to the STACKIT service account key file (JSON). Keep this file out of version control."
type = string
}
variable "stackit_organization_id" {
description = "STACKIT Organization ID (UUID). Found in the portal under Organization > Settings."
type = string
}
variable "stackit_project_name" {
description = "Display name of the hub project in STACKIT."
type = string
}
variable "stackit_org_admin" {
description = "Email address of the STACKIT user who will be set as project owner."
type = string
}
variable "stackit_region" {
description = "STACKIT region (e.g. eu01)."
type = string
}
variable "default_zone" {
description = "Availability zone within the region (e.g. eu01-1)."
type = string
}

View file

@ -0,0 +1,27 @@
# 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_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
}

View file

@ -0,0 +1,19 @@
# 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_resourcemanager_project" "this" {
parent_container_id = var.stackit_organization_id
name = var.stackit_project_name
owner_email = var.stackit_org_admin
}

View file

@ -0,0 +1,54 @@
# 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.
# Provision PostgreSQL Flex Database Instance
resource "stackit_postgresflex_instance" "this" {
project_id = stackit_resourcemanager_project.this.project_id
name = "tf-state-instance"
version = "17"
flavor = {
cpu = 2
ram = 4
}
storage = {
class = "premium-perf2-stackit"
size = 10
}
replicas = 1
backup_schedule = "00 00 * * *"
acl = [
# WARNING: Open ACL is for development only. Restrict to your specific egress IP ranges in production.
"0.0.0.0/0"
]
}
# Provision PostgreSQL Flex Database Owner
resource "stackit_postgresflex_user" "db_owner" {
project_id = stackit_resourcemanager_project.this.project_id
instance_id = stackit_postgresflex_instance.this.instance_id
username = "tf_state_user"
roles = ["login", "createdb"]
}
# Provision PostgreSQL Flex Database
resource "stackit_postgresflex_database" "this" {
project_id = stackit_resourcemanager_project.this.project_id
instance_id = stackit_postgresflex_instance.this.instance_id
owner = stackit_postgresflex_user.db_owner.username
name = "tf-states"
}

View file

@ -0,0 +1,27 @@
# 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.
locals {
pg_username = stackit_postgresflex_user.db_owner.username
pg_password = stackit_postgresflex_user.db_owner.password
pg_host = stackit_postgresflex_user.db_owner.host
pg_port = stackit_postgresflex_user.db_owner.port
pg_database = stackit_postgresflex_database.this.name
}
output "pg_connection_uri" {
description = "PostgreSQL Flex User Connection String"
value = "postgres://${local.pg_username}:${local.pg_password}@${local.pg_host}:${local.pg_port}/${local.pg_database}?sslmode=require"
sensitive = true
}

View file

@ -0,0 +1,23 @@
# Phase 0: Bootstrap
This module provisions the STACKIT PostgreSQL Flex instance, the `terraform_state` database, and the dedicated `tf_state_user`. Its state is kept locally (or in an independent CI/CD backend) to prevent dependency conflicts.
## Implementation Steps
1. Initialize Terraform with the default local backend:
```sh
terraform init
```
2. Provision the PostgreSQL Flex resources:
```sh
terraform apply
```
3. Extract the generated PostgreSQL connection string from the Terraform outputs. This URI is required to configure the remote backend in the next phase.
```sh
terraform output -raw pg_connection_uri
```

View file

@ -0,0 +1,31 @@
# ---------------------------------------------------------------------------
# terraform.tfvars.example
#
# Copy this file into a project directory (e.g. 00-bootcamp/terraform.tfvars)
# and fill in your values. Do NOT commit terraform.tfvars to version control.
#
# Alternatively, export variables as environment variables:
# export TF_VAR_stackit_organization_id="<uuid>"
# ---------------------------------------------------------------------------
# --- STACKIT Identity --------------------------------------------------------
# Your STACKIT Organization ID.
# Portal: Organization > Settings > Organization ID
stackit_organization_id = "<your-organization-id>"
## Name of the bootstrapping project
stackit_project_name = "00-bootstrap"
# Email address of the STACKIT user set as project owner.
stackit_org_admin = "<your-admin@mail.com"
# --- Service Account ---------------------------------------------------------
# Path to the STACKIT service account key file.
# Download: STACKIT portal > Service Accounts > Keys > Create key
stackit_service_account_key_path = "./keys/service-account.json"
# --- Region ------------------------------------------------------------------
stackit_region = "eu01"
default_zone = "eu01-1"

View file

@ -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",
]
}

View file

@ -0,0 +1,43 @@
# 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.
variable "stackit_service_account_key_path" {
description = "Path to the STACKIT service account key file (JSON). Keep this file out of version control."
type = string
}
variable "stackit_organization_id" {
description = "STACKIT Organization ID (UUID). Found in the portal under Organization > Settings."
type = string
}
variable "stackit_project_name" {
description = "Display name of the hub project in STACKIT."
type = string
}
variable "stackit_org_admin" {
description = "Email address of the STACKIT user who will be set as project owner."
type = string
}
variable "stackit_region" {
description = "STACKIT region (e.g. eu01)."
type = string
}
variable "default_zone" {
description = "Availability zone within the region (e.g. eu01-1)."
type = string
}

View file

@ -0,0 +1,17 @@
# 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 {
backend "pg" {}
}

View file

@ -0,0 +1,27 @@
# 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_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
}

View file

@ -0,0 +1,19 @@
# 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_resourcemanager_project" "this" {
parent_container_id = var.stackit_organization_id
name = var.stackit_project_name
owner_email = var.stackit_org_admin
}

View file

@ -0,0 +1,56 @@
# Phase 1: Example Infrastructure
This module contains the core infrastructure configuration. It uses the `pg` backend to store state and enforce state locks via the STACKIT PostgreSQL Flex database provisioned in Phase 0.
## Implementation Steps
1. Create a `backend.conf` file in this directory and define the connection string using the credentials generated by the bootstrap module:
```ini
conn_str = "postgres://tf_state_user:<PASSWORD>@<STACKIT_INSTANCE_HOST>:5432/terraform_state?sslmode=require"
```
2. Initialize Terraform and bind it to the remote PostgreSQL backend
```sh
terraform init -backend-config=backend.conf
```
3. Provision the infrastructure or run the lock validation script:
```sh
chmod +x ./scripts/validate_lock.sh
./scripts/validate_lock.sh
```
## Log Output
```sh
➜ 01-example-project ~ ./scripts/test-state-lock.sh
[INFO] Initiating background 'terraform apply' to acquire the state lock...
[INFO] Attempting concurrent 'terraform plan'...
[INFO] ------------------------------------------------------------------
│ Error: Error acquiring the state lock
│ Error message: Already locked for workspace creation: default
│ Lock Info:
│ ID: XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXXXXXX
│ Path:
│ Operation: OperationTypePlan
│ Who: XXXX
│ Version: 1.14.9
│ Created: 2026-06-25 16:21:59.636986 +0000 UTC
│ Info:
│ Terraform acquires a state lock to protect the state from being written
│ by multiple users at the same time. Please resolve the issue above and try
│ again. For most commands, you can disable locking with the "-lock=false"
│ flag, but this is not recommended.
[INFO] ------------------------------------------------------------------
[SUCCESS] Concurrent operation rejected. State locking is active and functional.
[INFO] Waiting for the background 'terraform apply' process to terminate...
[INFO] Evaluation complete. Cleaning up temporary logs...
```

View file

@ -0,0 +1,44 @@
#!/bin/bash
# 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.
set -u
echo "[INFO] Initiating background 'terraform apply' to acquire the state lock..."
# Redirecting output to avoid console clutter during the concurrent test
terraform apply -auto-approve > apply_bg.log 2>&1 &
APPLY_PID=$!
echo "[INFO] Attempting concurrent 'terraform plan'..."
echo "[INFO] ------------------------------------------------------------------"
# Disable exit-on-error to capture the expected failure code
set +e
terraform plan
PLAN_EXIT_CODE=$?
set -e
echo "[INFO] ------------------------------------------------------------------"
if [ $PLAN_EXIT_CODE -ne 0 ]; then
echo "[SUCCESS] Concurrent operation rejected. State locking is active and functional."
else
echo "[ERROR] Concurrent operation succeeded. State locking failed or is misconfigured."
fi
echo "[INFO] Waiting for the background 'terraform apply' process to terminate..."
wait $APPLY_PID
echo "[INFO] Evaluation complete. Cleaning up temporary logs..."
rm -f apply_bg.log

View file

@ -0,0 +1,31 @@
# ---------------------------------------------------------------------------
# terraform.tfvars.example
#
# Copy this file into a project directory (e.g. 00-bootcamp/terraform.tfvars)
# and fill in your values. Do NOT commit terraform.tfvars to version control.
#
# Alternatively, export variables as environment variables:
# export TF_VAR_stackit_organization_id="<uuid>"
# ---------------------------------------------------------------------------
# --- STACKIT Identity --------------------------------------------------------
# Your STACKIT Organization ID.
# Portal: Organization > Settings > Organization ID
stackit_organization_id = "<your-organization-id>"
## Name of the bootstrapping project
stackit_project_name = "01-example-project"
# Email address of the STACKIT user set as project owner.
stackit_org_admin = "<your-admin@mail.com"
# --- Service Account ---------------------------------------------------------
# Path to the STACKIT service account key file.
# Download: STACKIT portal > Service Accounts > Keys > Create key
stackit_service_account_key_path = "./keys/service-account.json"
# --- Region ------------------------------------------------------------------
stackit_region = "eu01"
default_zone = "eu01-1"

View file

@ -0,0 +1,9 @@
# Maintainers
General maintainers:
- Tim Reibe (<tim.reibe@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.

View file

@ -0,0 +1,12 @@
# STACKIT Terraform PostgreSQL Backend with State Locking
This repository demonstrates how to configure STACKIT PostgreSQL Flex as a Terraform backend to enable remote state storage and native state locking.
To resolve the circular dependency of provisioning a state backend using Terraform, the deployment is split into two isolated stages:
1. **`00-bootstrap/`**: Provisions the backend infrastructure (PostgreSQL Flex instance, database and service user).
2. **`01-example/`**: Represents the primary infrastructure, utilizing the provisioned PostgreSQL database as its remote backend.
---
**⚠️ Security Notice:** The PostgreSQL Flex instance in `00-bootstrap/` is configured with an open ACL (`0.0.0.0/0`) for development convenience. Before deploying to production, restrict the ACL to your specific egress IP ranges to prevent the database from being accessible via the public internet.