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

Example inputmain.tf
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
}
STACKIT Terraform by StackToITterraform/compute.tf
# 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.

Example inputmain.tf
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"]
  }
}
STACKIT Terraform by StackToITterraform/security.tf
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 folderWhat it holdsRead it when
assessment.jsonEach unit with class, confidence, target and reasonYou review the mapping
terraform/compute.tf, network.tf, security.tfThe draft resource blocks with review commentsYou prepare the plan
terraform/handover/server-web/A connectivity checklist for the serverBefore the cutover
terraform/handover/volume-data/data-copy.mdThe steps to copy the diskYou plan the data window
execution/api/iaas/Two scripts that delete the allow-all egress rulesYou confirm the egress default
migration/technical-sequencing.mdFour waves in dependency orderYou schedule the work
execution/manual/actions.md29 manual steps in 19 work packagesYou 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.

Count on both sidesterraform/handover/volume-data/data-copy.md
find <target-mount> -type f | wc -l
du -sb <target-mount>
find <source-mount> -type f | wc -l
du -sb <source-mount>
Content on both sidesterraform/handover/volume-data/data-copy.md
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.