All writing
April 2, 2025·6 min read

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.