# How to migrate from Hetzner Cloud to Quake AI with OpenTofu

Source: https://docs.quake.ai/docs/automation/migration/from-hetzner
Markdown: https://docs.quake.ai/docs/automation/migration/from-hetzner.md

---

# How to migrate from Hetzner Cloud to Quake AI with OpenTofu

Hetzner Cloud and Quake AI attract similar audiences: cost-conscious engineers who self-manage infrastructure. If you already use Terraform or OpenTofu with the Hetzner Cloud provider, migrating to Quake AI is a provider swap with resource remapping.

## Conceptual mapping

Both platforms offer similar primitives, but the APIs and resource names differ:

| Concept | Hetzner Cloud (hcloud) | Quake AI (OpenStack) |
|---|---|---|
| Provider | `hetznercloud/hcloud` | `terraform-provider-openstack/openstack` |
| Server | `hcloud_server` | `openstack_compute_instance_v2` |
| Server type | `cx22`, `cpx31`, etc. | `s1a.small`, `s1a.medium`, `m2a.large`, etc. |
| SSH key | `hcloud_ssh_key` | `openstack_compute_keypair_v2` |
| Floating IP | `hcloud_floating_ip` | `openstack_networking_floatingip_v2` |
| Firewall | `hcloud_firewall` | `openstack_networking_secgroup_v2` + rules |
| Volume | `hcloud_volume` | `openstack_blockstorage_volume_v3` |
| Network | `hcloud_network` + `hcloud_network_subnet` | `openstack_networking_network_v2` + `openstack_networking_subnet_v2` |
| Load balancer | `hcloud_load_balancer` | Self-managed reverse proxy or API gateway instance with a floating IP |
| Placement group | `hcloud_placement_group` | `openstack_compute_servergroup_v2` |
| Image | Hetzner base images | Quake AI images (Ubuntu, Debian, etc.) |

## Resource translation examples

### Server

Hetzner:

```hcl
resource "hcloud_server" "web" {
  name        = "web-1"
  server_type = "cx22"
  image       = "ubuntu-24.04"
  location    = "fsn1"

  ssh_keys = [hcloud_ssh_key.main.id]
}
```

Quake AI (private network, router, floating IP, and volume-backed boot; same topology as the [Simple VM template](/resources/iac-templates/simple-vm)):

```hcl
data "openstack_images_image_v2" "ubuntu" {
  name        = "Ubuntu-24.04"
  most_recent = true
}

data "openstack_networking_network_v2" "external" {
  name = "PublicStatic"
}

resource "openstack_networking_network_v2" "private" {
  name = "web-net"
}

resource "openstack_networking_subnet_v2" "private" {
  network_id = openstack_networking_network_v2.private.id
  cidr       = "192.168.10.0/24"
  ip_version = 4
}

resource "openstack_networking_router_v2" "router" {
  name                = "web-router"
  external_network_id = data.openstack_networking_network_v2.external.id
}

resource "openstack_networking_router_interface_v2" "private" {
  router_id = openstack_networking_router_v2.router.id
  subnet_id = openstack_networking_subnet_v2.private.id
}

resource "openstack_networking_port_v2" "web" {
  network_id = openstack_networking_network_v2.private.id

  fixed_ip {
    subnet_id = openstack_networking_subnet_v2.private.id
  }

  depends_on = [openstack_networking_router_interface_v2.private]
}

resource "openstack_compute_instance_v2" "web" {
  name        = "web-1"
  flavor_name = "s1a.small"
  key_pair    = openstack_compute_keypair_v2.main.name

  block_device {
    uuid                  = data.openstack_images_image_v2.ubuntu.id
    source_type           = "image"
    destination_type      = "volume"
    volume_size           = 20
    boot_index            = 0
    delete_on_termination = true
  }

  network {
    port = openstack_networking_port_v2.web.id
  }
}

resource "openstack_networking_floatingip_v2" "web" {
  pool = data.openstack_networking_network_v2.external.name
}

resource "openstack_networking_floatingip_associate_v2" "web" {
  floating_ip = openstack_networking_floatingip_v2.web.address
  port_id     = openstack_networking_port_v2.web.id
}
```

### Firewall to security group

Hetzner:

```hcl
resource "hcloud_firewall" "web" {
  name = "web-firewall"

  rule {
    direction = "in"
    protocol  = "tcp"
    port      = "80"
    source_ips = ["0.0.0.0/0"]
  }

  rule {
    direction = "in"
    protocol  = "tcp"
    port      = "443"
    source_ips = ["0.0.0.0/0"]
  }
}
```

Quake AI:

```hcl
resource "openstack_networking_secgroup_v2" "web" {
  name = "web-secgroup"
}

resource "openstack_networking_secgroup_rule_v2" "http" {
  security_group_id = openstack_networking_secgroup_v2.web.id
  direction         = "ingress"
  ethertype         = "IPv4"
  protocol          = "tcp"
  port_range_min    = 80
  port_range_max    = 80
  remote_ip_prefix  = "0.0.0.0/0"
}

resource "openstack_networking_secgroup_rule_v2" "https" {
  security_group_id = openstack_networking_secgroup_v2.web.id
  direction         = "ingress"
  ethertype         = "IPv4"
  protocol          = "tcp"
  port_range_min    = 443
  port_range_max    = 443
  remote_ip_prefix  = "0.0.0.0/0"
}
```

### Volume

Hetzner:

```hcl
resource "hcloud_volume" "data" {
  name     = "data-vol"
  size     = 50
  location = "fsn1"
  format   = "ext4"
}
```

Quake AI:

```hcl
resource "openstack_blockstorage_volume_v3" "data" {
  name = "data-vol"
  size = 50
}
```

Volume attachment and formatting happen through `openstack_compute_volume_attach_v2` and cloud-init, respectively.

## Migration workflow

1. **Export your Hetzner state.** Run `tofu show` to document current resources.
2. **Map server types to Quake AI flavors.** Compare vCPU/RAM specs between Hetzner server types and Quake AI flavors in the dashboard.
3. **Rewrite the provider block.** Replace `hcloud` with `openstack` and configure environment variable authentication.
4. **Translate resources.** Use the mapping table above. Start with a single server and security group.
5. **Set up data migration.** For volumes, use `rsync` or `scp` to copy data between instances. For S3-compatible storage, use `rclone`.
6. **Test with `tofu plan`.** Validate the configuration before applying.
7. **Apply and verify.** Deploy on Quake AI and confirm services are reachable.

For an existing `hcloud_load_balancer`, deploy the [Edge Reverse Proxy template](/resources/iac-templates/edge-reverse-proxy) and translate its services and targets into proxy routes that use backend private addresses. Use the [API Gateway template](/resources/iac-templates/api-gateway) when the workload needs API-specific routing and policy controls.

## Key differences from Hetzner

- **Authentication.** Hetzner uses a single API token. Quake AI uses OpenStack Keystone with username, password, project, and domain, set via environment variables.
- **Networking model.** Hetzner auto-assigns a public IPv4. Quake AI uses floating IPs that you explicitly associate with instances.
- **Firewall vs security group.** Hetzner firewalls are standalone resources applied to servers. Quake AI security groups are attached to ports/instances with individual rules as separate resources.
- **Object storage.** Hetzner does not offer S3-compatible storage. Quake AI does; see the [S3 Storage with ACLs template](/resources/iac-templates/s3-storage-acl).
- **Pricing model.** Both offer fixed monthly pricing. Compare per-resource costs in the Quake AI dashboard.

## See also

- [Migration Explorer](/resources/migration): interactive service comparison across cloud providers
- [IaC on Quake AI](/docs/automation/concepts/iac-comparison)
- [Simple VM template](/resources/iac-templates/simple-vm)
- [Infrastructure Templates](/resources/iac-templates)
- [How to get started with Infrastructure as Code on Quake AI](/docs/automation/how-to/getting-started-iac)
