# Deploy a regional edge cache with the edge-cache template

Source: https://docs.quake.ai/resources/deployments/deploy-edge-cache-template
Markdown: https://docs.quake.ai/resources/deployments/deploy-edge-cache-template.md

---

# Deploy a regional edge cache with the edge-cache template

Stand up a [Varnish Cache](https://varnish-cache.org) HTTP accelerator on one Quake AI instance using the [validated OpenTofu template](/docs/platform/validation#how-infrastructure-templates-are-checked) `edge-cache`. You apply the template, launch a private origin on the same subnet, confirm a cache miss reaches the origin and a repeat request is a cache hit, purge a path from localhost, lock the origin security group to the cache, and optionally point a domain for automatic TLS.

The cache holds the only public floating IP. The origin stays on the private subnet with no address on the internet. You operate both instances yourself; this is a regional HTTP cache you run, not a global PoP network.

<Figure size="md" caption="What you'll build: Varnish on a cache instance with a floating IP stores cacheable responses from a private origin on the same subnet">

```d2
direction: right

client: Browser {shape: person}
fip: Floating IP\n80 / 443
cache: Cache VM\nVarnish {
  store: file-backed cache\n/data/varnish
}
origin: Origin VM\nno floating IP {
  app: App :8080
}

client -> fip: HTTP
fip -> cache.store
cache.store -> origin.app: private subnet only
```

</Figure>

<PricingCompanion
  components={[
    { kind: "template", slug: "edge-cache", required: true },
    { kind: "primitive", required: true, label: "Private origin instance", vm: { flavor: "s1a.small", count: 1 } },
  ]}
/>

## Prerequisites

You need:

- OpenTofu 1.6.0 or later (or Terraform 1.6.0 or later) installed locally.
- Your OpenStack credentials sourced into the shell (`source openrc.sh`). See [the OpenStack CLI guide](/docs/tools/openstack-cli).
- An SSH keypair that already exists in your project. Record its name for the `key_name` variable.
- A copy of the `edge-cache` template directory from [the template reference page](/resources/iac-templates/edge-cache).
- A domain you can point at the cache floating IP when you enable TLS (optional for the HTTP verification steps below).

## Step 1: Apply the cache template

Copy the template's example variables file and set `key_name`. Leave `domain` empty so Varnish serves plain HTTP on port 80 while you stand up the origin. Keep the default `upstream_host` and `upstream_port` unless you plan a different private address:

```bash
cp terraform.tfvars.example terraform.tfvars
```

```hcl
key_name       = "YOUR_KEY_NAME"
upstream_host  = "10.42.0.10"
upstream_port  = 8080
```

Initialize, preview, and apply:

```bash
tofu init
tofu plan
tofu apply
```

OpenTofu provisions a private network, router, security group, data volume, cache instance, and floating IP. cloud-init installs Docker, mounts the data volume at `/data`, writes a VCL file that honors origin `Cache-Control` headers, and starts Varnish on first boot.

Record the outputs:

```bash
tofu output floating_ip
tofu output private_ip
tofu output cache_url
```

Wait two to three minutes for cloud-init to finish before you test the cache.

## Step 2: Launch a private origin on the cache network

The template creates network `edge-cache-net` and subnet `edge-cache-subnet` (names follow the default `app_name`). Launch a small origin instance on that subnet at the fixed address `10.42.0.10` with no floating IP.

1. Create a security group for the origin:

```bash
openstack security group create app-origin-sg \
  --description "Private app origin; ingress from cache only after step 6"
```

Allow SSH from your workstation while you configure the origin:

```bash
openstack security group rule create \
  --protocol tcp --dst-port 22 --remote-ip YOUR_IP/32 app-origin-sg
```

2. Create a port with the fixed private IP:

```bash
SUBNET_ID=$(openstack subnet list -f value -c ID -c Name | awk '/edge-cache-subnet/ {print $1}')

openstack port create \
  --network edge-cache-net \
  --fixed-ip subnet=$SUBNET_ID,ip-address=10.42.0.10 \
  --security-group app-origin-sg \
  app-origin-port
```

3. Write cloud-init that serves a cacheable test page on port 8080 with an explicit `Cache-Control` header:

```bash
cat > origin-cloud-init.yaml <<'EOF'
#cloud-config
package_update: true
packages:
  - nginx
runcmd:
  - |
    echo 'origin ok' > /var/www/html/index.html
    printf '%s\n' \
      'server {' \
      '  listen 8080;' \
      '  root /var/www/html;' \
      '  add_header Cache-Control "public, max-age=3600";' \
      '}' > /etc/nginx/sites-available/default
    systemctl restart nginx
EOF
```

4. Launch the origin instance on the port:

```bash
openstack server create \
  --flavor s1a.small \
  --image Ubuntu-24.04 \
  --key-name YOUR_KEY_NAME \
  --port app-origin-port \
  --user-data origin-cloud-init.yaml \
  app-origin
```

Wait until the instance reports `ACTIVE`, then confirm the origin answers on the private subnet from the cache host:

```bash
ssh ubuntu@YOUR_FLOATING_IP 'curl -sI http://10.42.0.10:8080/ | tr -d "\r" | grep -i cache-control'
```

The response should include `Cache-Control: public, max-age=3600`.

## Step 3: Verify traffic through the cache

From your workstation, request the cache floating IP over HTTP:

```bash
curl -s http://YOUR_FLOATING_IP/
```

The response body should include `origin ok`. The request path is client to cache floating IP to Varnish to private origin.

If Varnish returns `503` or `502`, cloud-init may still be running on the cache or origin. Wait and retry. SSH to the cache host and run `docker compose -f /opt/edge-cache/docker-compose.yml ps` to confirm Varnish is up.

## Step 4: Confirm a cache miss, then a cache hit

Varnish adds an `Age` header and an `X-Varnish` header on every response. On the first request for a URL, `Age` is `0` and `X-Varnish` carries one ID (a miss). On a repeat request while the object is still fresh, `Age` is greater than zero and `X-Varnish` carries two space-separated IDs (a hit).

Send two requests and inspect the headers:

```bash
curl -sI http://YOUR_FLOATING_IP/ | tr -d '\r' | grep -iE '^(age|x-varnish):'
curl -sI http://YOUR_FLOATING_IP/ | tr -d '\r' | grep -iE '^(age|x-varnish):'
```

The first line should show `Age: 0` (or a low value) and one `X-Varnish` ID. The second line should show a higher `Age` and two IDs in `X-Varnish`, which confirms Varnish served the response from its file-backed store at `/data/varnish` instead of contacting the origin again.

## Step 5: Purge a cached path from localhost

The default VCL allows `PURGE` only from localhost on the cache instance. SSH to the cache host and purge the root path:

```bash
ssh ubuntu@YOUR_FLOATING_IP 'curl -sI -X PURGE http://127.0.0.1/ | tr -d "\r" | head -5'
```

The response status should be `200`. A `PURGE` from your workstation is blocked:

```bash
curl -sI -X PURGE http://YOUR_FLOATING_IP/ | tr -d '\r' | grep -i '^HTTP'
```

The status should be `405`.

After the purge, the next client request is a miss again:

```bash
curl -sI http://YOUR_FLOATING_IP/ | tr -d '\r' | grep -iE '^(age|x-varnish):'
```

`Age` should be `0` and `X-Varnish` should carry one ID.

## Step 6: Lock the origin security group to the cache

Restrict the origin so it accepts application traffic only from the cache security group `edge-cache-sg`. Remove any rule that opens the application port to `0.0.0.0/0` if you added one during testing.

```bash
openstack security group rule create \
  --protocol tcp \
  --dst-port 8080 \
  --remote-group edge-cache-sg \
  app-origin-sg
```

List the origin rules and confirm port 8080 allows only the cache group:

```bash
openstack security group rule list app-origin-sg -f table
```

The origin has no floating IP, so it is not reachable directly from the internet. Only the cache can forward traffic to it on the private subnet.

Verify cached traffic still passes through the cache:

```bash
curl -s http://YOUR_FLOATING_IP/
```

The response should still include `origin ok`.

## Step 7: Point a domain and enable HTTPS (optional)

Skip this step if HTTP on the floating IP is enough for your test. To enable automatic TLS with Caddy in front of Varnish:

1. Create a DNS **A record** for your domain (for example `cache.example.com`) pointing at `YOUR_FLOATING_IP`. Follow [How to point a domain at a Quake AI resource](/docs/network/how-to/point-domain-to-quake-ai). Wait until it resolves:

```bash
dig +short cache.example.com
```

2. Set `domain` in `terraform.tfvars` and re-apply:

```hcl
domain = "cache.example.com"
```

```bash
tofu apply
```

cloud-init switches to the HTTPS branch: Caddy terminates TLS on ports 80 and 443 and forwards to Varnish on port 6081. Wait two to three minutes for Caddy to obtain a Let's Encrypt certificate.

3. Confirm HTTPS end to end:

```bash
curl -sI https://cache.example.com/ | tr -d '\r' | grep -iE '^(HTTP|age|x-varnish):'
```

The status should be `200` and repeat requests should still show cache hits via `Age` and `X-Varnish`.

For certificate background, see [How to issue and auto-renew a TLS certificate with Let's Encrypt](/docs/network/how-to/lets-encrypt-certificate).

## What you built

- **Applied the `edge-cache` template** to provision a private network, cache security group, data volume, Varnish host, and floating IP
- **Launched a private origin** on the same subnet at `10.42.0.10` with cacheable `Cache-Control` headers and no public address
- **Confirmed cache miss and cache hit behavior** by inspecting `Age` and `X-Varnish` response headers
- **Purged a cached path** with `PURGE` from localhost on the cache instance
- **Locked the origin security group** so application traffic accepts only the cache as its source

## Scope of this deployment

This cache runs in one region on a VM you operate. It stores cacheable HTTP responses on a block volume you size with `cache_size` and honors origin `Cache-Control` policy. It is not a global PoP network: Quake AI has no anycast, no global PoPs, and no first-party CDN. For geographic distribution and volumetric DDoS absorption at the network edge, [front the origin with a third-party CDN](/docs/network/how-to/front-with-cdn).

The walkthrough adds a second small instance for the origin; that VM is not part of the OpenTofu template. Size the cache with the template defaults; scale the origin independently for your application.

## Next steps

- [Edge cache template](/resources/iac-templates/edge-cache): parameters, VCL behavior, ports, and resource map
- [Front with a CDN](/docs/network/how-to/front-with-cdn): cache static assets and absorb edge traffic geographically
- [Edge reverse proxy template](/resources/iac-templates/edge-reverse-proxy): TLS termination without a dedicated cache layer
- [Edge WAF template](/resources/iac-templates/edge-waf): L7 inspection in front of a private origin
- [Security hardening checklist](/docs/security/hardening-checklist): audit security groups and floating IP usage

## Clean up

When you no longer need the deployment, destroy the OpenTofu stack and delete the origin resources you created in step 2:

```bash
tofu destroy
openstack server delete app-origin
openstack port delete app-origin-port
openstack security group delete app-origin-sg
```

Remove any DNS A record you pointed at the cache floating IP.
