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.
Written by
Software Engineer at GoCardless
Written by
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.
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:
- A relaxAI API key
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-kubeconfigkubectl get nodes
Windows PowerShell:
$env:KUBECONFIG="C:\Users\YourUsername\Downloads\civo-n8n-cluster-kubeconfig"kubectl get nodes
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.
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.yamlmain:extraEnv:N8N_PORT:value: "5678"DB_TYPE:value: "postgresdb"DB_POSTGRESDB_HOST:valueFrom:secretKeyRef:name: n8n-db-credentialskey: DB_POSTGRESDB_HOSTDB_POSTGRESDB_PORT:valueFrom:secretKeyRef:name: n8n-db-credentialskey: DB_POSTGRESDB_PORTDB_POSTGRESDB_DATABASE:valueFrom:secretKeyRef:name: n8n-db-credentialskey: DB_POSTGRESDB_DATABASEDB_POSTGRESDB_USER:valueFrom:secretKeyRef:name: n8n-db-credentialskey: DB_POSTGRESDB_USERDB_POSTGRESDB_PASSWORD:valueFrom:secretKeyRef:name: n8n-db-credentialskey: DB_POSTGRESDB_PASSWORDDB_POSTGRESDB_SSL_ENABLED:value: "true"DB_POSTGRESDB_SSL_REJECT_UNAUTHORIZED:value: "false"N8N_ENCRYPTION_KEY:valueFrom:secretKeyRef:name: n8n-encryption-keykey: N8N_ENCRYPTION_KEYN8N_SECURE_COOKIE:value: "false"N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS:value: "false"service:type: LoadBalancerport: 80persistence:enabled: falseresources:requests:cpu: 100mmemory: 250Milimits:cpu: 500mmemory: 512MireplicaCount: 1deployment: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.
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.
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.

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:
- API Key: Your relaxAI API key
- Base URL:
https://api.relax.ai/v1(replace the default OpenAI URL)
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.
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
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.
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

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

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) }}

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

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.

7.5 Wire and save
Canvas should show Webhook → AI Agent → Slack. Hit Save. Nothing is live yet.

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-keyheader 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
401means the relaxAI credential is wrong. A connection error means the Base URL in the credential still points atapi.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.exeon Windows, not the PowerShellcurlalias.

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.

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

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
actionattribute to your webhook URL and usemethod="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
curlto 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.

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
- Civo Kubernetes documentation: Guides for cluster creation, networking, and storage on Civo.
- Civo Managed Databases documentation: Setup and configuration for Civo's managed PostgreSQL and MySQL services.
- relaxAI documentation: API reference, supported models, and pricing for Civo's GPU inference service.
- n8n documentation: Full reference for nodes, expressions, credentials, and self-hosting.
- 8gears Helm chart on Artifact Hub: Chart values reference and version history for the n8n Helm chart.

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.
Share this article