Skip to content

How to deploy a multi-tier application with Terraform

How-to

Coming from another cloud?

▸AWS·EC2 Instances

EC2 Instanceshigh

  • Uses EC2 RunInstances API instead of Nova servers.create.
  • Requires predefined instance type selection.
  • Supports per-second On-Demand billing and Spot/Reserved options.
  • Includes hibernation state not standard in OpenStack.
AWS docs ↗
▸Azure·Virtual Machines

Virtual Machineshigh

  • Uses Azure Resource Manager (ARM) REST API at /providers/Microsoft.Compute/virtualMachines instead of OpenStack Nova API at /v2.1/servers.
  • Tightly integrated with Azure services like Azure Active Directory for authentication, unlike OpenStack's Keystone.
  • VM creation requires specifying size from predefined series with hardware-specific features (e.g., AMD/Intel/ARM), not custom flavor configs.
  • Billed per second with complex pricing tiers based on series/reservation options, vs OpenStack's typically hourly or usage-based.
Azure docs ↗
▸DigitalOcean·Droplets

Dropletshigh

  • API surface is DigitalOcean’s proprietary REST/CLI/Terraform tooling rather than OpenStack Nova/Neutron/Glance APIs (Droplets are managed via DigitalOcean UI/CLI/API/Terraform).
  • Billing is usage-based with per-second billing (60-second minimum and monthly cap) rather than the typical per-hour, quota-based charge model users often see in OpenStack-based clouds.
  • Droplets include a bundled outbound transfer allowance with each plan (starting at 500 GiB/month) rather than a separate bandwidth quota/metering model users often encounter in OpenStack deployments.
  • Droplets are described as Linux-based VMs on virtualized hardware with local SSD storage, whereas OpenStack deployments commonly expose distinct block storage (Cinder) and image services (Glance) and may not bundle bandwidth/monitoring/firewalls into the instance offering.
DigitalOcean docs ↗
▸Google Cloud·VM instances

VM instanceshigh

  • Uses REST API 'instances.insert' instead of Nova 'servers.create' with different auth via service accounts vs Keystone.
  • Supports bare metal instances (no hypervisor), not in standard OpenStack Nova.
  • Network interfaces tied to VPC subnets; differs from Neutron ports/floating IPs.
Google Cloud docs ↗
▸Hetzner·Cloud Servers

Cloud Servershigh

  • Servers provisioned individually via Hetzner API (hcloud), not OpenStack Nova flavors; fixed instance types like CX11 (1 vCPU, 2GB RAM, 20GB NVMe).
  • No flavor customization; choose from predefined shared/dedicated vCPU series.
  • Billing hourly with monthly cap per server (e.g., €3.29/mo cap for CX11), charged even when powered off until deleted, unlike typical OpenStack stop-to-pause billing.
  • Custom REST API at api.hetzner.cloud/v1/servers instead of OpenStack Nova /v2.1/servers (different auth, payloads, response formats).
Hetzner docs ↗

How to deploy a multi-tier application with Terraform

Deploy a public edge proxy, private application instances, and a private database instance with OpenTofu or Terraform. The edge proxy owns the floating IP and routes requests to the application tier over the private network.

Prerequisites

Windows: CLI examples use bash. Set up a Linux CLI environment on Windows before proceeding.

  • Familiarity with Terraform on Quake AI
  • An SSH key pair in your project
  • A domain name if the edge proxy should obtain and renew a TLS certificate
  • Enough quota for the instances, volumes, router gateway, and edge floating IP

Plan the topology#

Use three network roles:

  1. Edge: A Caddy, Nginx, HAProxy, or API gateway instance with one floating IP. Its security group accepts ports 80 and 443.
  2. Application: One or more private instances. Their security group accepts application traffic from the edge instance's private address.
  3. Database: A private instance with an attached data volume. Its security group accepts the database port from the application subnet.
InternetQuake AIEdge proxyfloating IPApplication tierprivate addressesDatabase tierprivate address HTTPSprivate routedatabase traffic
Click to zoom
A floating IP reaches a self-managed edge proxy, which routes to private application and database tiers

Choose a starting template#

Use the Full-Stack Application template when the deployment needs web, application, and database roles on one private network. Configure its public web instance as the edge proxy and route it to the app_private_ip output.

Use the Three-Tier Application template when each tier needs a separate subnet. Configure the public web tier as the reverse proxy and keep the application and database tiers private.

Use the Edge Reverse Proxy template when the private application network already exists. Adapt its network data sources and upstream_host value to reference that existing network and the application's private port. This avoids creating a second, disconnected private network.

Keep the backend address explicit so the edge configuration depends on a stable private port rather than an instance name lookup. If the application configuration exposes several private addresses, render those addresses into the proxy configuration with templatefile().

Restrict network access#

Attach security groups to ports by ID. The edge port accepts public HTTP and HTTPS traffic. Application ports accept traffic from the edge proxy's fixed private address or security group. Database ports accept traffic from the application subnet only.

HCL
resource "openstack_networking_secgroup_rule_v2" "edge_https" {
  security_group_id = openstack_networking_secgroup_v2.edge.id
  direction         = "ingress"
  ethertype         = "IPv4"
  protocol          = "tcp"
  port_range_min    = 443
  port_range_max    = 443
  remote_ip_prefix  = "0.0.0.0/0"
}

resource "openstack_networking_secgroup_rule_v2" "app_from_edge" {
  security_group_id = openstack_networking_secgroup_v2.app.id
  direction         = "ingress"
  ethertype         = "IPv4"
  protocol          = "tcp"
  port_range_min    = var.application_port
  port_range_max    = var.application_port
  remote_ip_prefix  = "${openstack_networking_port_v2.edge.all_fixed_ips[0]}/32"
}

Do not expose application or database ports to 0.0.0.0/0. Keep SSH access behind a bastion, VPN, or source-restricted rule.

Configure TLS and routing#

The edge instance terminates TLS and stores certificate state on its attached volume. Point the domain's DNS A record at the edge floating IP before Caddy requests the certificate.

For several private backends, generate a Caddy upstream list:

app.example.com {
  reverse_proxy 10.42.0.20:8080 10.42.0.21:8080 {
    health_uri /health
  }
}

Use the Edge WAF template when the public origin needs request filtering. Use an external CDN or WAF when your architecture already depends on one, and configure its origin to use the edge floating IP.

Plan and apply#

Initialize the root module and review the dependency graph:

bash
tofu init
tofu plan -out=multi-tier.tfplan
tofu apply multi-tier.tfplan

Review replacements, quota usage, and security group changes in the plan before you apply it.

Verify the deployment#

Read the edge address and private tier addresses from the outputs:

bash
tofu output

Confirm the public endpoint responds:

bash
curl --fail --show-error --head "https://YOUR_DOMAIN"

Confirm the application and database instances have no floating IPs:

bash
openstack server list -c Name -c Networks

From the edge instance, request the application health endpoint over its private address. From an application instance, connect to the database port over the private network. These checks verify each permitted path without opening private tiers to the internet.

See also#

Usage Guidelines

The sample code, software libraries, command line tools, proofs of concept, templates, and other related technology on this page (including any of the foregoing that is provided by Quake AI personnel) is provided to you as Quake AI Content under the Quake AI Customer Agreement, or the relevant written agreement between you and Quake AI (whichever applies). Do not use this Quake AI Content in your production accounts, or on production or other critical data. You are responsible for testing, securing, and optimizing the Quake AI Content (such as sample code) as appropriate for production grade use based on your specific quality control practices and standards. Deploying Quake AI Content may incur Quake AI charges for creating or using Quake AI chargeable resources, such as running Compute instances or storing data in Object Storage. Your use is also subject to the Acceptable Use Policy.

For the full policy, see Usage Guidelines.

Was this page helpful?