# Next.js App on Compute

Source: https://docs.quake.ai/resources/iac-templates/nextjs-app
Markdown: https://docs.quake.ai/resources/iac-templates/nextjs-app.md

---

# Next.js app on compute

This pattern composes Compute and Network.

A [validated OpenTofu template](/docs/platform/validation#how-infrastructure-templates-are-checked) for `quake.yaml` manifests with `runtime: container`, shaped for Next.js. It provisions a CPU instance with Docker, runs a prebuilt Next.js image behind a Caddy reverse proxy, and publishes it on a floating IP with automatic HTTPS when you supply a domain.

## What this template does

Provisions a single compute instance for a containerized Next.js app:

- Private network, subnet, router, and neutron port
- Security group allowing SSH, the public HTTP port, and HTTPS (443)
- Volume-backed boot disk
- Floating IP for public access
- Cloud-init installs Docker, runs the Next.js container with `PORT` set to `app_port` and any `container_env` variables, and runs a Caddy reverse proxy that terminates TLS

A Next.js deploy is `runtime: container` with a Node Dockerfile. This template is the Next.js-shaped option alongside the generic [containerized-app](/resources/iac-templates/containerized-app): it defaults `app_port` to 3000, adds the reverse proxy for TLS, and ships a documented standalone Dockerfile in the template's `docker/` directory.

## Parameters

| Parameter | Description | Default |
| --- | --- | --- |
| `key_name` | SSH keypair name (must already exist in your project) | No default |
| `container_image` | Prebuilt Next.js image to pull and run | `registry.example.com/nextjs-app:latest` |
| `flavor_name` | Instance size | `s1a.small` |
| `image_name` | Operating system image | `Ubuntu-24.04` |
| `app_name` | Resource name prefix | `nextjs-app` |
| `container_env` | Environment variables injected into the container | `{}` |
| `app_port` | Port the Next.js server listens on | `3000` |
| `host_port` | Public HTTP port the reverse proxy listens on | `80` |
| `domain` | Domain for automatic HTTPS (empty serves HTTP only) | `""` |
| `external_network` | External network for floating IP | `PublicStatic` |
| `private_cidr` | Private subnet CIDR | `10.10.10.0/24` |

## When to use this pattern

Deploy a single VM that runs a containerized Next.js app with a TLS-ready reverse proxy. Build the app with `output: "standalone"`, push the image to a registry, and set `container_image`. Put an [edge reverse proxy](/resources/iac-templates/edge-reverse-proxy) in front when you need a dedicated entry point for one or more app hosts.

This is a single-VM origin, not a global edge deploy. There is no native CDN; put a third-party CDN in front of the floating IP for static and ISR assets if you need edge caching.

## Estimated cost

<PricingCompanion
  components={[
    { kind: "template", slug: "nextjs-app", required: true },
  ]}
/>

## Template source

<TemplateSource slug="nextjs-app" />

<TemplateResourceMap template="nextjs-app" format="opentofu" />

## quake.yaml mapping

| Manifest field | Template parameter |
| --- | --- |
| `runtime: container` | Resolves to a container runtime template (`nextjs-app` or `containerized-app`) |
| `environments.production.resources: cpu-standard` | `flavor_name = "m2a.large"` |
| `environments.preview.resources: cpu-small` | `flavor_name = "s1a.small"` |
| `source.build: dockerfile` | Build the standalone image in CI, push to a registry, set `container_image` before apply |
| `secrets` and resolved `services` outputs | `container_env` map injected into the running container |

The launch handoff plan populates `container_env` from `quake.yaml` secret names (you supply the values) and from the outputs of service templates applied earlier in the plan, such as a Postgres `DATABASE_URL`. For the POC, `container_env` values pass as `docker run -e` flags, which are visible in the instance process list. Source production secrets from a secret store.

## Outputs

| Output | Description |
| --- | --- |
| `floating_ip` | Public floating IP assigned to the instance |
| `private_ip` | Private IP address of the instance |
| `app_url` | HTTPS on the domain when set, otherwise HTTP on the floating IP and host port |
| `instance_id` | Compute instance ID |

Read `app_url` after apply to verify the deployment. For HTTPS, set `domain` and point its DNS A record at `floating_ip` before apply so Caddy can obtain a certificate.

## Customize this pattern

- [Customize a template's image and flavor](/docs/automation/how-to/customize-template-image-flavor)
- [Add a block volume to a template](/docs/automation/how-to/add-volume-to-template)
- [Parameterize a template with a tfvars file](/docs/automation/how-to/parameterize-template-tfvars)

## See also

- [Containerized app template](/resources/iac-templates/containerized-app)
- [Simple VM with Floating IP](/resources/iac-templates/simple-vm)
