How to deploy n8n on Leafcloud: self-hosted workflow automation in Europe
A practical guide to self-hosting n8n on a sustainable, European cloud. Docker Compose, PostgreSQL, automatic TLS via Caddy, and a backup strategy that won't lose your encryption keys. About thirty minutes, end to end.
By
Published on
Workflow automation is a polite way to describe a piece of software that holds all your API keys, talks to every system your business depends on, and quietly runs at three in the morning. n8n is one of the better ones. It’s fair-code licensed, self-hostable, with first-class support for both no-code workflows and dropping into JavaScript or Python when the no-code abstraction starts getting in the way.
The “self-hostable” part is the bit worth dwelling on. If a tool is going to touch your CRM, your inbox, your internal database, your Slack workspace, and a half-dozen partner APIs on every run, it matters quite a lot where that tool lives, and under whose jurisdiction.
This guide walks through deploying n8n on a Leafcloud cloud server with PostgreSQL, automatic TLS, and persistent backups. About thirty minutes, end to end.
Why self-host n8n on a European cloud
A few reasons that come up often:
Data sovereignty. A workflow engine is, in practice, a very enthusiastic data processor. Every node it runs touches some piece of personal or commercial data. Running it inside the EU under GDPR, on infrastructure that doesn’t hand jurisdiction to a US parent company, is the path of least regulatory friction. No transatlantic data transfer agreements, no Schrems III roulette.
Predictable cost. n8n’s hosted pricing scales with executions. Self-hosted, your bill is the VM. If your team is running thousands of workflows a day, the math gets favourable quickly.
Pairs with managed Postgres. SQLite is fine for trying n8n out. For anything resembling production, you want PostgreSQL. Leafcloud’s managed databases mean you’re not also signing up to operate a database.
Heat reuse. Our compute runs in Leafsites placed inside real buildings, where the waste heat goes into the building’s hot water and space heating loops. n8n is a CPU workload, so you’re not heating an entire apartment, but the principle holds, and the carbon math is honest.
What you’ll end up with
A single Ubuntu 22.04 VM running:
- n8n in Docker, fronted by Caddy for automatic TLS
- PostgreSQL in Docker, with data on a persistent block volume
- All n8n state (encryption keys, workflows, credentials) on the same persistent volume
- A publicly reachable HTTPS endpoint suitable for webhook triggers from GitHub, Stripe, Typeform, and similar
- Daily backups to Leafcloud object storage
The “publicly reachable” bit matters more than you might think. n8n’s killer feature is webhook triggers from third-party services, and those need a stable HTTPS URL. The cloudflared tunnel n8n’s docs describe is fine for local development, but for production you want a real server with a real domain.
1. Provision the VM
In the Leafcloud dashboard, launch a new instance:
| Setting | Value |
|---|---|
| Image | Ubuntu 22.04 LTS |
| Flavor | A CoreBalance flavor (2 vCPU and 4–8 GB RAM is enough for small to medium teams) |
| Root volume | 40 GB |
| Key pair | Your SSH key |
Create a security group with three rules:
22/tcp: SSH, restricted to your IP range80/tcp: HTTP, only used for Let’s Encrypt’s HTTP-01 challenge443/tcp: HTTPS, open to0.0.0.0/0
Allocate a floating IP and SSH in.
If you expect heavy use (many concurrent workflows, large data transformations, or AI nodes calling external models), bump the flavor up. n8n’s per-execution memory footprint can spike, especially with large JSON payloads.
2. Point your DNS at the instance
Before doing anything else, set up the DNS record for your n8n hostname. Caddy will need to resolve it during the TLS challenge.
In your registrar’s DNS panel (Strato, Cloudflare, wherever you keep your zones), add an A record pointing your chosen subdomain (e.g. n8n.yourcompany.com) at the floating IP.
Wait for the record to propagate. dig n8n.yourcompany.com should return the floating IP before you continue.
3. Install Docker
sudo apt update && sudo apt upgrade -y
curl -fsSL https://get.docker.com | sudo sh
sudo usermod -aG docker $USER
newgrp docker
Verify:
docker compose version
You want Compose v2, which ships with modern Docker installs.
4. Attach a persistent volume
n8n’s /home/node/.n8n directory contains the encryption key used to secure stored credentials. If you lose this key, every credential in the database becomes undecryptable garbage. The same goes for your PostgreSQL data.
So: attach a separate block volume and put both on it.
Attach a 50 GB block volume in the Leafcloud dashboard. It’ll appear as /dev/vdb. Then on the VM:
sudo mkfs.ext4 /dev/vdb
sudo mkdir -p /var/n8n-data
echo '/dev/vdb /var/n8n-data ext4 defaults 0 2' | sudo tee -a /etc/fstab
sudo mount -a
sudo mkdir -p /var/n8n-data/n8n /var/n8n-data/postgres /var/n8n-data/caddy
sudo chown -R 1000:1000 /var/n8n-data/n8n
The 1000:1000 ownership matches the node user inside the n8n container.
5. Create the Docker Compose stack
mkdir -p ~/n8n && cd ~/n8n
Create a .env file with your secrets:
# ~/n8n/.env
N8N_HOST=n8n.yourcompany.com
N8N_PROTOCOL=https
WEBHOOK_URL=https://n8n.yourcompany.com/
POSTGRES_USER=n8n
POSTGRES_PASSWORD=<a long random string>
POSTGRES_DB=n8n
GENERIC_TIMEZONE=Europe/Amsterdam
Generate the Postgres password with openssl rand -base64 32. Don’t get creative.
Now create docker-compose.yml:
services:
postgres:
image: postgres:16
restart: unless-stopped
environment:
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_DB: ${POSTGRES_DB}
volumes:
- /var/n8n-data/postgres:/var/lib/postgresql/data
healthcheck:
test: ['CMD-SHELL', 'pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}']
interval: 10s
timeout: 5s
retries: 5
n8n:
image: docker.n8n.io/n8nio/n8n
restart: unless-stopped
depends_on:
postgres:
condition: service_healthy
environment:
DB_TYPE: postgresdb
DB_POSTGRESDB_HOST: postgres
DB_POSTGRESDB_PORT: 5432
DB_POSTGRESDB_DATABASE: ${POSTGRES_DB}
DB_POSTGRESDB_USER: ${POSTGRES_USER}
DB_POSTGRESDB_PASSWORD: ${POSTGRES_PASSWORD}
N8N_HOST: ${N8N_HOST}
N8N_PROTOCOL: ${N8N_PROTOCOL}
WEBHOOK_URL: ${WEBHOOK_URL}
N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS: 'true'
N8N_RUNNERS_ENABLED: 'true'
GENERIC_TIMEZONE: ${GENERIC_TIMEZONE}
TZ: ${GENERIC_TIMEZONE}
volumes:
- /var/n8n-data/n8n:/home/node/.n8n
caddy:
image: caddy:2
restart: unless-stopped
ports:
- '80:80'
- '443:443'
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile
- /var/n8n-data/caddy:/data
depends_on:
- n8n
And a Caddyfile:
n8n.yourcompany.com {
reverse_proxy n8n:5678
}
Caddy will obtain a Let’s Encrypt certificate automatically on first start. No manual cert wrangling, no certbot cron jobs.
6. Launch the stack
docker compose up -d
Watch the logs while everything comes up:
docker compose logs -f
You’re looking for n8n’s “Editor is now accessible via” line and Caddy’s “certificate obtained successfully” message. The TLS handshake takes 30–60 seconds the first time.
Then visit https://n8n.yourcompany.com in your browser and create your owner account.
7. Back up the encryption key (do this now, not later)
Before you do anything else with n8n, make sure your encryption key is backed up. It lives at /var/n8n-data/n8n/config. Lose it and every credential stored in n8n becomes unrecoverable.
A reasonable daily backup script using restic against Leafcloud object storage:
#!/bin/bash
# /usr/local/bin/n8n-backup.sh
export RESTIC_REPOSITORY=s3:https://leafcloud.store/n8n-backups
export RESTIC_PASSWORD_FILE=/etc/restic/password
export AWS_ACCESS_KEY_ID=<your key>
export AWS_SECRET_ACCESS_KEY=<your secret>
# Snapshot Postgres first
docker compose -f /home/ubuntu/n8n/docker-compose.yml exec -T postgres \
pg_dump -U n8n n8n | gzip > /tmp/n8n-db.sql.gz
restic backup /var/n8n-data /tmp/n8n-db.sql.gz
restic forget --keep-daily 7 --keep-weekly 4 --keep-monthly 6 --prune
rm /tmp/n8n-db.sql.gz
Drop it in cron:
0 3 * * * /usr/local/bin/n8n-backup.sh
The Postgres dump is technically redundant with the volume backup, but it makes restoration to a different machine much easier, and the storage cost is negligible.
8. Updating n8n
cd ~/n8n
docker compose pull
docker compose down
docker compose up -d
Always check n8n’s breaking changes log before pulling a new major version. They’re conservative about breaking workflows, but it does happen.
What you’ve actually deployed
A self-hosted, GDPR-aligned workflow automation server, running on 100% renewable-powered European infrastructure, with the waste heat going to displace gas heating in a real building. Your credentials are encrypted at rest with a key you control, the database is persistent and backed up to object storage, and your webhook URLs don’t depend on a third-party tunnelling service.
If you’d rather use Leafcloud’s managed PostgreSQL instead of running it in a container, the swap is straightforward. Point DB_POSTGRESDB_HOST at the managed instance’s hostname and remove the postgres service from the compose file. Same goes for moving the whole thing to managed Kubernetes once you outgrow a single VM.
That’s the appeal of building on an open, European cloud. No lock-in, no surprise bills tied to execution counts, and infrastructure that’s actually doing something useful with the heat it produces instead of blowing it into the sky above someone else’s datacenter.
Related Reading
If you’re building a self-hosted stack on European infrastructure, these guides might be useful:
-
Run Your Own Private Coding Assistant with OpenCode — Deploy OpenCode with VLLM and Qwen3-Coder on A100 GPUs. Keep your code and development workflows sovereign.
-
Unlock the Potential of Sustainable Private LLMs — Set up your own LLM on Kubernetes with Ollama and AnythingLLM. Perfect if you want to add AI capabilities to your n8n workflows without third-party API dependencies.
Ready to deploy?
Sign up for Leafcloud and have your n8n instance running this afternoon. If you’d like to talk through sizing, networking, or how to fit this into a wider Kubernetes setup first, book a call with our team — we answer.