---
title: "Cloud Migration Trail Authoring Guide"
description: 'Cloud migration trail authoring guide: compose, structure, and link sequential architecture journey timelines in the STACKIT Cloud Framework content repo.'
scfAsset:
  category: "guide"
  external: false
  tags: ["Trails", "Timelines", "Astro", "Blueprint"]
  maintainers:
    - user: "tobias.mueller"
source_url: "https://framework.stackit.cloud/advisory/assetcontainer/stackit/howto-trail-setup/"
source_file: "docs/advisory/assetcontainer/stackit/howto-trail-setup.mdx"
---

Mapping out comprehensive migration paths connects scattered individual pieces into a single unified workspace experience. The core layout engine interprets structured parameters to build interactive timeline tracks for developers.

---

## Folder Structure and Loading Requirements

For the system to automatically detect and register a journey path, your documents must follow precise workspace file paths.

### Workspace Path Regulations

- **Target Storage Directory**: Save your configuration file in the following specific directory path inside the workspace repository:

```text
apps/docs/src/content/docs/[framework-slug]/trails/[contributor-slug]/[trail-slug].mdx
```

- **Required Configuration Property**: The file header parameters must include the base `scfTrail` key. If this entry is missing, the indexing loader will automatically skip the document to ensure work-in-progress content stays hidden.
- **Optional Tags**: Add up to 10 tags under `scfTrail.tags` (max 20 characters each) to make the trail discoverable. The card shows the first few plus a `+N` chip; every tag is searchable in the trail explorer.

---

## Step Stage Styles (`style` enum)

Each timeline step accepts an optional `style` key that tags the milestone with its journey stage. These are the values validated by the content schema:

| Style Key | Milestone Stage |
| :--- | :--- |
| `compass` | **PLAN** — strategic alignment, assessments, and migration discovery analysis. |
| `hut` | **BASE** — baseline environment setup and prerequisites. |
| `stairs` | **STEP** — core engineering requirements and platform maturity. |
| `t-bar` | **LIFT (low)** — incremental enablement of capabilities. |
| `chairlift` | **LIFT (mid)** — scaling capabilities across teams. |
| `gondola` | **AUTO** — continuous delivery and automated workflow execution pipelines. |
| `shield` | **SAFE** — governance validation, security baselines, and C5 compliance tracking. |
| `crevasse` | **WARN** — risks, pitfalls, and remediation checkpoints. |
| `chart` | **OPS** — observability, cost, and operational steering. |
| `rocket` | **LIVE** — production launches and go-live milestones. |
| `summit` | **GOAL** — milestone completion, handover, and monitoring. |

The `style` value is optional. Steps without it still render correctly.

---

## Step Configuration Types

Timeline items can contain either custom descriptions or dynamic links to framework documents.

### 1. Descriptive Context Items
Perfect for steps that describe a high-level concept or phase where no physical code repository exists yet. Authors supply a custom `title` and a `description` field manually.

### 2. Connected Component Items
Points directly to a dynamic codebase example or pattern via its standard **Astro ID**. The core parser fetches the asset information, handles the creator data, and displays an interactive `SCFAssetCard` right on the milestone timeline track. Append an optional `#section-anchor` to the `assetId` to embed only that section of the asset instead of the whole document, exactly like the page references below (for example `.../assetcontainer/stackit/howto-contributors#directory-structure--asset-placement`).

### 3. Page Reference Items
Links a regular framework page (not an asset) via its **`pageId`**. The step renders an interactive `SCFPageCard` and embeds the page content directly in the timeline. Append an optional `#section-anchor` to render only that section of the page instead of the whole document, for example `migration/migrate/#core-modules-in-this-phase`.

---

## Nesting Steps as Sub-Points (`role`)

Long trails often walk through several sections of the *same* source, and repeating the full step header (stage marker + asset card + contributor card) for each one buries the timeline. Give such a step **`role: sub`** and it nests as a compact, lettered row (a, b, …) under the nearest preceding main step — just its title and a one-line context, no stage marker and no cards. Steps default to `role: main`, so existing trails are unchanged.

- **Grouping** — sub-steps attach to the main step above them. A `sub` with no preceding main is treated as a main step.
- **Foreign sources** — if a sub-step points at a different `assetId` / `pageId` than its main step, a small source chip appears in the row and a slim source banner (logo · title · contributor · category · open link) is shown above its content, so the reader always knows where the content comes from. Same source as the parent = nothing extra.
- **Expand all** — a single button above the timeline opens or collapses every sub-step at once.
- **Presentation link** — a step's `role` is also the default agenda role when the trail is presented (see the [Presentation guide](/assets?asset=advisory/assetcontainer/stackit/howto-presentation)), so `sub` steps come through as indented sub-points without extra work.

```yaml
- style: "stairs"
  title: "Provision the Landing Zone"
  assetId: "migration/assetcontainer/stackit/landing-zone-foundation-opentofu"
  trailContext: "The accelerator provisions the baseline. The next rows walk its layers."
- role: "sub"
  title: "Module-by-module breakdown"
  assetId: "migration/assetcontainer/stackit/landing-zone-foundation-opentofu#module-by-module-breakdown"
  trailContext: "Governance, connectivity and DevOps: what each module provisions."
- role: "sub"
  title: "Account governance"
  pageId: "migration/design-and-mobilize/design/account-governance"
  trailContext: "A different source, so this row shows a source banner."
```

---

## Giving a Step Its Own Picture

A step can carry a figure. In the timeline it appears under the step; in the presentation it becomes the slide's second column, or a full-width cover. Two fields control the layout in both cases:

- **`imagePosition`**: leave it out and the picture is centered — a cover that spans the slide under the title, with the step text following underneath (`full` says the same thing explicitly). `right` or `left` puts the picture beside the text instead.
- **`imageWidth`**: the picture column's width in percent for `left` / `right` (20–70, default 45). Ignored for centered covers.

Both fields are optional, so existing trails keep working unchanged.

The other column is whatever the step already shows as its content — you choose it with the fields you know: a `trailContext` paragraph, an asset section via `assetId` with a `#section-anchor`, or a page section via `pageId` with a `#section-anchor`. So "image on the right, an asset section on the left" is just both fields on one step:

```yaml
- style: "gondola"
  title: "Run the Migrate Phase"
  pageId: "migration/migrate/#core-modules-in-this-phase"
  imageSrc: "migration/design-and-mobilize/security-and-compliance/files/security-and-compliance-overview-map-en.svg"
  imageAlt: "Security topics that accompany the Migrate phase"
  imagePosition: right
  imageWidth: 40
```

The picture itself is named with **`imageSrc`**, a path relative to the content root, so you can keep the file next to your trail:

```yaml
- style: "shield"
  title: "Anchor Security and Compliance Early"
  trailContext: "The topic map below shows what has to be in place before the first workload moves."
  imageSrc: "migration/design-and-mobilize/security-and-compliance/files/security-and-compliance-overview-map-en.svg"
  imageAlt: "Security and compliance topic map"
  imagePosition: full
```

`imageAlt` is both the alt text and the caption. Only pictures on this site are allowed — a path starting with `/` works too, but external `http(s)` addresses are ignored on purpose. Animated SVGs work: whatever animation is baked into the file plays in the timeline and on the slide.

---

## Placing Content in Columns (`left` / `right` / `center`)

The picture fields above are the shortcut for "one content plus one image". For full control over both sides, a step can place **any** content type into named slots — not just images. Each of `left`, `right` and `center` holds exactly one thing:

- **`text: "…"`** — free prose
- **`assetId: "…"`** — an asset **card** (a pointer). Add a `#section-anchor` to embed just that one section instead (the card is hidden by default; add `cards: true` to keep it).
- **`pageId: "…"`** — a page **card** (a pointer). Add a `#section-anchor` to embed just that one section instead (the card is hidden by default; add `cards: true` to keep it).
- **`trailId: "…"`** — a trail **card** (a pointer to another trail).
- **`imageSrc: "…"`** (+ `imageAlt`) — a figure (click to zoom)

**Card = pointer, `#anchor` = content.** This is the key rule for columns: a *bare* `assetId` / `pageId` / `trailId` renders only the card — a compact pointer that says "here is the thing". When you want the reader to actually see some of that content in the column, add a `#section-anchor` to an `assetId` or `pageId`, and exactly that one section is embedded on its own — **the card is hidden by default** so the column shows just the section (a compact origin breadcrumb `Framework › Title` stands in, so the reader still sees where it comes from). Trail cards are always pointers (a trail has no sections to embed). A whole asset or page body is too tall for a column — if you want the full content in the deck, reference it as a **flat step** (`assetId` / `pageId` at the step level, not in a column) instead.

**Showing the card with `cards: true`.** Because a step that stacks several sections of the same source would otherwise repeat that card in front of every one of them, a section column drops the card by default. When you *do* want the pointer card above the section, set **`cards: true`** on the column and it appears above the embedded content. The flag only matters when the column carries a section; a bare pointer always keeps its card so it never renders as an empty column.

**Several items in one slot.** `left`, `right` and `center` each also accept a list. The items stack in the given order inside that column, separated by a thin divider, and they can come from different sources: three sections of one asset on the left, two pages on the right.

Where each slot renders:

| Frontmatter | Layout |
| :--- | :--- |
| `left:` + `right:` | two columns side by side (split) |
| `left:` only | that content in the left half, left-aligned |
| `right:` only | that content in the right half, right-aligned |
| `center:` | that content centered as a cover |
| none of these | the step renders exactly as before (backward compatible) |

Under ~900px the columns stack. For a split, `splitRatio` sets the left column's width in percent (10–90, default 50). The timeline and the presentation slide use the same layout, so what you compose here is what the deck shows.

```yaml
# split: a page section on the left, an asset on the right, 55/45
- style: "stairs"
  title: "Decision, then the tool that implements it"
  splitRatio: 55
  left:
    pageId: "migration/design-and-mobilize/design/overview/#decision-criteria-by-r-strategy"
  right:
    assetId: "migration/assetcontainer/stackit/landing-zone-foundation-opentofu"

# text on the left, a figure on the right
- style: "shield"
  title: "Anchor Security Early"
  left:
    text: "Map the control topics before the first workload moves, not in the audit."
  right:
    imageSrc: "migration/design-and-mobilize/security-and-compliance/files/security-and-compliance-overview-map-en.svg"
    imageAlt: "Security and compliance topic map"

# center: a single cover figure (or centered text)
- style: "chart"
  title: "The Whole Terrain"
  center:
    imageSrc: "advisory/trails/stackit/getting_started/map-overview.svg"
    imageAlt: "Framework overview map"

# several sections in one column: cards hidden by default, each under an origin breadcrumb
- style: "stairs"
  title: "Three Steps of the Same Runbook"
  splitRatio: 60
  left:
    - assetId: "migration/assetcontainer/stackit/landing-zone-foundation-opentofu/#overview"
    - assetId: "migration/assetcontainer/stackit/landing-zone-foundation-opentofu/#deployment-flavours-and-what-they-enable"
  right:
    text: "Each section keeps its origin breadcrumb, so the reader can tell the three apart without three identical cards. Add cards: true to a column if you do want its pointer card shown."
```

---

## Alternative Steps (`alternatives`)

Sometimes one stage of a journey has several equally valid ways to walk it: migrating the VMs with the standard guide, with a live-migration tool or with a replication tool. Without alternatives you would either build three steps for one decision or leave the alternatives out. With alternatives the step stays **one waypoint** (one title, one stage marker, one agenda entry) and carries several **tracks** the reader can switch between.

- The step's own `assetId` / `pageId` (or, with neither, its `trailContext` text) is the **default track**. Nothing changes for existing trails: `alternatives` is purely additive.
- Each alternative needs a stable **`id`** (deep links and presentation views key on it) and carries exactly one of the three content kinds a flat step can carry: an **`assetId`**, a **`pageId`** (both may take a `#section-anchor`) or, with neither, plain text from **`label` + `trailContext`**.
- `label` is the name shown in the chooser; for asset and page tracks it falls back to the referenced title.
- An alternative's own `trailContext` replaces the step's for that track. It is also that track's speaker note in the presentation.
- **Limits:** up to 4 alternatives per step, and only on flat main steps. Sub steps (`role: sub`) and column steps (`left` / `right` / `center`) do not support alternatives; the validator tells you if you try.

On the page the step renders its active track exactly like a normal step, with the track's own asset and contributor cards. A quiet line under the cards ("2 alternatives for this step") opens the chooser; picking a track swaps the cards and content in place. The choice lands in the URL as `?alt=<step-id>:<alternative-id>` (comma-separated across steps), so a link can share a specific route — give alternative steps an explicit step `id` for that. "Copy for AI" always exports the track that is currently shown and names the alternatives.

In the presentation the launch screen offers a track select per alternative step (defaulting to whatever the page has active), and a prepared view can pin a track via `alternative:` on its step reference — see the presentation guide below.

```yaml
- style: "stairs"
  id: "migrate-vms"
  title: "Migrate the Virtual Machines"
  trailContext: "Move the compute layer in waves. Pick the alternative that matches your downtime budget."
  assetId: "migration/assetcontainer/stackit/migration-asset"
  alternatives:
    - id: "hystax"
      label: "Live migration with Hystax Acura"
      assetId: "migration/assetcontainer/hystax/hystax-acura-live-migration"
      trailContext: "For near-zero downtime windows."
    - id: "coriolis"
      assetId: "migration/assetcontainer/cloudbase/cloudbase-coriolis"
    - id: "manual"
      label: "Manual wave migration"
      trailContext: "Plan and execute the waves by hand with the runbooks."
```

---

## Production Frontmatter Blueprint

Copy this metadata configuration layout as a baseline template when creating a new framework tracking timeline. Optionally add a `maintainers` list under `scfTrail` to credit who maintains the trail as a point of contact (shown above the git history), the same field assets use:

If you have a STACKIT Git account, name yourself by **username** and nothing else — that is the whole entry:

```markdown
scfTrail:
  maintainers:
    - user: "jane.doe"
```

| Field | Purpose |
| :--- | :--- |
| `user` | STACKIT Git username. The lookup key for the mirrored profile, and the fallback label until it has been mirrored. |
| `role` | Sets the role text literally. For people without an account: with a `user`, the biography from their own profile appears instead, and only if they allowed it. |
| `website` | Sets a URL literally, same rule as `role`. |
| `email` | Sets an address literally, same rule as `role`. It renders as its own mail action, so it sits alongside a profile link instead of competing with it. |
| `name` | Overrides the mirrored full name, and credits people who have no Git account. It makes the entry a person: initials avatar, role line reading *role · organization*. |
| `contributor` | Slug of a `/contributors/<slug>` profile, for people without an account. On its own it credits the organization, using the profile title, its logo initials and its official partner badge. Ignored next to a `user`, whose organization comes from the membership file. |
| `link` | A profile or homepage URL, typically LinkedIn. It takes precedence over both the mirrored website and the contributor profile. |

Every field is optional and they combine freely; an entry only has to carry at least one of `user`, `name`, `contributor` or `email`, and one trail accepts up to **6** entries. Keys the schema does not know are dropped without an error, so a typo leaves the build green and renders nothing. Maintainers are a credit, not a permission: the list does not restrict who may edit the trail. The [asset integration guide](/advisory/assetcontainer/stackit/howto-asset-integration#crediting-maintainers) carries the same fields with an example for external partners.

```markdown
---
title: "Standard Kubernetes Onboarding Track"
description: "A comprehensive guided journey trail directing engineering workloads from legacy virtualization targets into sovereign managed STACKIT clusters."
scfTrail:
  # Optional: credit the maintainers (shown as a point of contact above the git history).
  maintainers:
    - name: "Jane Doe"
      role: "Trail Owner"
  steps:
    - style: "compass"
      title: "Initial Assessment Phase"
      trailContext: "Analyze workload traits and infrastructure requirements before initializing resources."
    
    - style: "gondola"
      assetId: "migration/assetcontainer/stackit/landing-zone-foundation-opentofu"
      trailContext: "Deploy the landing zone automated profile using standardized infrastructure code scripts."

    - style: "stairs"
      title: "Execute the Migration Modules"
      trailContext: "Work through the core migration modules; the optional #anchor renders only that page section."
      pageId: "migration/migrate/#core-modules-in-this-phase"

    - style: "summit"
      title: "Final Cluster Validation"
      description: "Review compliance policies and confirm that all configuration checks pass security rules."
---
```

---

## Reference Links in Supporting Pages

A trail file itself is frontmatter-only, so the link rule never touches it: text in `trailContext` and column `text` renders as plain text, links included. It does apply to the **asset and content pages your steps point at**: there, every reference that stands on its own line must be a `<LinkCard>` or `<LinkChip>`, both auto-imported, both deriving their kind (portal, marketplace, documentation, code, third party) from the URL alone. The PR pipeline blocks bare standalone links in changed files and verifies that on-site card and chip targets exist. The full rule lives in the [asset integration guide](/advisory/assetcontainer/stackit/howto-asset-integration).

---

## Presenting a Trail

Every trail can be opened as a slide deck from the presentation button on its detail view — no extra frontmatter required. As the author you can also predefine **prepared views** (curated slide selections), preset the launch behaviour, and turn a step's figure into a split slide. All of that lives in a `presentations` block under `scfTrail` and is documented in its own guide: the **[Presentation guide](/assets?asset=advisory/assetcontainer/stackit/howto-presentation)**.

---

## SCF Studio

To compose a trail without writing YAML by hand, use the **SCF Studio**. Start the docs (`hike`) and open `/studio` in your dev container, or use it on the dev site. It builds the steps, resolves every asset and page you point at, and it can load an existing trail and change only what you edit.

---

## Live Component Playground (local dev server)

Run the docs locally (VS Code task **🥾 Hike — Start Dev Docs**, or the `hike` command in the container terminal, served on `http://localhost:4321`) and open the interactive playgrounds below to see the trail components rendered live:

- **Trail Loader** — the auto-discovered, equal-height trail grid exactly as embedded via `<ScfTrailLoader />`, including the inline detail view: `http://localhost:4321/demo/components/demo-scf-trail-loader`
- **Trail (single timeline)** — the full interactive milestone timeline with accordion asset cards, demonstrating per-step `style`, `assetId`, `title`, and `trailContext` handling: `http://localhost:4321/demo/components/demo-scf-trail`

---

## Central Overview

<div class="scf-overview-card-container">
	<a href="/trails" class="scf-custom-link-card">
		<div class="scf-card-text-box">
			<h4 class="scf-card-title">Explore the Cloud Framework Trails Overview</h4>
			<p class="scf-card-desc">Return to the main directory to view all live, interactive migration journeys and developer tracks.</p>
		</div>
		<div class="scf-card-icon-box">
			<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><line x1="5" y1="12" x2="19" y2="12"></line><polyline points="12 5 19 12 12 19"></polyline></svg>
		</div>
	</a>
</div>

<style>{`
	.scf-overview-card-container {
		margin: 3rem 0;
		width: 100%;
		box-sizing: border-box;
	}

	.scf-custom-link-card {
		display: flex;
		flex-direction: row;
		align-items: center;
		justify-content: space-between;
		padding: var(--nds-viewport-spacing-component-150, 24px);
		background: var(--nds-mode-color-background-neutral-surface-default-rest, #18232c);
		border: 1px solid var(--nds-mode-color-border-neutral-subtle-rest, #2e3c48);
		border-radius: var(--nds-viewport-border-radius-container, 16px);
		text-decoration: none !important;
		transition: all 0.2s ease-in-out;
		gap: 24px;
	}

	.scf-custom-link-card:hover {
		background: var(--nds-mode-color-background-neutral-surface-default-hover, #202c36);
		border-color: var(--nds-mode-color-foreground-primary-on-neutral, #00c2cc);
		transform: translateY(-2px);
		box-shadow: 0 8px 24px -8px rgba(0, 194, 204, 0.2);
	}

	.scf-card-text-box {
		display: flex;
		flex-direction: column;
		gap: 6px;
		flex: 1;
		min-width: 0;
	}

	.scf-card-title {
		margin: 0 !important;
		font-family: var(--nds-mode-font-family-heading, 'Univia Pro'), sans-serif;
		font-size: var(--nds-viewport-font-size-heading-content-level-3, 18px) !important;
		font-weight: 700;
		color: var(--nds-mode-color-foreground-neutral-high-contrast, #fff) !important;
	}

	.scf-card-desc {
		margin: 0 !important;
		font-family: var(--nds-mode-font-family-body, 'DIN 2014'), sans-serif;
		font-size: var(--nds-viewport-font-size-body-small, 14px);
		color: var(--nds-mode-color-foreground-neutral-subtle, #9da5ac);
		line-height: 1.4;
	}

	.scf-card-icon-box {
		display: flex;
		align-items: center;
		justify-content: center;
		width: 40px;
		height: 40px;
		border-radius: 50%;
		background: var(--nds-mode-color-background-neutral-component-subtle-rest, #25323d);
		color: var(--nds-mode-color-foreground-neutral-default, #e5eaee);
		flex-shrink: 0;
		transition: all 0.2s ease-in-out;
	}

	.scf-custom-link-card:hover .scf-card-icon-box {
		background: var(--nds-mode-color-foreground-primary-on-neutral, #00c2cc);
		color: var(--nds-mode-color-background-neutral-surface-sunken-rest, #101820);
		transform: translateX(4px);
	}

	@media (max-width: 600px) {
		.scf-custom-link-card {
			flex-direction: column;
			align-items: flex-start;
			gap: 16px;
		}
		.scf-card-icon-box {
			align-self: flex-end;
		}
	}
`}</style>
