ARCHITECTURE · COST COMPARISON AND BUDGET
Cloud migration cost comparison and budget planning
A migration budget fails in two ways: it compares prices of things that are not alike, or it forgets everything that is not a price. The design question is which method gives you a number you can defend. This page sets out four approaches, shows what a list-price model built from your Terraform can state, using a StackToIT run that it priced with and without assumptions, and gives a rule for choosing.
Four ways to build the budget
Pick one as the spine and use the others to check it. Rows marked practice are general method, not something StackToIT computes.
| Approach | What it gives you | What it cannot give you |
|---|---|---|
| 1. List-price model from the assessed estate (the run) | A line per billable resource on both sides, with its state and the assumption behind it; a fast first view of coverage | Usage-based cost, support, licences, operations, migration labour; any saving across currencies without an exchange rate |
| 2. Usage-based model from billing exports and monitoring (practice) | Real quantities for traffic, requests and storage growth | A target design; STACKIT lines until the design is chosen |
| 3. Pilot wave, measured (practice) | Measured run cost and migration effort for one workload | A figure for workloads of another shape; the cost of waiting for the pilot |
| 4. Supplier quote for committed capacity (practice) | A price for a stated commitment | A comparison until the sizes are fixed by one of the approaches above |
Approach 1 is fast and open about its gaps. Approaches 2 and 3 fill the gaps with measurement.
What the run priced and what it left open
Assessed by StackToIT on 4 November 2026: two application servers, a data volume, a Multi-AZ PostgreSQL database and a backup bucket on AWS. Written for this page.
resource "aws_instance" "app" {
count = 2
ami = "ami-0abcdef1234567890"
instance_type = "m5.xlarge"
root_block_device {
volume_size = 80
volume_type = "gp3"
}
}
resource "aws_ebs_volume" "data" {
availability_zone = "eu-central-1a"
size = 500
type = "gp3"
}
resource "aws_db_instance" "main" {
identifier = "main"
engine = "postgres"
engine_version = "16.3"
instance_class = "db.m5.xlarge"
allocated_storage = 200
multi_az = true
username = "app"
password = "change-me-at-migration"
}
resource "aws_s3_bucket" "backups" {
bucket = "acme-app-backups"
}Why this shape
Compute, block storage, a database and an object bucket are the lines most budgets are made of, and they show all four reasons why a line stays unpriced.
Two runs: why a line has no price
The table of pricing states in financial/cost-coverage.md, from a run without assumptions and from a second run with the assumption file shown below.
15 lines in all: 6 of the source, 9 of the target.
| State | Lines | Meaning |
|---|---|---|
| AMBIGUOUS | 1 | More than one price fits and none is picked. |
| PRICED | 5 | A price record and a known quantity give an amount. |
| PRICE_SOURCE_ABSENT | 1 | No price source was available for this line. |
| QUANTITY_UNKNOWN | 1 | A price is known and the quantity is not: neither zero nor one. |
| TARGET_NOT_CHOSEN | 7 | The target design does not choose what the price needs. |15 lines in all: 6 of the source, 9 of the target.
| State | Lines | Meaning |
|---|---|---|
| AMBIGUOUS | 1 | More than one price fits and none is picked. |
| PRICED | 9 | A price record and a known quantity give an amount. |
| PRICE_SOURCE_ABSENT | 1 | No price source was available for this line. |
| TARGET_NOT_CHOSEN | 4 | The target design does not choose what the price needs. |Run 1
Of 15 lines, 5 are priced. 7 wait for a target choice such as a server flavor, 1 is ambiguous between prices, 1 has no source price, 1 has a price and no quantity.
Run 2
With the assumption file, 9 of 15 lines are priced. Of the 6 left, 4 are the server boot volume and the data volume, whose target the file does not name, 1 is the AWS server compute (operating system and tenancy not stated) and 1 is the AWS bucket storage, for which the cache holds no price list.
The assumption file
Two of the four entries of the file passed with --assumptions in run 2. The other two name a server flavor and a PostgreSQL storage class as target choices. Each entry names a subject, a variable, a value and a reason. The values are demo figures, not recommendations.
schema_version: "1.0"
assumptions:
- id: cutover
scenario: STANDARD_730H
subject: scenario
variable: parallel_run_months
value: "2"
unit: month
explanation: "Planning figure for this demo: two months of side-by-side running" - id: volume-size-stackit-objectstorage-bucket.backups
scenario: STANDARD_730H
subject: stackit_objectstorage_bucket.backups
variable: volume_size
value: "2000"
unit: GB
explanation: "Demo figure: backups hold about 2 TB on average"Only what you state is used
The template that the run writes lists every value the model waits for. A line the file does not mention stays unpriced. Note that in run 2 cost-model.json lists only the overlap and the bucket entry as used, although the two target choices priced their lines; check each priced line's reason text, which names the assumption.
Planning figure for overlap
parallel_run_months is how long both estates run at once. The run states the per-month figures of each side, multiplies them by your months, and keeps the result out of every total and comparison. In run 2 that was 736.47 USD per month for the priced source lines and 1,355.93 to 1,531.01 EUR for the priced STACKIT lines on the demo choices, each a lower bound because unpriced lines are missing.
How the model states a line
Read from the two runs. The class says how much of a line is fact.
| State or class | Meaning | Next action in the run |
|---|---|---|
| PRICED | A price record and a known quantity give an amount | None; check the assumption if the class is ASSUMPTION |
| TARGET_NOT_CHOSEN | The draft leaves the flavor, plan or storage class to a variable | State the choice in the assumption file or in the inputs file |
| AMBIGUOUS | More than one price fits and none is picked, for example the operating system of an instance | State the missing fact |
| PRICE_SOURCE_ABSENT, CACHE_MISSING | The price list of the source offer is not in the cache | Fill the price cache once, online, with stacktoit prices refresh |
| QUANTITY_UNKNOWN, USAGE_NOT_STATED | A price is known and the use of one month is not; the line is neither 0 nor 1 | State the use, for example gigabytes held in a bucket |
| KNOWN, ESTIMATE, ASSUMPTION, OUT_OF_MODEL | Fact from the source, a stated multiplier such as 730 hours, a quantity or class you assumed, or not computed | Treat OUT_OF_MODEL as unknown, never as zero |
In run 2 the estate-level comparison was PARTIAL and stated no saving, because source prices were in USD, STACKIT prices in EUR, and no exchange rate was given.
What belongs on the budget and is not in the model
The model computes no usage, traffic, support, licence, operations or migration labour cost. Put these on the budget yourself.
Data transfer and requests
The model prices usage only where the file states one month of it. Your source cloud decides what it charges to read and transfer objects out; ask for the charge of the volumes you move.
Version history
Amazon S3 charges normal rates for every version of an object, each a whole object. Copying history multiplies the volume to read and to store, so decide whether you move it.
Redundancy settings
Azure redundancy options trade lower cost against higher availability. STACKIT Object Storage has no managed cross-region replication according to the run, so a second copy is a sync job with its own storage and transfer.
Parallel running
Both estates cost money while you cut over. The run states it apart as a one-off item, per month of overlap and for your assumed months, in no total.
People and time
Migration labour, retraining and the pilot are yours to estimate with approach 3.
Savings timing (practice)
A saving counts from the day the source is retired, not from the day the target runs. Plan the retirement as its own milestone.
How to decide
A rule you can apply in a steering meeting.
Start with approach 1 on every estate
It is cheap and shows how much of the estate can be priced at all. A coverage of 5 of 15 lines is a finding, not a failure.
Close target choices with the architects
TARGET_NOT_CHOSEN lines are design questions. Answer them in the inputs file, then reprice.
Add measured quantities for the top cost lines
Use approach 2 for storage and traffic where the line has a price and no quantity.
Use a pilot wave when workloads differ
If the estate has several shapes, measure one of each before you extrapolate.
Present ranges and coverage, not a single figure
Show the share of lines priced, the assumptions behind them and the items kept out. Run 2 already gives a range for the STACKIT side rather than one figure.
Questions finance and architecture ask
Does StackToIT say how much we will save?
Only when both sides are priced for the same units in one currency. Run 2 stated no saving, and said why. A saving percentage needs a full comparison with an exchange rate whose source and date are named.
Are the prices current?
They come from a price cache with a retrieval date shown in the run (26 October 2026 here). Both runs showed the AWS S3 offer missing from the cache and named the refresh command. The demo runs were offline; without --offline an assessment can fetch missing public prices itself.
Is the output an invoice?
No. The run calls its figures an initial infrastructure cost estimate under explicit assumptions, not a bill: list prices, no discounts, no Spot, no savings plans.
What about the cost of the migration itself?
Not computed. The one one-off item the model states is the cost of running both estates in parallel, apart from every total.
Which currency should we use?
Use one for the decision. The model keeps each side in its own currency and compares only with an exchange rate you give.
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.