How to deploy a multi-tier application with Terraform
Coming from another cloud?
▸AWS·EC2 Instances
EC2 Instances
- 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.
▸Azure·Virtual Machines
Virtual Machines
- 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.
▸DigitalOcean·Droplets
Droplets
- 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.
▸Google Cloud·VM instances
VM instances
- 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.
▸Hetzner·Cloud Servers
Cloud Servers
- 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).
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
- TerraformOpenTofu installed with Quake AI provider configured
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:
- Edge: A Caddy, Nginx, HAProxy, or API gateway instance with one floating IP. Its security group accepts ports 80 and 443.
- Application: One or more private instances. Their security group accepts application traffic from the edge instance's private address.
- Database: A private instance with an attached data volume. Its security group accepts the database port from the application subnet.
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.
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:
tofu init
tofu plan -out=multi-tier.tfplan
tofu apply multi-tier.tfplanReview 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:
tofu outputConfirm the public endpoint responds:
curl --fail --show-error --head "https://YOUR_DOMAIN"Confirm the application and database instances have no floating IPs:
openstack server list -c Name -c NetworksFrom 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.