Build visual AI workflows with n8n on Civo Kubernetes

Deploy n8n on Civo Kubernetes and ship a working automation that receives incoming webhook data, summarizes it with AI, and posts the result to Slack.

16 minutes reading time

Written by

Mostafa Ibrahim
Mostafa Ibrahim

Software Engineer at GoCardless

Every team has a version of the same problem where something comes in, a support ticket, an API event, a form submission, and needs to be read, summarized, and handed to the right person before it gets buried.

Most teams can describe exactly what they want: an automated layer that reads incoming events, pulls out what matters, and routes the result to whoever needs to act on it. The idea is straightforward. The infrastructure required to run it reliably is not. You need a host, a deployment pipeline, and a runtime that stays up under load. That gap between the idea and the working system is where most automation projects stall.

n8n removes that gap. It's a self-hosted workflow builder where you connect nodes on a canvas in the browser instead of writing code. The same flow you would have written across multiple files becomes a single visual pipeline you can see, adjust, and hand off to someone else on your team. It has over 100,000 GitHub stars and hundreds of built-in integrations, which means most of what you need is already available as a node you can drag onto the canvas.

Running it still requires a stack, though:

  • Somewhere to host n8n itself
  • A database to store workflow state and credentials
  • A model to read and summarize your incoming data

Civo gives you all three under one project. Managed Kubernetes for n8n, managed PostgreSQL for state, and relaxAI for inference, all in one account with one bill, and every component is something you can inspect and manage yourself.

What you'll build

You’ll build a webhook that takes incoming data, summarizes it with AI, and sends the result straight to Slack. 

Here is the flow:

  • A service sends data to your n8n webhook URL.
  • n8n receives the payload and forwards it to an AI agent.
  • relaxAI processes the input and returns a concise 3-bullet summary plus a recommended next action.
  • n8n then posts the formatted result directly to Slack.

Core stack:

  • n8n: Orchestrates the workflow end-to-end.
  • relaxAI: Generates summaries and action insights.
  • Civo Kubernetes + PostgreSQL: Hosts the system and manages persistent data.

Outcome: A live endpoint that turns incoming data into instant, actionable Slack updates.

Why use Civo for this project?

Hosting this stack on Civo keeps your entire automation pipeline centralized and easy to manage.

  • Unified infrastructure: n8n runs on Civo Kubernetes, and PostgreSQL handles all persistent data in one managed setup.
  • Reliable state management: Workflows, credentials, and configurations persist even if containers restart.
  • Fast deployment: Helm and standard Kubernetes tools let you spin everything up quickly.
  • Cost control: No per-user or per-execution SaaS pricing, just predictable infrastructure costs.
  • Scalable setup: You can expand resources easily as your automation needs grow.

Civo provides a simple, stable foundation for building and running internal automation systems end-to-end.

Architecture overview

Every request follows the same path through the stack.

  • A webhook source sends a POST request to a public URL
  • The Civo LoadBalancer receives it and routes it into the cluster
  • The n8n pod picks it up, processes the payload, and orchestrates what happens next
  • relaxAI summarizes the content and returns the result to n8n
  • n8n writes the workflow state to managed PostgreSQL and posts the summary to Slack
The full request path, from incoming POST to Slack message, and where each piece of the stack lives.

The full request path, from incoming POST to Slack message, and where each piece of the stack lives. 

PostgreSQL and relaxAI are both managed by Civo and sit outside the cluster, which means the only thing actually running inside Kubernetes is the n8n pod. If that pod goes down, or the cluster itself restarts, Kubernetes brings n8n back up automatically, and everything you care about, your workflow definitions, credentials, and execution history, is still there untouched because it lives in the external database and was never inside the cluster to begin with. 

Prerequisites

You'll need:

Civo account:

  • A Civo account with access to Kubernetes and Managed Databases 

Local tools:

  • Kubectl installed 
  • Helm 3 installed 
  • psql (PostgreSQL client) installed 
  • A terminal (macOS Terminal, Windows Terminal with WSL, or any Linux shell)

Windows users: PowerShell has a built-in `curl` alias that behaves differently from the real curl. Use curl.exe instead of curl for all commands in this tutorial.

API access:

Slack:

  • A Slack workspace where you have permission to create and install apps

Provision Civo infrastructure

We're doing everything through the Civo dashboard in this section. CLI comes later.

4.1 Create the Kubernetes Cluster

Open the Civo dashboard, go to Kubernetes, and hit Create Cluster.

  • Name: n8n-cluster
  • Number of nodes: 1
  • Node size: Small
  • Region: Pick the region geographically nearest to your users or your target audience

Default network and firewall settings are fine. Click Create Cluster. It'll be ready in about 90 seconds. Civo runs k3s under the hood, which is why provisioning is that fast. 

Grab the kubeconfig file from the cluster detail page once it shows Active. A kubeconfig is just a file with the credentials and endpoint `kubectl` needs to talk to your cluster. Point your terminal at it:

macOS/Linux:

export KUBECONFIG=~/Downloads/civo-n8n-cluster-kubeconfig
kubectl get nodes

Windows PowerShell:

$env:KUBECONFIG="C:\Users\YourUsername\Downloads\civo-n8n-cluster-kubeconfig"
kubectl get nodes

One node, `Ready`:

One node, `Ready`

Terminal output showing one cluster node in Ready status after pointing kubectl at the downloaded kubeconfig file.


If you close your terminal and kubectl stops responding with a connection error, the KUBECONFIG variable has been reset. Run the export command again before continuing.

4.2 Create the managed PostgreSQL instance

Still in the dashboard. Go to Databases, click Create Database.

  • Engine: PostgreSQL 17
  • Name: n8n-db
  • Node size: Smallest available (n8n's database usage is minimal)

When it's up, copy the host, port, username, and password from the connection details panel. You'll need them in a minute.

The Civo Managed PostgreSQL creation screen showing n8n-db in Ready status with connection details visible.

The Civo Managed PostgreSQL creation screen showing n8n-db in Ready status with connection details visible. 

4.3 Create the n8n database and user

Connect from your terminal with the credentials you just copied:

psql "postgresql://USERNAME:PASSWORD@HOST:PORT/postgres?sslmode=require"

If the connection fails with an SSL error, try replacing sslmode=require with to test connectivity first.

Civo's managed PostgreSQL uses postgres as the default database name, not defaultdb.

Create a database and a dedicated user for n8n:

CREATE DATABASE n8n;
CREATE USER n8n_user WITH PASSWORD 'your-strong-password';
GRANT ALL PRIVILEGES ON DATABASE n8n TO n8n_user;
GRANT ALL ON SCHEMA public TO n8n_user;
GRANT CREATE ON SCHEMA public TO n8n_user;
GRANT n8n_user TO civo;

Pick a real password, not your-strong-password. Type \q to exit.

4.4 Create the namespace and Kubernetes secrets

In your terminal (make sure KUBECONFIG is still set). Create a namespace, which is just a way to keep n8n's resources grouped separately from anything else on the cluster:

kubectl create namespace n8n

You need two Kubernetes Secrets. A Secret is a Kubernetes object that holds sensitive data (passwords, API keys) so you don't hardcode them in config files or check them into git.

Database credentials first.

macOS/Linux:

kubectl create secret generic n8n-db-credentials \
--namespace n8n \
--from-literal=DB_POSTGRESDB_HOST=YOUR_PG_HOST \
--from-literal=DB_POSTGRESDB_PORT=YOUR_PG_PORT \
--from-literal=DB_POSTGRESDB_DATABASE=n8n \
--from-literal=DB_POSTGRESDB_USER=n8n_user \
--from-literal=DB_POSTGRESDB_PASSWORD=your-strong-password

Windows PowerShell:

kubectl create secret generic n8n-db-credentials `
--namespace n8n `
--from-literal=DB_POSTGRESDB_HOST=YOUR_PG_HOST `
--from-literal=DB_POSTGRESDB_PORT=YOUR_PG_PORT `
--from-literal=DB_POSTGRESDB_DATABASE=n8n `
--from-literal=DB_POSTGRESDB_USER=n8n_user `
--from-literal=DB_POSTGRESDB_PASSWORD=your-strong-password

Then the encryption key. n8n encrypts every credential it stores (API keys, tokens, OAuth secrets) using this key. Save the key value somewhere safe before running. If you lose it, every credential stored in n8n is unrecoverable. There's no reset. You'd have to re-enter every single one.

macOS/Linux:

kubectl create secret generic n8n-encryption-key \
--namespace n8n \
--from-literal=N8N_ENCRYPTION_KEY=$(openssl rand -hex 32)

Windows PowerShell:

$key = -join ((1..32) | ForEach-Object { '{0:x2}' -f (Get-Random -Max 256) })
kubectl create secret generic n8n-encryption-key `
--namespace n8n `
--from-literal=N8N_ENCRYPTION_KEY=$key

Deploy n8n with Helm

5.1 Install with Helm

The 8gears community chart is the most widely used Helm chart for deploying n8n on Kubernetes. It's distributed as an OCI artifact, which means it's stored in a container registry the same way Docker images are. You don't need to add a Helm repo first. The install command in section 5.3 pulls the chart directly from the registry, and the chart tells Kubernetes to pull the official `n8nio/n8n` image from Docker Hub when it creates the pod. 

5.2 Write the values file

Create n8n-values.yaml. The chart v2 requires all configuration under the main: key, with n8n settings passed as extraEnv entries. A LoadBalancer service tells Civo to provision a public IP and route incoming traffic to your workload.

# n8n-values.yaml
main:
extraEnv:
N8N_PORT:
value: "5678"
DB_TYPE:
value: "postgresdb"
DB_POSTGRESDB_HOST:
valueFrom:
secretKeyRef:
name: n8n-db-credentials
key: DB_POSTGRESDB_HOST
DB_POSTGRESDB_PORT:
valueFrom:
secretKeyRef:
name: n8n-db-credentials
key: DB_POSTGRESDB_PORT
DB_POSTGRESDB_DATABASE:
valueFrom:
secretKeyRef:
name: n8n-db-credentials
key: DB_POSTGRESDB_DATABASE
DB_POSTGRESDB_USER:
valueFrom:
secretKeyRef:
name: n8n-db-credentials
key: DB_POSTGRESDB_USER
DB_POSTGRESDB_PASSWORD:
valueFrom:
secretKeyRef:
name: n8n-db-credentials
key: DB_POSTGRESDB_PASSWORD
DB_POSTGRESDB_SSL_ENABLED:
value: "true"
DB_POSTGRESDB_SSL_REJECT_UNAUTHORIZED:
value: "false"
N8N_ENCRYPTION_KEY:
valueFrom:
secretKeyRef:
name: n8n-encryption-key
key: N8N_ENCRYPTION_KEY
N8N_SECURE_COOKIE:
value: "false"
N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS:
value: "false"
service:
type: LoadBalancer
port: 80
persistence:
enabled: false
resources:
requests:
cpu: 100m
memory: 250Mi
limits:
cpu: 500m
memory: 512Mi
replicaCount: 1
deployment:
livenessProbe: {}
readinessProbe: {}

N8N_SECURE_COOKIE is set to falsebecause we're accessing n8n over HTTP on a raw IP. Without this, n8n sets secure-only cookies and your browser gets stuck in a login loop. The tradeoff is real: over plain HTTP, session cookies and any credentials you enter travel unencrypted. Anyone on the same network path can intercept them. This is acceptable for a quick local test but not for a deployment you leave running. Before exposing this publicly, add a domain with TLS and set this back to true

N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS prevents n8n from crashing on startup due to config file permission warnings inside the container. The livenessProbe and readinessProbe are set to empty to reduce the default probe aggressiveness on this node size.

The N8N_ENCRYPTION_KEY is pulled from the n8n-encryption-key Secret you created in section 4.4. Keep that secret value backed up somewhere safe. If you lose it, every credential stored in n8n is unrecoverable. 

If the pod goes into CrashLoopBackOff and the logs show a liveness probe failure, run the install command with these additional flags instead.

macOS/Linux:

helm install n8n oci://8gears.container-registry.com/library/n8n \
--namespace n8n \
--values n8n-values.yaml \
--set main.deployment.livenessProbe=null \
--set main.deployment.readinessProbe=null

Windows PowerShell:

helm install n8n oci://8gears.container-registry.com/library/n8n `
--namespace n8n `
--values n8n-values.yaml `
--set "main.deployment.livenessProbe=null" `
--set "main.deployment.readinessProbe=null"

This disables the probes at install time so the pod has enough time to initialize.

5.3 Install n8n

Missing macOS/Linux label:

helm install n8n oci://8gears.container-registry.com/library/n8n \
--namespace n8n \
--values n8n-values.yaml

Windows PowerShell:

helm install n8n oci://8gears.container-registry.com/library/n8n `
--namespace n8n `
--values n8n-values.yaml

5.4 Verify the deployment

Three things to check.

Pod status:

kubectl get pods -n n8n

You want 1/1 Running:

Terminal output showing the n8n pod in 1/1 Running status with zero restarts, confirming a successful deployment.

       Terminal output showing the n8n pod in 1/1 Running status with zero restarts, confirming a successful deployment. 

If you see instead, the database credentials are almost certainly wrong. Check the logs.

Logs:

macOS/Linux:

kubectl logs -n n8n $(kubectl get pods -n n8n -o jsonpath='{.items[0].metadata.name}')

Windows PowerShell:

$pod = kubectl get pods -n n8n -o jsonpath='{.items[0].metadata.name}'
kubectl logs -n n8n $pod

Scroll to the bottom. You're looking for n8n ready on ::, port 5678. That confirms n8n connected to PostgreSQL and started.

Service and external IP:

kubectl get svc -n n8n

You should see output like this: 

Terminal output showing the n8n LoadBalancer service with the public external IP assigned by Civo, ready to receive incoming requests.

Terminal output showing the n8n LoadBalancer service with the public external IP assigned by Civo, ready to receive incoming requests.

That EXTERNAL-IP is your public address. If it says <pending>, give it 30 seconds and check again.

5.5 First-run setup

Open http://EXTERNAL-IPin a browser. n8n will ask you to create an owner account (name, email, password). This login is completely separate from your Civo credentials and your PostgreSQL user. It's how n8n controls who can access the editor and run workflows.

The n8n first-run setup screen prompts you to create an owner account before accessing the editor.

Connect n8n to relaxAI and Slack

6.1 relaxAI credential

Go to Settings → Credentials → Add Credential in the n8n UI and search for OpenAI. relaxAI speaks the OpenAI API protocol, so the same n8n node works for both. You just swap the base URL.

One thing to watch out for: the v2 version of the OpenAI node does not support custom Base URLs. You need the v1 node. Search for "OpenAI API," not "OpenAI Chat Model v2.

Fill it in:

Hit Save, then Test. Green banner means you're good.

The n8n credentials screen showing the OpenAI API credential configured with the relaxAI base URL, tested successfully with a green confirmation banner

The n8n credentials screen showing the OpenAI API credential configured with the relaxAI base URL, tested successfully with a green confirmation banner.

6.2 Slack credential

Head to api.slack.com/apps, click Create New App → From a manifest, pick your workspace, and paste this:

{
"display_information": {
"name": "n8n AI Summarizer",
"description": "Posts AI summaries from n8n workflows"
},
"features": {
"bot_user": {
"display_name": "n8n Summarizer",
"always_online": true
}
},
"oauth_config": {
"scopes": {
"bot": [
"chat:write",
"chat:write.public"
]
}
}
}

It should look like this:

The Slack App Manifest editor

The Slack App Manifest editor

Click Save. On the next screen, go to OAuth & Permissions in the left sidebar and confirm the bot scopes chat:write and chat:write.public are listed under Bot Token Scopes. Then scroll up Install App and click Install to Workspace, authorize the app, and copy the Bot User OAuth Token that starts with xoxb-

Back in n8n: Settings → Credentials → Add Credential, search Slack, paste the token, click Test. It should show your workspace name.

n8n credentials screen showing the Slack bot token connection tested successfully

n8n credentials screen showing the Slack bot token connection tested successfully.

6.3 Test both credentials

When a credential test fails, n8n shows a red banner with the error. Here's what to look for.

relaxAI: a 401 Unauthorized means your API key is wrong or expired. A connection error usually means the Base URL still points at api.openai.com. Open the credential and check the URL field first, then the key.

Slack: the most common failure is a mangled bot token. It has to start with xoxb-. If you're getting , go back to api.slack.com/apps, verify the app shows "Installed" for your workspace, and copy the token fresh from the OAuth & Permissions page.

Build the workflow

7.1 The plan

Three nodes in a straight line. The Webhook receives raw JSON from an external source. The AI Agent ships that JSON to a language model on relaxAI and gets back a structured summary. The Slack node formats the summary with metadata from the original request and posts it to a channel.

7.2 Webhook node

Hit the + button on the canvas and add a Webhook node.

  • HTTP Method: POST
  • Path: /summarize
  • Authentication: Header Auth
  • Header Name: x-api-key
  • Header Value: Choose a secret value and note it down (e.g., my-webhook-secret-123
Webhook parameters

After setting the path and method, configure the authentication. Under Credential for Header Auth, click Select Credential, choose Create new credential, and fill in the header name and value 

Header Auth section inside Webhook parameters

Something that confuses people: n8n generates two URLs per webhook. The test URL only works while you're in the editor with "Execute Workflow" running. The production URL only works when the workflow is toggled to Active. They have different paths.

7.3 AI agent node

Add an AI Agent node and wire it to the Webhook. Model: llama-4-maverick-17b-128e. Good speed, good quality for summarization, runs on Civo's relaxAI GPU infrastructure.

Back in the AI Agent node settings, set Prompt > Source for Prompt to Define below. This reveals the System Message and User Message fields.

Paste this into the System Message:

You are a concise summarizer. Given the input below, return:
- 3 bullet points summarizing the key information
- A suggested next action for the team

Be direct. No preamble.

For User Message, use this n8n expression to forward the entire webhook body:

{{ JSON.stringify($json.body) }}
Agent section

Open its settings and click the + button under Chat Model. Pick OpenAI Chat Model (v1, not v2) and select the relaxAI credential.

Model section

7.4 Slack node

Add a Slack node, connect it to the AI Agent.

  • Resource: Message
  • Operation: Send
  • Channel: Select or type the channel name (e.g., #alerts)
  • Message Text: Use the following expression to combine the AI output with metadata from the original webhook:
*New AI Summary*
Source: {{ $('Webhook').item.json.body.source }}
Event: {{ $('Webhook').item.json.body.event }}
{{ $json.output }}

That expression reaches back to the Webhook node for the source and event fields, then appends the AI summary from the current node's output.

Slack section

7.5 Wire and save

Canvas should show Webhook → AI Agent → Slack. Hit Save. Nothing is live yet.

n8n canvas

The n8n canvas showing the three-node workflow wired in sequence: Webhook node connected to AI Agent node connected to the Slack node, saved and ready to test.

Test and activate

8.1 Test the workflow

Click Execute Workflow in the editor. n8n starts listening on the test URL. Create a file called test.json with the sample payload:

{
"source": "github",
"event": "pull_request",
"repository": "backend-api",
"author": "john.doe",
"title": "Add rate limiting to auth endpoints",
"description": "This PR adds rate limiting to prevent brute force attacks on login and password reset endpoints. Tested with 1000 concurrent requests."
}

From another terminal, send it to the test URL.

Swap YOUR_TEST_URL for the actual test URL from the Webhook node.

macOS/Linux:

curl -X POST YOUR_TEST_URL \
-H "Content-Type: application/json" \
-H "x-api-key: my-webhook-secret-123" \
-d @test.json

Windows PowerShell:

curl.exe -X POST YOUR_TEST_URL `
-H "Content-Type: application/json" `
-H "x-api-key: my-webhook-secret-123" `
-d "@test.json"

On Windows, use curl.exe not curl. PowerShell has a built-in curl alias that behaves differently and will fail with JSON payloads.

If something goes wrong:


  • Webhook returns 404: You're hitting the wrong URL, or "Execute Workflow" isn't running. The test URL only listens while the editor is in execute mode. Copy the URL fresh from the Webhook node and resend.
  • Webhook returns 401: The x-api-key header value doesn't match what you set in the node. Check for trailing spaces.
  • AI Agent node turns red: Open the node output and check the error message. A 401 means the relaxAI credential is wrong. A connection error means the Base URL in the credential still points at api.openai.com.
  • Slack node turns red: The most common cause is the bot not being in the channel. In Slack, open the channel and invite the bot with /invite @n8n Summarizer, then resend the test payload.
  • All nodes stay grey after sending the request: The workflow received nothing. Double-check you're using curl.exe on Windows, not the PowerShell curl alias.
workflow started successfully

Watch the canvas. Each node turns green as it finishes. Click any node to inspect its data: the Webhook shows raw input, the AI Agent shows the summary, and the Slack node shows what got posted.

n8n canvas after a successful test execution

Over in Slack, you should see the formatted message with bullet points and a next-action recommendation.

The Slack channel showing the AI-generated summary posted by the n8n workflow, including the source, event metadata, three bullet points summarizing the payload, and a suggested next action

8.2 Activate the workflow

Flip the Active toggle (top-right of the canvas). The production URL is live. Any POST with the right `x-api-key` header now triggers the whole flow, 24/7.

8.3 Connect a real source

Some things you can point at the production URL right now:

  • Web forms: Set the form's action attribute to your webhook URL and use method="POST" to submit the data. The form fields will arrive as JSON in the webhook payload.
  • GitHub webhooks: In your repo, go to Settings → Webhooks → Add webhook and paste the production URL. Select individual events like `issues` or `pull_request` to control what triggers the workflow.
  • Stripe webhooks: In the Stripe dashboard, go to Developers → Webhooks → Add endpoint and enter the production URL. Pick the events you care about, like payment_intent.failed.
  • Scheduled cron jobs: Use a service like cron-job.org or a server crontab running curl to POST a data payload on a schedule for recurring daily summaries.

Prove it survives

Kill the pod on purpose.

macOS/Linux:

kubectl delete pod -n n8n $(kubectl get pods -n n8n -o jsonpath='{.items[0].metadata.name}')

Windows PowerShell:

$pod = kubectl get pods -n n8n -o jsonpath='{.items[0].metadata.name}'
kubectl delete pod -n n8n $pod

Before deleting, open a second terminal window and start watching the pods:

kubectl get pods -n n8n --watch

Then run the delete command in the first terminal. In the watch window you will see the old pod move to Terminating, a new pod appear in ContainerCreating, and within about 30 seconds the new pod reaches Running status with zero restarts. Kubernetes detected the missing pod and scheduled a replacement automatically.

Terminal output showing the old pod terminating and a new pod reaching Running status

Open n8n at the same public IP. Log in with the same credentials. Your workflow is there, your Slack credential is there, and the execution history from before the deletion is intact.

This matters because the default n8n install uses SQLite, which lives on the container's local disk. Kill that pod and everything goes with it. By pointing n8n at Civo Managed PostgreSQL, you've moved all the state out of the container. The pod itself is just running code. Kubernetes can trash it, reschedule it to a different node, restart it after a crash, and nothing changes from n8n's perspective because everything it cares about is in the database.

Clean up

kubectl delete namespace n8n

That removes the n8n deployment, service, and all associated resources from the cluster in one shot. Then delete the cluster and database from the Civo dashboard if you're done with them entirely.

Deleting the namespace does not touch your PostgreSQL instance. Drop that separately from the Civo dashboard to avoid ongoing charges.

Key takeaways 

The full stack is now running inside a single Civo project where Kubernetes hosts n8n, managed PostgreSQL holds the state, and relaxAI handles the heavy lifting of inference. Centralizing these components under one dashboard provides a level of control that third-party SaaS cannot match. When a model is deprecated or an API contract changes, you simply update your credentials yourself instead of waiting for a vendor. This setup also simplifies security and compliance, as every data flow remains visible and contained within your own cluster.

Building your next automation requires no additional infrastructure because the foundation is already in place. You can launch a new canvas with a different model or trigger while the underlying environment remains stable. Most importantly, because n8n stores all data in PostgreSQL rather than the local pod, the system is truly resilient. You can tear down containers or move them to different nodes, and every workflow, credential, and execution log will reappear untouched. The infrastructure is finished, leaving you free to focus entirely on your ideas.

Additional resources


Mostafa Ibrahim
Mostafa Ibrahim

Software Engineer at GoCardless

Mostafa Ibrahim is a software engineer and technical writer specializing in developer-focused content for SaaS and AI platforms. He currently works as a Software Engineer at GoCardless, contributing to production systems and scalable payment infrastructure.

Alongside his engineering work, Mostafa has written more than 200 technical articles reaching over 500,000 readers. His content covers topics including Kubernetes deployments, AI infrastructure, authentication systems, and retrieval-augmented generation (RAG) architectures.

View author profile