# Deploy n8n with the n8n-workflow template

Source: https://docs.quake.ai/resources/deployments/deploy-n8n-workflow-template
Markdown: https://docs.quake.ai/resources/deployments/deploy-n8n-workflow-template.md

---

# Deploy n8n with the n8n-workflow template

Stand up [n8n](https://n8n.io), an open-source workflow-automation tool, on a single Quake AI instance using the [validated OpenTofu template](/docs/platform/validation#how-infrastructure-templates-are-checked) `n8n-workflow`. You apply the template, reach the editor over the floating IP, create the owner account, put a reverse proxy in front so the editor runs over HTTPS, build a webhook-triggered workflow, and activate it.

n8n is the glue layer that wires together the services a project depends on. You run it yourself; this is a self-hosted tool you operate, not a managed service.

<Figure size="md" caption="What you'll build: an n8n host on a single instance, reached over HTTPS through a Caddy reverse proxy, running a webhook-triggered workflow">

```d2
direction: right

dev: You {shape: person}
caller: Webhook caller {shape: person}
domain: Your domain\n(DNS A record)
fip: Floating IP
instance: Ubuntu instance {
  caddy: Caddy\nreverse proxy
  n8n: n8n\neditor + engine
  caddy -> n8n: proxies 443 to 5678
}

dev -> domain: HTTPS editor
caller -> domain: POST /webhook
domain -> fip
fip -> instance.caddy
```

</Figure>

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

## 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 `n8n-workflow` template directory from [the template reference page](/resources/iac-templates/n8n-workflow).
- Your workstation's public IP address, so you can open the editor port to it for first-boot setup. Find it with `curl -sS https://api.ipify.org`.

A domain is optional for first boot. You add it in step 3 to serve the editor over HTTPS.

## Step 1: Set the variables and apply the template

The editor listens on port 5678 over plain HTTP. The template's security group restricts port 5678 to `editor_allowed_cidr`, which defaults to the private network only, so the raw editor stays off the public internet. To reach the editor from your workstation for first-boot setup, set `editor_allowed_cidr` to your own address.

Copy the template's example variables file and open it:

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

Set `key_name` to the SSH keypair already in your project, and `editor_allowed_cidr` to your workstation's public IP with a `/32` suffix:

```hcl
key_name            = "YOUR_KEY_NAME"
editor_allowed_cidr = "YOUR_IP/32"
```



If you would rather not expose port 5678 at all, leave `editor_allowed_cidr` at its default and reach the editor over an SSH tunnel instead: `ssh -L 5678:localhost:5678 ubuntu@YOUR_FLOATING_IP`, then open `http://localhost:5678`. Once you add a domain in step 3, Caddy serves the editor over HTTPS on port 443 and you no longer need port 5678 open.



Initialize the working directory, preview the plan, and apply:

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

OpenTofu provisions a private network, a router, a security group, a block volume mounted at `/var/lib/docker`, an instance, and a floating IP. On first boot, cloud-init mounts the data volume, installs Docker Engine, and starts n8n from a compose file on port 5678.

When the apply finishes, read the outputs:

```bash
tofu output
```

Record `floating_ip` and `editor_url`.

## Step 2: Create the owner account

n8n does not ship a default password. You create the owner account the first time you open the editor, and n8n generates its encryption key on first start and persists it on the data volume.

cloud-init takes a minute or two after the instance reaches `ACTIVE`. Open `editor_url` (for example `http://YOUR_FLOATING_IP:5678`) in your browser. If the page does not load yet, wait and retry; you can watch the container start over SSH:

```bash
ssh ubuntu@YOUR_FLOATING_IP "sudo docker ps --filter name=n8n"
```

When the setup screen appears, enter an email, a name, and a strong password to create the owner account. This account administers the n8n instance. n8n signs you in and opens an empty workflow canvas.



Until you attach a domain in step 3, the editor is served over unencrypted HTTP on port 5678, reachable only from `editor_allowed_cidr`. Avoid sending production credentials over it from a shared or public network. Adding a domain (step 3) moves the editor to HTTPS on port 443.



## Step 3: Serve the editor over HTTPS with Caddy

The template leaves ports 80 and 443 open for a reverse proxy. [Caddy](https://caddyserver.com) obtains and renews a TLS certificate automatically once a domain resolves to the instance.

1. Create a DNS **A record** for your domain (for example `n8n.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 the record resolves:

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

The command returns your floating IP once the record propagates.

2. SSH to the instance and add a Caddy service that proxies HTTPS to n8n on port 5678. Create `/opt/n8n/Caddyfile`:

```text
n8n.example.com {
  reverse_proxy 127.0.0.1:5678
}
```

3. Add Caddy to the compose file at `/opt/n8n/docker-compose.yml` so it runs alongside n8n:

```yaml
services:
  caddy:
    image: caddy:2
    restart: unless-stopped
    network_mode: host
    volumes:
      - /opt/n8n/Caddyfile:/etc/caddy/Caddyfile
      - caddy_data:/data
volumes:
  caddy_data:
```

4. Tell n8n its public host so webhook URLs use HTTPS. Edit `/opt/n8n/.env` and set:

```text
N8N_HOST=n8n.example.com
N8N_PROTOCOL=https
WEBHOOK_URL=https://n8n.example.com/
```

5. Apply the changes and confirm both containers run:

```bash
cd /opt/n8n
sudo docker compose up -d
sudo docker compose ps
```

Open `https://n8n.example.com` and confirm the padlock. For background on certificate issuance and renewal, see [How to issue and auto-renew a TLS certificate with Let's Encrypt](/docs/network/how-to/lets-encrypt-certificate). Once HTTPS works, close direct access to port 5678 by setting `editor_allowed_cidr` back to the private network in `terraform.tfvars` and running `tofu apply`.

## Step 4: Build and activate a webhook-triggered workflow

You build a workflow that accepts an HTTP POST, transforms the payload, and returns a JSON response. This is the shape most integrations take: a service calls n8n, n8n does work, n8n answers.

1. On the workflow canvas, select **Add first step** > **On webhook call**. n8n adds a **Webhook** trigger node.
2. Open the node. Set **HTTP Method** to `POST` and **Path** to `order-received`. Copy the **Test URL** shown in the node; you call it in a moment.
3. Add a **Set** node after the Webhook node. Add a field named `status` with the value `accepted`, and a field named `order` mapped from the incoming body (for example `{{ $json.body.order_id }}`).
4. Add a **Respond to Webhook** node after the Set node. Set **Respond With** to `JSON` and the body to the output of the Set node.
5. Select **Test workflow**, then send a request to the test URL from your workstation:

```bash
curl -X POST "https://n8n.example.com/webhook-test/order-received" \
  -H "Content-Type: application/json" \
  -d '{"order_id": "1234"}'
```

n8n runs the workflow and returns:

```json
{ "status": "accepted", "order": "1234" }
```

6. Select **Save**, then toggle the workflow to **Active**. n8n now serves the production webhook at `https://n8n.example.com/webhook/order-received` and runs the workflow on every call.



Swap the **Respond to Webhook** node for an **HTTP Request** node to forward the payload to another service, or add a **Postgres** node to write a row. The Postgres node needs the `postgres` datastore mode: set `db_type = "postgres"` and the `postgres_*` variables when you apply the template, then add `DB_POSTGRESDB_PASSWORD` to `/opt/n8n/.env`. See the [self-managed PostgreSQL template](/resources/iac-templates/self-managed-postgres) for a datastore to point at.



## What you built

- **Applied the `n8n-workflow` template** to provision a network, security group, data volume, instance, and floating IP, and let cloud-init install Docker and start n8n
- **Created the owner account** on the editor's first-boot screen
- **Served the editor over HTTPS** by pointing a domain at the floating IP and routing it through a Caddy reverse proxy
- **Built and activated a webhook-triggered workflow** that accepts a POST, transforms it, and returns JSON

## Scope of this deployment

This template runs a single-VM n8n host, not a managed automation cloud. The instance is CPU-only and runs in one region. You operate the instance, Docker, n8n, and the data volume yourself: back them up, patch them, and watch resource use as workflow volume grows. n8n stores its encryption key on the data volume, so snapshot the volume before you resize or rebuild the host. For high throughput or long-running workflows, move to the Postgres and queue mode and size the instance up.

## Next steps

- [n8n workflow template](/resources/iac-templates/n8n-workflow): the template reference, parameters, and resource map
- [self-managed PostgreSQL template](/resources/iac-templates/self-managed-postgres): the datastore to point at for production and queue mode
- [Deploy Coolify with the coolify-host template](/resources/deployments/deploy-coolify-host-template): run a deploy platform alongside your automation
- [How to store application secrets and inject them at runtime](/docs/security/how-to/inject-app-secrets): move webhook and API credentials out of plain environment variables
- [Security hardening checklist](/docs/security/hardening-checklist): tighten SSH access and exposure before you serve real traffic

## Clean up

When you no longer need the deployment, destroy everything the template created:

```bash
tofu destroy
```

Then remove the DNS A record you created in step 3. Because n8n, its workflows, and the encryption key all live on the instance and its attached volume, `tofu destroy` removes them along with the infrastructure. Export any workflows you want to keep before you destroy.
