Self-Hosting n8n on Oracle Cloud: The Full Setup
A complete walkthrough of running n8n on Oracle Free Tier — Docker Compose, DuckDNS domain, Redis deduplication, and why this beats every SaaS automation tool I've tried.
- n8n
- Docker
- Oracle Cloud
- Automation
I've used Zapier, Make, and Pipedream. They're all fine until you hit the limits — workflow count, execution history, monthly operation caps — and then they become expensive fast. n8n self-hosted on Oracle Cloud Free Tier is genuinely free, runs 24/7, and gives you full control over your data and workflows.
Here's the complete setup I run for my financial transaction pipeline, which has been running without incident for five months.
Why Oracle Cloud Free Tier
Oracle's Always Free tier is legitimately generous: 2 AMD compute instances with 1GB RAM each, or 4 ARM-based Ampere A1 cores with 24GB RAM total. The ARM option is overkill for n8n but I use a 2-core / 4GB ARM instance and it handles everything comfortably with headroom.
The catch: Oracle Cloud's UI is confusing and the network security rules are non-obvious. I'll cover those.
The Docker Compose setup
services:
n8n:
image: n8nio/n8n:latest
restart: always
ports:
- "5678:5678"
environment:
- N8N_HOST=your-domain.duckdns.org
- N8N_PORT=5678
- N8N_PROTOCOL=https
- WEBHOOK_URL=https://your-domain.duckdns.org/
- N8N_ENCRYPTION_KEY=${N8N_ENCRYPTION_KEY}
- DB_TYPE=postgresdb
- DB_POSTGRESDB_HOST=postgres
- DB_POSTGRESDB_PORT=5432
- DB_POSTGRESDB_DATABASE=n8n
- DB_POSTGRESDB_USER=n8n
- DB_POSTGRESDB_PASSWORD=${POSTGRES_PASSWORD}
volumes:
- n8n_data:/home/node/.n8n
depends_on:
postgres:
condition: service_healthy
postgres:
image: postgres:15
restart: always
environment:
- POSTGRES_DB=n8n
- POSTGRES_USER=n8n
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U n8n"]
interval: 5s
timeout: 5s
retries: 5
redis:
image: redis:7-alpine
restart: always
volumes:
- redis_data:/data
volumes:
n8n_data:
postgres_data:
redis_data:
Use PostgreSQL, not SQLite. SQLite locks under concurrent writes and n8n will start throwing errors after a few hundred workflow executions. Postgres is more work to set up but runs indefinitely without issues.
DuckDNS for a persistent domain
Oracle Cloud gives you a public IP, but it can change if you stop/start the instance. DuckDNS provides free dynamic DNS — you get a yourname.duckdns.org subdomain that you keep updated via a cron job.
# /etc/cron.d/duckdns
*/5 * * * * root curl -s "https://www.duckdns.org/update?domains=yourname&token=YOUR_TOKEN&ip=" > /dev/null
This runs every 5 minutes. In practice the IP never changes on Oracle's always-free instances, but it costs nothing to keep it updated.
Nginx + Let's Encrypt
n8n needs HTTPS for webhooks. I run Nginx as a reverse proxy in the same Docker Compose stack:
server {
listen 443 ssl;
server_name yourname.duckdns.org;
ssl_certificate /etc/letsencrypt/live/yourname.duckdns.org/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/yourname.duckdns.org/privkey.pem;
location / {
proxy_pass http://n8n:5678;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
}
}
Certbot handles SSL issuance and auto-renewal. The Oracle Cloud security list needs port 443 open — this trips people up. Go to Networking → Virtual Cloud Networks → your VCN → Security Lists and add an ingress rule for TCP port 443 from 0.0.0.0/0.
Redis for deduplication
My financial transaction workflow fires when Gmail receives a bank email. Banks resend emails, and Gmail webhooks occasionally fire twice for the same message. Without deduplication, I get duplicate rows in Google Sheets.
The deduplication pattern in n8n uses a Function node that checks Redis before writing:
const redis = $node["Redis"].json;
const key = `txn:${items[0].json.amount}:${items[0].json.merchant}:${items[0].json.date}`;
if (redis.exists === 1) {
return []; // already processed
}
// Continue to write node
return items;
The Redis node before this sets a key with a 48-hour TTL. If the key exists, the Function node returns an empty array, stopping the workflow branch.
Five months in
The setup has processed 847 transactions without a duplicate or missed entry. Total cost: $0. The only maintenance I've done is a docker compose pull && docker compose up -d every few weeks to stay on the latest n8n image.
If you're paying for any automation SaaS for personal use, just move to self-hosted n8n. The initial setup takes an afternoon and you won't look back.