AMAZON EC2 → STACKIT COMPUTE ENGINE · TUTORIAL
Amazon EC2 to STACKIT Compute Engine, hands on
This walk-through takes one web server from AWS Terraform to a STACKIT draft. You start with eight resources, run one command, and read what comes out: a stackit_server, a stackit_volume, security group rules, a public IP, and a data-copy document for the disk. The last section lists what you change before anything is applied.
The walk-through in five moves
Everything below was produced on 4 November 2026 from an estate written for this page.
1. Put the Terraform in a folder
The demo estate has a VPC, one subnet, a security group, a key pair, an m6i.large instance with a 40 GiB gp3 root disk, a 200 GiB encrypted data volume with its attachment, and an Elastic IP.
2. Run the assessment
The command is stacktoit assess ./ec2-demo. It reads the Terraform offline, evaluates seven units and writes the report folder. It does not call AWS and does not touch STACKIT.
3. Read the classes
Two units are DIRECT (the key pair and the Elastic IP) and five are RECONFIGURE (VPC, subnet, security group, instance, volume). None needs a redesign.
4. Read the draft and the data-copy document
The terraform folder holds the draft. The handover folder holds a connectivity checklist for the server and a data-copy document for the disk.
5. Answer the open inputs
Name an image, a machine type and a performance class, review the start script, decide the egress default, and run again until the report shows no value left to supply.
The instance and its data volume
Left: the EC2 instance, the EBS volume and the attachment. Right: the resource blocks of the draft with two of their comment lines, the NOT CARRIED notes for the gp3 type and the device name; the other review comments of the file are left out. Note the three variables: the image, the machine type and the performance class are yours to choose.
resource "aws_instance" "web" {
ami = "ami-0abcdef1234567890"
instance_type = "m6i.large"
subnet_id = aws_subnet.app.id
vpc_security_group_ids = [aws_security_group.web.id]
key_name = aws_key_pair.ops.key_name
root_block_device {
volume_size = 40
volume_type = "gp3"
}
user_data = <<-EOT
#!/bin/bash
systemctl enable --now nginx
EOT
tags = {
Name = "web-01"
}
}
resource "aws_ebs_volume" "data" {
availability_zone = "eu-central-1a"
size = 200
type = "gp3"
encrypted = true
tags = {
Name = "web-01-data"
}
}
resource "aws_volume_attachment" "data" {
device_name = "/dev/sdf"
volume_id = aws_ebs_volume.data.id
instance_id = aws_instance.web.id
}# NOT CARRIED (aws_instance.web): root_block_device.volume_type = gp3 — the boot volume's performance is a STACKIT performance class (boot_volume.performance_class, pinned provider): AWS volume types, IOPS and throughput do not map — not generated, choose the class.
resource "stackit_server" "web" {
project_id = var.project_id
name = "web-01"
machine_type = var.web_machine_type
availability_zone = var.availability_zone
boot_volume = {
source_type = "image"
source_id = var.web_image_id
size = 40
delete_on_termination = true
}
keypair_name = stackit_key_pair.ops.name
user_data = var.web_user_data
network_interfaces = [stackit_network_interface.web.network_interface_id]
labels = {
Name = "web-01"
}
}
# NOT CARRIED (aws_volume_attachment.data): device_name = /dev/sdf — stackit_server_volume_attach has no device name (pinned provider); find the volume in the guest by its ID, not by the device path.
resource "stackit_volume" "data" {
project_id = var.project_id
name = "web-01-data"
availability_zone = var.availability_zone
size = 200
performance_class = var.data_performance_class
labels = {
Name = "web-01-data"
}
}
resource "stackit_server_volume_attach" "data" {
project_id = var.project_id
server_id = stackit_server.web.server_id
volume_id = stackit_volume.data.volume_id
}Boot volume
The 40 GiB root size is carried. The gp3 type is not: STACKIT sets performance through a class, so the run asks you to choose one.
Image instead of AMI
An AMI id means nothing on STACKIT. The draft takes a STACKIT image id as a variable, because which image corresponds to your AMI is your choice.
Start script held back
The user_data is not copied into the draft. The run declares a sensitive variable for it and asks you to review the script first, since scripts often call AWS APIs or read the EC2 metadata paths.
Attachment without a device name
stackit_server_volume_attach has no device name, so you find the disk in the guest by its size and serial.
The security group, rule by rule
The two ingress rules arrive one for one. The source's egress rule, all traffic to anywhere, is not copied: the subnet had no internet route, so the run restricts outbound traffic to the VPC range with a separate egress group and writes two API calls that delete STACKIT's own allow-all egress rules.
resource "aws_security_group" "web" {
name = "web-sg"
description = "HTTPS from the internet, SSH from the office"
vpc_id = aws_vpc.main.id
ingress {
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = ["203.0.113.0/24"]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}resource "stackit_security_group" "web" {
project_id = var.project_id
description = "HTTPS from the internet, SSH from the office"
name = "web-sg"
}
resource "stackit_security_group_rule" "web_ingress_1" {
project_id = var.project_id
security_group_id = stackit_security_group.web.security_group_id
direction = "ingress"
ether_type = "IPv4"
ip_range = "0.0.0.0/0"
port_range = {
max = 443
min = 443
}
protocol = {
name = "tcp"
}
}
resource "stackit_security_group_rule" "web_ingress_2" {
project_id = var.project_id
security_group_id = stackit_security_group.web.security_group_id
direction = "ingress"
ether_type = "IPv4"
ip_range = "203.0.113.0/24"
port_range = {
max = 22
min = 22
}
protocol = {
name = "tcp"
}
}Closed egress is a default you confirm
The draft allows outbound traffic only to the VPC range, which is all the source subnet could reach. The decision stays open until you answer it in decisions.yaml; three automated steps rest on this default. Before the allow-all rules are deleted, they apply next to the restricted group and nothing is restricted.
Rules reference ranges, not AWS objects
A security group rule that pointed at an AWS prefix list or VPC range must be rewritten with STACKIT network prefixes.
What the run writes and where to read it
Paths are relative to the output folder of the run.
| File or folder | What it holds | Read it when |
|---|---|---|
| assessment.json | Each unit with class, confidence, target and reason | You review the mapping |
| terraform/compute.tf, network.tf, security.tf | The draft resource blocks with review comments | You prepare the plan |
| terraform/handover/server-web/ | A connectivity checklist for the server | Before the cutover |
| terraform/handover/volume-data/data-copy.md | The steps to copy the disk | You plan the data window |
| execution/api/iaas/ | Two scripts that delete the allow-all egress rules | You confirm the egress default |
| migration/technical-sequencing.md | Four waves in dependency order | You schedule the work |
| execution/manual/actions.md | 29 manual steps in 19 work packages | You staff the migration |
Copy the disk: method and checks
The data-copy document defines a file-level copy with rsync over SSH between the two servers, the method STACKIT's block-storage how-to describes, with a dedicated key and the services stopped for the final pass. Below are two of its post-transfer checks, unchanged: count files and bytes on both sides, then compare content by checksum. StackToIT runs none of them; your team does, after a rehearsal.
find <target-mount> -type f | wc -l
du -sb <target-mount>
find <source-mount> -type f | wc -l
du -sb <source-mount>rsync -aHAX --numeric-ids -nc --itemize-changes -e ssh <user>@<source-host>:<source-mount>/ <target-mount>/
sha256sum <large-file> <one-file-per-top-level-directory>First pass while the source runs
The copy itself is rsync -aHAX --numeric-ids -e ssh from the source mount to the target mount, run on the target. The first pass is not consistent because the source keeps writing; the final pass, the same command, runs inside a write freeze you decide. The run notes that the rsync line printed on STACKIT's how-to is malformed and takes the syntax from the rsync manual.
Check the size unit
The run copies 200 as a number. STACKIT documents the volume size as GB without saying whether it means GiB; if it means 10^9 bytes the volume is about 7 % smaller. The document asks you to compare the usable size on the target with lsblk -b against the used space of the source before the first pass.
Rollback
Keep the source instance, its volume and its Elastic IP until the target is verified. Data written on the target after the cutover is copied back with the same command in the other direction, or accepted as lost; that is a decision.
Change these before you apply
The draft is a proposal. These are the values and decisions that stand between it and a plan.
project_id, image, machine type
Three variables without a default. m6i.large is about 2 vCPU and 8 GiB, a 1:4 ratio. STACKIT's machine-type list gives its general family (g) the same 1:4 ratio, and the number after the family and version is the vCPU count, so start from a 2-vCPU g type and confirm it in the list.
data_performance_class
Choose the class for the data volume; the variable lists the classes of the catalogue the run read.
web_user_data
Review the start script, remove AWS-specific calls, and pass it as cloud-init.
Elastic IP to public IP
The public IP is a new address. DNS names and allow-lists that pointed at the old one move at cutover; lower the TTL ahead of time.
Private DNS and routes
The run records that private name resolution of the AWS resolver is not matched and that the subnet's routing is a decision for your network team.
Questions platform teams ask
Can I import my AMI into STACKIT?
The draft takes a STACKIT image id: a public image, or a raw or qcow2 image you upload. Do not count on exporting the AMI with VM Import/Export: AWS documents export only for a VM that was previously imported from your own environment. For most servers the practical route is a public STACKIT image plus the cloud-init script, with the data volume copied separately.
Does the encrypted flag on my EBS volume carry over?
Not as a setting. The run notes that STACKIT documents encryption at rest for block storage and that a customer-managed key is a separate request, so nothing is generated for the flag.
Does StackToIT stop the server or copy the disk?
No. It reads Terraform and writes files. Every command in the data-copy document is run by a person.
Why does the draft ask me to delete egress rules by API?
Because the source subnet reached only the VPC range and the draft keeps that. STACKIT creates allow-all egress rules by default; the two scripts remove them and only run when you set a confirmation variable.
How long does the copy take?
The run cannot know. The size of the data and the change rate are not in the Terraform, so the document tells you to time a rehearsal.
Sources
Your next move: STACKIT.
Tell us about your estate. We show you what StackToIT delivers for it: target architecture, Terraform artifacts and a structured migration plan.