---
title: "Rehost Spring Boot with Terraform and Ansible"
description: "Runnable Rehost automation for a Spring Boot JAR on one STACKIT VM with local PostgreSQL, rehearsed cutover, rollback evidence, Observability, and Server Backup."
sidebar:
  badge:
    text: "STACKIT"
    variant: success
scfAsset:
  managed: false
  category: "runbook"
  external: false
  tags: ["design-and-mobilize", "design", "runnable-example", "rehost", "automation", "terraform", "ansible", "spring-boot"]
  maintainers:
    - user: "lukas.weberruss"
      role: true
      website: true
source_url: "https://framework.stackit.cloud/migration/assetcontainer/stackit/rehost-automation-spring-boot-terraform-ansible/"
source_file: "docs/migration/assetcontainer/stackit/rehost-automation-spring-boot-terraform-ansible.mdx"
---

## Reference implementation

The Migration Framework defines the strategy, design, landing-zone, migration, and operations
principles for Rehost workloads. This asset applies those principles to one concrete, runnable
Spring Boot and PostgreSQL implementation on STACKIT.

The maintained repository is the source of truth for Terraform, Ansible, application artifacts,
migration scripts, validation, and runtime configuration. This asset explains how to use that
implementation without duplicating its complete source code.

<LinkCard
  title="STACKIT CMF Rehost Spring Boot repository"
  description="Open the runnable Terraform and Ansible reference implementation for the Spring Boot and PostgreSQL Rehost path."
  href="https://github.com/stackitcloud/stackit-cmf-Rehost-springboot"
/>

## Migration context and scope

This example represents an application Rehost path for a Spring Boot workload (Spring Music sample).
The migration focus is the runtime relocation of the application to STACKIT:

- **Infrastructure Rehost**: Terraform provisions one STACKIT VM, its boot volume, network, public IP,
  security group, SSH key, Observability resources, and an optional Server Backup schedule.
- **Application Rehost**: Ansible installs Java, deploys the Spring Boot JAR, and manages it with
  `systemd` without changing the application runtime model.
- **Stateful Rehost**: Ansible installs self-managed PostgreSQL on the same VM. The database engine is
  not changed to a managed service as part of this path.
- **Controlled migration**: The repository provides separate rehearsal, cutover, validation, and
  rollback workflows with machine-readable evidence.

The validated baseline deliberately does not include an application load balancer, DNS switch,
multiple VMs, Kubernetes, Cloud Foundry, or PostgreSQL Flex. Add those only as separately designed
and tested extensions; they are not implied by this Rehost example.

## What is included in the repository

- **Provisioning**: Terraform resources for network, security, one VM, boot volume, SSH key, and public IP.
- **Configuration bridge**: Terraform triggers Ansible after infrastructure provisioning.
- **Application deployment**: Ansible deploys a concrete Spring Boot artifact and configures a systemd service.
- **VM-local database path**: PostgreSQL installation, application role and database bootstrap, and
  a dump-based restore with ownership and data-integrity checks.
- **Operations baseline**: Node exporter metrics are scraped by STACKIT Observability, and Terraform
  can enable daily Server Backup for the VM boot volume.
- **Migration controls**: Source dump generation, independent restore validation, rehearsal, cutover,
  rollback verification, rollback execution, and evidence capture.
- **Deployable sample artifact**: A ready-to-run JAR is included in the repository and used by default deployment settings.

## Unified automation flags

To keep automation behavior consistent across CMF examples, use this common flag model in your Terraform variable set:

- **`setup_project`**: create/use project context.
- **`setup_observability`**: enable/disable observability resources.
- **`setup_database`**: enable/disable optional VM-local PostgreSQL path.
- **`setup_workload`**: enable/disable workload installation on VM.
- **`setup_loadgen`**: enable/disable optional synthetic load generation.
- **`setup_dns`**: accepted by the shared wrapper but unsupported in this baseline.

In the current Rehost repository, these map to existing switches (`create_project`, `enable_observability`, `enable_local_postgresql`, and load-generation related flags).

## Validated target architecture

The following diagram shows the implemented and tested target, not a future high-availability variant.

```d2
direction: right

Operator: "Approved operator source" {
  icon: ../../../../../../public/stackit-icons/networking/ip.svg
}

TargetProject: "STACKIT Project" {
  VM: "Ubuntu VM" {
    icon: ../../../../../../public/stackit-icons/computing/virtual-machine.svg
    link: https://docs.stackit.cloud/products/compute-engine/server/
    App: "Spring Boot JAR + systemd"
    PostgreSQL: "Self-managed PostgreSQL"
  }
  Obs: "Observability" {
    icon: ../../../../../../public/stackit-icons/logging-monitoring/observability.svg
    link: https://docs.stackit.cloud/products/logging-and-monitoring/observability/
  }
  Backup: "Backup Archive" {
    icon: ../../../../../../public/stackit-icons/computing/archive.svg
    link: https://docs.stackit.cloud/products/compute-engine/server-backup-management/
  }
}

Operator -> TargetProject.VM.App: "restricted HTTP/SSH"
TargetProject.VM.App -> TargetProject.VM.PostgreSQL: "localhost SQL"
TargetProject.Obs -> TargetProject.VM: "restricted metrics scrape"
TargetProject.VM -> TargetProject.Backup: "boot-volume backup"
```

## Prepare the walkthrough workspace

Use an isolated Linux lab with Git, Terraform, Ansible, ShellCheck, SSH/SCP, curl, jq,
and PostgreSQL server/client tools including `pg_config`. The sample dump scripts use
`runuser` and the local `postgres` OS account and require root privileges in that lab.
When Server Backup is enabled, the cutover workflow also needs an authenticated STACKIT CLI.
Use the Terraform version that created an existing saved plan; plan files are version-specific.

Start in a parent directory without an existing checkout of the same name. This revision
contains the migration workflows and declarative Observability management. Its JAR is unchanged
from the artifact pinned by the Replatform reference:

```bash
umask 077 &&
  git clone https://github.com/stackitcloud/stackit-cmf-Rehost-springboot.git &&
  git -C stackit-cmf-Rehost-springboot checkout --detach b9225eb35c64b9ef4761c208fadb9a2356431793 &&
  test -f stackit-cmf-Rehost-springboot/scripts/run_cutover.sh &&
  cd stackit-cmf-Rehost-springboot &&
  printf '%s\n' "Workspace ready. Continue from this Rehost checkout." || {
    printf '%s\n' "Preparation failed. Resolve the error before continuing." >&2
    false
  }
```

Run the following steps from that checkout only after preparation succeeds. Preserve an existing
checkout, state and evidence rather than replacing them. Keep credentials, plans, state, dumps,
inventory and evidence private and outside version control; do not enable shell tracing.

## Configure the target inputs

Create the private variable file only when it does not already exist:

```bash
umask 077
test -e env.tfvars || cp env.tfvars.example env.tfvars
chmod 600 env.tfvars
```

Edit the file before planning. Configure the approved existing project or project-creation scope,
service-account key path, SSH key pair, available image, availability zone, VM flavor and storage.
Restrict SSH and application ingress to the source CIDRs actually observed on the target path.
Verify the SSH host key through a trusted channel before migration scripts use strict host checking.
Set `SSH_KEY` and `SSH_USER` for those scripts when they differ from `~/.ssh/id_rsa` and `ubuntu`;
keep them consistent with Terraform's `private_ssh_key_path` and `ssh_user`.

Enable VM-local PostgreSQL, Observability and Server Backup for this walkthrough. For the initial
runtime deployment, leave `postgresql_restore_after_copy = false` and the source dump path empty:
provisioning must not import the source before rehearsal and cutover approval. Supply the database
password through the approved secret mechanism as `TF_VAR_postgresql_app_password` and retain it
securely for subsequent plans; do not put it in the variable file, shell history or documentation.

Confirm permissions, quota, cost approval and the private Terraform backend before initialization.
This workflow assumes a prepared execution host, not a complete lab installation procedure.

## Prepare source evidence

Create a custom-format source dump and record its SHA-256 checksum, expected row count, and a
workload-specific deterministic fingerprint. The repository includes a reproducible eight-row sample:

```bash
./scripts/create_source_dump.sh
./scripts/validate_source_dump.sh
```

The validation script restores into a separate local PostgreSQL cluster before the dump can enter a
migration rehearsal. These scripts generate the bundled sample in temporary local clusters; they do
not export a running application or the STACKIT VM. Use a fresh artifact directory and do not
overwrite a dump already approved for migration. For a real source, export it under the agreed
write-freeze procedure and record equivalent checksum, count and fingerprint evidence.

## Provision the target

Configure an existing STACKIT project or project creation, restrict SSH and application ingress to
approved source CIDRs, and pass the database password through `TF_VAR_postgresql_app_password` rather
than a variable file. Review a saved plan before apply.

```bash
terraform init &&
  ./scripts/check.sh &&
  terraform plan -input=false -var-file=env.tfvars -out=tfplan
```

Stop if initialization, checks or planning fail. Review resource changes, target project, ingress,
cost and the disabled initial restore before explicitly applying that saved plan:

```bash
terraform apply tfplan
```

Terraform runs Ansible after provisioning. A successful apply proves completion of that orchestration,
not acceptance of migrated data. Check the runtime before beginning rehearsal:

```bash
./scripts/validate_deployment.sh
```

## Rehearse the migration

Run the rehearsal against a temporary database on the target VM:

```bash
./scripts/run_migration_rehearsal.sh
```

The rehearsal checks the source evidence, restores the dump, compares row count and fingerprint, and
removes its temporary database without changing the production `springmusic` database.

## Execute cutover

Set the expected source evidence in `env.tfvars`:

```hcl
enable_local_postgresql                  = true
postgresql_source_dump_local_path        = "artifacts/source-postgresql.dump"
postgresql_restore_after_copy            = true
postgresql_expected_album_count          = 8
postgresql_expected_album_fingerprint    = "<source-fingerprint>"
```

Then execute the explicit approval gate:

```bash
./scripts/run_cutover.sh --confirm
```

The script requires a fully available Server Backup no older than 24 hours, creates a saved Terraform
plan that replaces only the Ansible orchestration resource, rejects unrelated infrastructure changes,
applies the restore, validates data and runtime behavior, and requires a final no-op Terraform plan.
Retries of the same source-dump hash preserve the original database rollback point.

## Validate and roll back

Validation checks the source row count and fingerprint, table ownership, a transactionally rolled-back
write as the application role, application reachability, services, and the protected rollback dump.

```bash
./scripts/validate_migration.sh
./scripts/validate_deployment.sh
./scripts/verify_rollback.sh
```

If an approved rollback trigger is met during the rollback window, preserve the current target database
and restore the original pre-cutover dump:

```bash
./scripts/rollback_postgresql.sh --confirm
```

## Backup and recovery boundary

With `enable_server_backup = true`, Terraform enables STACKIT Server Backup and a daily schedule for
the VM boot volume. The default retention is 14 days. Cutover requires a fully available backup no older
than 24 hours. Backup creation and status were validated against the real target; an in-place Server
Backup restore remains a disruptive disaster-recovery operation and must be rehearsed in a separate
recovery environment.

The Server Backup complements the PostgreSQL pre-restore dump. It does not replace database-level
consistency checks or the application rollback procedure.

## Usage

Follow workspace preparation, private target configuration, source evidence and reviewed provisioning
in that order. Rehearse before the approved cutover and retain the acceptance evidence afterwards.
The repository scripts invoke `terraform` explicitly; an OpenTofu-only execution host needs a
separately reviewed adaptation, not just replacement of commands in this page.

## Monitoring and dashboard snapshot

The implementation provisions STACKIT Observability and scrapes node exporter. The Grafana provider
manages the included dashboard in the `SCF Rehost` folder with the existing `Thanos` datasource,
waiting for instance readiness instead of silently skipping dashboard creation.

Verify live VM, Spring Boot and PostgreSQL metrics through `grafana_dashboard_url`, then require a
no-op plan. Seven panels cover the active baseline; the synthetic request panel is included only
when the local load generator is enabled. Synthetic requests do not represent all application traffic.
The deployment validation also checks exporter metrics and service health after apply and database restore.

Initial Grafana admin credentials remain a temporary authentication dependency. Protect state and saved
plans. Notification receivers, alert-routing ownership, application logs and distributed tracing require
separate configuration and acceptance before production use.

## Application landing zone alignment

This example can be run in two valid setup modes.

- **With an existing Application Landing Zone**: Reuse the already defined project context and service account setup from your landing zone implementation.
  Use the known project ID as deployment target (for example with `create_project = false` and `project_id = "..."`) and the service account JSON configured for that landing zone scope.
- **Standalone without existing landing zone project**: Let the example create a dedicated project.
  In this mode, set `create_project = true` and provide `parent_container_id` with an existing folder/container where the service account has sufficient permissions.

For landing-zone design guidance, see [Application Landing Zone](/migration/design-and-mobilize/landing-zones/application-landing-zone/).

## Minimal configuration example

Use this as a minimal starting point in env.tfvars.

```hcl
create_project              = true
target_project_name         = "cmf-rehost-springboot"
target_project_owner_email  = "owner@example.com"
parent_container_id         = "cmf-xxxxxxxx"
service_account_key_path    = "~/.ssh/cmf-sa.json"
public_ssh_key_path         = "~/.ssh/id_rsa.pub"
private_ssh_key_path        = "~/.ssh/id_rsa"
availability_zone          = "eu01-1"
machine_type               = "g2i.2"
ssh_allowed_cidr           = "203.0.113.10/32"
app_allowed_cidr           = "203.0.113.10/32"
enable_local_postgresql    = true
enable_observability       = true
enable_server_backup       = true
```

## Deployment artifact behavior

- **Default artifact path**: `ansible/files/springboot-app.jar`
- **Default variable**: `jar_local_path` points to the same file.
- **Alternative artifact**: Replace the file or override `jar_local_path`.

When the artifact changes, Terraform detects the checksum difference and re-runs the Ansible deployment step.

## Evidence and acceptance

Every rehearsal, cutover, and rollback writes an `evidence.env` file below
`artifacts/evidence/<timestamp>-<mode>/`. Accept a cutover only when the evidence records the expected
checksum, row count, fingerprint, target VM, successful runtime validation, and final Terraform no-op.

```bash
./scripts/validate_migration.sh
./scripts/validate_deployment.sh
terraform plan -var-file=env.tfvars -detailed-exitcode
```

Use the same approved source path, restore flag, expected count and fingerprint as the completed
cutover. Exit code `0` means no changes, `2` means changes are proposed, and `1` means an error.
A plan alone is not proof of apply. The cutover workflow records `status=passed` and
`terraform_noop=true` only after applying its saved plan and completing data and runtime validation.

## Migration decision gates

- **Prepare**: Approve the target design, access paths, source evidence and recovery responsibilities.
- **Provision**: Review and apply the infrastructure plan, then verify the VM runtime independently.
- **Rehearse**: Restore into an isolated database and verify integrity without changing the application database.
- **Cut over**: Freeze source writes, approve the final dump and allow only the intended orchestration change.
- **Accept or recover**: Require data, runtime and no-op evidence; invoke the agreed rollback before its deadline when acceptance fails.
- **Handover**: Transfer monitoring, backup ownership, evidence and source-retention decisions before optimization.

## Reuse as a Replatform baseline

The Replatform reference reuses this repository's pinned Spring Music JAR and sample-data generator.
Its sample walkthrough therefore needs this checkout and validated dump artifacts, but not a newly
provisioned Rehost VM. Do not create another VM solely to generate the local sample.

For an actual migration from the Rehost VM to SKE and PostgreSQL Flex, verify that the VM is currently
healthy, freeze its application writes, export its real PostgreSQL database and qualify that export.
A historical successful Rehost apply does not establish current reachability or make the local sample
equivalent to live VM data. Source export, acceptance and source failback need their own approved procedure.

## Notes

- Keep operational procedures aligned with the runbook and migration governance.
- For production usage, adapt security controls, sizing, image selection, and life cycle automation.
