# How to deploy a multi-tier application with Terraform

Source: https://docs.quake.ai/docs/automation/how-to/terraform-full-stack
Markdown: https://docs.quake.ai/docs/automation/how-to/terraform-full-stack.md

---

# 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.



Start from the [Full-Stack Application template](/resources/iac-templates/full-stack-app) for the private application and database tiers. Add the [Edge Reverse Proxy template](/resources/iac-templates/edge-reverse-proxy) for Caddy and instance-managed TLS, or use the [API Gateway template](/resources/iac-templates/api-gateway) for API routing and policy controls.



<PrerequisiteBlock methods={["terraform"]}>

- Familiarity with [Terraform on Quake AI](/docs/automation/concepts/terraform)
- 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

</PrerequisiteBlock>

## 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.

<Figure size="md" caption="A floating IP reaches a self-managed edge proxy, which routes to private application and database tiers">

```d2
direction: right

internet: Internet {shape: cloud}

cloud: Quake AI {
  edge: Edge proxy\nfloating IP
  apps: Application tier\nprivate addresses
  db: Database tier\nprivate address {shape: cylinder}
}

internet -> cloud.edge: HTTPS
cloud.edge -> cloud.apps: private route
cloud.apps -> cloud.db: database traffic
```

</Figure>

## Choose a starting template

Use the [Full-Stack Application template](/resources/iac-templates/full-stack-app) 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](/resources/iac-templates/three-tier-app) 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](/resources/iac-templates/edge-reverse-proxy) 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:

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

Use the [Edge WAF template](/resources/iac-templates/edge-waf) 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

- [Full-Stack Application template](/resources/iac-templates/full-stack-app)
- [Edge Reverse Proxy template](/resources/iac-templates/edge-reverse-proxy)
- [API Gateway template](/resources/iac-templates/api-gateway)
- [Edge WAF template](/resources/iac-templates/edge-waf)
- [How to parameterize a template with tfvars](/docs/automation/how-to/parameterize-template-tfvars)
- [How to manage OpenTofu state](/docs/automation/how-to/state-management)
