Blog

  • YouTube Automation Bot: Complete Docker Setup Guide

    How to Build a YouTube Automation Bot with Docker and the YouTube API

    Disclosure: This post contains one or more links to providers we have a real, registered affiliate/referral relationship with. We may earn a commission at no extra cost to you if you sign up through them.

    If you’re running a faceless channel, a multi-channel network, or you’re just tired of manually uploading videos at 2 a.m., a youtube automation bot can take over the repetitive parts of your workflow — uploading, tagging, scheduling, and reporting — while you focus on the content itself.

    This guide walks through building one the right way: using the official YouTube Data API, containerized with Docker, and deployed on a VPS you control. We’ll skip anything that touches fake views, click farms, or engagement manipulation — those tactics violate YouTube’s Terms of Service and can get a channel terminated. Everything here uses Google’s public, documented APIs.

    What Is a YouTube Automation Bot?

    A YouTube automation bot is a script or service that handles repetitive channel-management tasks programmatically instead of through the YouTube Studio UI. Typical jobs include:

  • Uploading pre-rendered videos on a schedule
  • Setting titles, descriptions, tags, and thumbnails automatically
  • Pulling analytics (views, watch time, subscriber deltas) into a dashboard
  • Auto-replying to comments with templated responses
  • Rotating playlists or updating end screens across a catalog
  • None of this requires touching YouTube’s private infrastructure — it’s all exposed through the YouTube Data API v3, which is free up to a daily quota and well documented.

    Legitimate vs. Risky Automation

    There’s an important line here. Automating your own upload pipeline, metadata, and reporting is fine — YouTube expects creators to use the API this way. What crosses the line is anything that simulates fake engagement: bots that generate views, subscribers, likes, or comments to game the algorithm. That behavior violates YouTube’s Terms of Service and typically results in channel strikes or termination. This guide only covers the former, and you should treat any tool advertised as a ‘view bot’ or ‘engagement bot’ as a way to get your channel banned, not grown.

    Why Use Docker for Your YouTube Automation Bot

    Running the bot as a bare Python script on your laptop works fine until your laptop is asleep at upload time. Containerizing it with Docker gives you a few concrete advantages:

  • Portability — move the bot from your laptop to a VPS with no dependency headaches
  • Isolation — API credentials and rendering dependencies stay scoped to the container
  • Restart policies — Docker restarts the bot automatically if it crashes mid-run
  • Reproducibility — the same image runs identically in staging and production
  • If you haven’t containerized a Python service before, our Docker Compose guide for beginners covers the basics before you dive into this build.

    Core Components You’ll Need

  • A Google Cloud project with the YouTube Data API v3 enabled
  • OAuth 2.0 credentials (client ID + secret) for the channel you’re automating
  • A small Python service (or Node, if you prefer) that calls the API
  • A cron schedule or task queue to trigger uploads
  • A VPS to host the container long-term
  • Setting Up the YouTube Data API

    Start in the Google Cloud Console. Create a project, enable ‘YouTube Data API v3,’ and generate OAuth 2.0 credentials of type ‘Desktop App.’ Download the client_secret.json file — you’ll mount it into the container rather than baking it into the image.

    Install the client library locally first to generate a refresh token:

    pip install google-api-python-client google-auth-oauthlib google-auth-httplib2

    # authorize.py — run once locally to generate token.json
    from google_auth_oauthlib.flow import InstalledAppFlow
    
    SCOPES = ["https://www.googleapis.com/auth/youtube.upload"]
    
    flow = InstalledAppFlow.from_client_secrets_file("client_secret.json", SCOPES)
    credentials = flow.run_local_server(port=0)
    
    with open("token.json", "w") as f:
        f.write(credentials.to_json())

    Run it once, approve the OAuth prompt in your browser, and you’ll have a token.json refresh token that the bot can reuse indefinitely without re-authenticating, as long as your OAuth consent screen is published rather than left in testing mode.

    Building the Bot Container

    Here’s a minimal upload bot. It reads a folder of rendered videos plus a matching metadata file and pushes them to YouTube.

    # uploader.py
    import json
    import logging
    from googleapiclient.discovery import build
    from googleapiclient.http import MediaFileUpload
    from google.oauth2.credentials import Credentials
    
    logging.basicConfig(
        filename="/app/logs/uploader.log",
        level=logging.INFO,
        format="%(asctime)s %(levelname)s %(message)s",
    )
    
    def get_service():
        creds = Credentials.from_authorized_user_file("/app/secrets/token.json")
        return build("youtube", "v3", credentials=creds)
    
    def upload_video(youtube, video_path, meta):
        body = {
            "snippet": {
                "title": meta["title"],
                "description": meta["description"],
                "tags": meta.get("tags", []),
                "categoryId": "22",
            },
            "status": {"privacyStatus": "public"},
        }
        media = MediaFileUpload(video_path, chunksize=-1, resumable=True)
        request = youtube.videos().insert(part="snippet,status", body=body, media_body=media)
        try:
            response = request.execute()
            logging.info(f"Uploaded video id={response['id']} title={meta['title']}")
        except Exception as exc:
            logging.error(f"Upload failed: {exc}")
            raise
    
    if __name__ == "__main__":
        youtube = get_service()
        with open("/app/queue/next.json") as f:
            meta = json.load(f)
        upload_video(youtube, meta["video_path"], meta)

    Now containerize it:

    FROM python:3.12-slim
    
    WORKDIR /app
    COPY requirements.txt .
    RUN pip install --no-cache-dir -r requirements.txt
    
    COPY uploader.py .
    
    CMD ["python", "uploader.py"]

    # docker-compose.yml
    services:
      yt-bot:
        build: .
        restart: unless-stopped
        volumes:
          - ./secrets:/app/secrets:ro
          - ./queue:/app/queue
          - ./videos:/app/videos
          - ./logs:/app/logs
        env_file: .env

    Build and run it with:

    docker compose up -d --build

    Scheduling Uploads with Cron

    Rather than keeping the container running as a daemon, it’s usually cleaner to have the bot exit after each run and trigger it on a schedule from the host:

    # crontab -e
    0 15 * * * cd /opt/yt-bot && docker compose run --rm yt-bot

    This uploads a new video every day at 15:00 server time, pulling the next item from the queue/ folder. If the queue is empty, have the script exit cleanly and log a warning rather than throwing an unhandled exception that fills your disk with tracebacks.

    Handling Thumbnails and Metadata Automatically

    A fully automated pipeline usually needs more than just the video file — it needs a thumbnail and a metadata template ready before the cron job fires.

    Generating Thumbnails with FFmpeg

    You can extract a frame directly from the rendered video instead of designing a thumbnail manually every time:

    ffmpeg -i input.mp4 -ss 00:00:05 -vframes 1 thumbnail.jpg

    Upload it separately once the video exists:

    youtube.thumbnails().set(
        videoId=response["id"],
        media_body=MediaFileUpload("thumbnail.jpg")
    ).execute()

    Auto-Tagging with a Metadata Template

    Keep a JSON template per video series so titles and descriptions stay consistent without you retyping boilerplate (channel links, disclosure text, hashtags) every upload:

    {
      "title": "{{episode_title}} | Episode {{number}}",
      "description": "{{summary}}nnSubscribe for more: https://youtube.com/yourchannel",
      "tags": ["tutorial", "automation", "devops"]
    }

    Have the uploader script fill in the {{ }} placeholders from a CSV or spreadsheet row before calling the API.

    Monitoring and Logging Your Bot

    An automation bot that fails silently is worse than no bot at all — you’ll only find out something broke when a scheduled upload never appeared. Ship container logs somewhere you’ll actually see them, and set up an uptime check that alerts you if the container stops running or the cron job doesn’t fire. BetterStack is a solid option for log aggregation plus uptime monitoring if you’d rather not roll your own alerting stack — worth checking out if you’re running more than one automated channel.

    At minimum, log every upload attempt with a timestamp and API response code, and add a retry wrapper for transient failures:

    import time
    
    def upload_with_retry(youtube, video_path, meta, retries=3):
        for attempt in range(1, retries + 1):
            try:
                return upload_video(youtube, video_path, meta)
            except Exception:
                if attempt == retries:
                    raise
                time.sleep(2 ** attempt)

    Deploying to a VPS

    For a bot this lightweight, you don’t need much horsepower — 1 vCPU and 1–2GB RAM is plenty unless you’re also rendering video on the same box. DigitalOcean droplets are a straightforward option for this: spin one up, install Docker, clone your bot repo, drop in your .env and secrets/ folder, and add the cron entry from earlier.

    If your bot also serves a small dashboard for viewing upload history or analytics, put Cloudflare in front of it for free TLS and basic DDoS protection rather than exposing the VPS directly. For a deeper walkthrough of hardening a VPS before you deploy anything to it, see our VPS security checklist.

    Alternatives to Building Your Own Bot

    If writing and maintaining Python isn’t something you want to own long-term, there are hosted ‘YouTube automation’ SaaS tools that wrap the same API behind a UI — you trade flexibility and cost for convenience. The tradeoffs to weigh:

  • Build it yourself: full control, no recurring SaaS fee, but you own the maintenance
  • Use a hosted tool: faster to start, but you’re limited to whatever workflows the vendor supports
  • Hybrid: use your own upload/scheduling bot, but outsource rendering or thumbnail design
  • For most developers already comfortable with Docker and cron, the self-hosted route in this guide costs a few dollars a month in VPS fees and gives you full control over the pipeline.

    Best Practices for Safe Automation

  • Respect the API quota — the default is 10,000 units/day, and an upload costs 1,600 units, so you’re capped around 6 uploads/day per project
  • Never commit token.json or client_secret.json to a public repo or bake them into the Docker image
  • Add retry logic with exponential backoff for transient API errors (403, 500, 503)
  • Keep a human in the loop for thumbnails and titles if you’re relying on AI-generated metadata — spot-check before it goes live
  • Rotate OAuth credentials if you ever suspect the container or VPS has been compromised
  • Don’t automate anything that touches views, likes, comments, or subscriber counts — that’s the boundary between automation and a ToS violation
  • Recommended: Ready to put this into practice? DigitalOcean is a tool we use for exactly this, and we have a real, disclosed affiliate relationship with them.

    FAQ

    Is it against YouTube’s rules to automate uploads?
    No. Using the official YouTube Data API to automate uploads, metadata, and scheduling is explicitly supported and widely used by creators and MCNs. What’s against the rules is using bots to fake engagement metrics like views, likes, or subscribers.

    Do I need YouTube Partner Program access to use the API?
    No, the YouTube Data API is available to any Google account. You do need to enable the API in Google Cloud Console and complete OAuth verification for scopes beyond basic read access.

    How many videos can my automation bot upload per day?
    With the default 10,000-unit daily quota and each upload costing 1,600 units, you can realistically upload around 6 videos per day per Google Cloud project. You can request a quota increase from Google if you consistently need more.

    Can I run this bot on a Raspberry Pi instead of a VPS?
    Yes — the Docker image is lightweight enough to run on a Pi 4 or similar. The tradeoff is reliability: a VPS gives you better uptime, a static IP, and doesn’t depend on your home internet connection staying up.

    What happens if my OAuth token expires?
    Refresh tokens generated via the installed-app flow don’t expire unless revoked, unused for six months, or the OAuth consent screen is still in ‘testing’ mode, which caps tokens at 7 days. Publish your OAuth consent screen to avoid that 7-day limit.

    Should I use webhooks instead of cron for triggering uploads?
    Cron is simpler and sufficient for scheduled publishing. Webhooks make more sense if uploads are triggered by an external event, like a rendering pipeline finishing a new video file, rather than a fixed time of day.

    A well-built youtube automation bot should feel invisible — videos go out on schedule, metadata is consistent, and the only time you think about it is when a log alert tells you something needs attention. Start small: automate uploads and metadata first, add thumbnail generation once that’s stable, and layer in analytics reporting last.

  • n8n Cloud Pricing 2026: Plans, Costs & Alternatives

    n8n Cloud Pricing in 2026: What You’ll Actually Pay (and When to Self-Host Instead)

    Disclosure: This post contains one or more links to providers we have a real, registered affiliate/referral relationship with. We may earn a commission at no extra cost to you if you sign up through them.

    If you’ve started building workflow automations and landed on n8n, you’ve probably hit the same wall everyone does: the pricing page tells you a starting number, but not what you’ll actually pay once your workflows scale. Execution-based pricing sounds simple until you have a dozen workflows firing on webhooks, cron schedules, and API polling loops all at once.

    This guide breaks down n8n cloud pricing tier by tier, flags the costs that catch people off guard, and walks through the math on self-hosting n8n on your own infrastructure instead — including a working Docker Compose setup you can deploy today.

    What n8n Actually Is (Quick Context)

    n8n is an open-source workflow automation tool — think Zapier or Make, but self-hostable and node-based, with the ability to write custom JavaScript directly inside workflow steps. It connects APIs, databases, webhooks, and internal services without requiring you to write a full integration layer from scratch.

    Because the core project is fair-code licensed and available as a Docker image, you have two real paths to running it:

  • n8n Cloud — a managed SaaS offering, billed monthly, scaled by workflow executions.
  • Self-hosted n8n — you run the container yourself on a VPS, paying only for the server.
  • Both are legitimate choices depending on your team size, technical comfort, and how many executions you’re actually running per month.

    n8n Cloud Plan Breakdown

    n8n Cloud pricing is structured around three main variables: number of active workflows, execution volume, and feature access (things like environments, SSO, and advanced permissions). The tiers roughly break down like this:

  • Starter — entry-level tier aimed at solo builders and small teams. Includes a capped monthly execution allowance (commonly a few thousand executions/month), a limited number of active workflows, and community support only.
  • Pro — the tier most small businesses land on. Higher execution caps, more active workflows, shared team workspaces, and priority support.
  • Enterprise — custom pricing, negotiated directly with n8n’s sales team. Includes SSO/SAML, audit logs, dedicated infrastructure options, and SLA-backed uptime guarantees.
  • Exact dollar figures shift periodically as n8n adjusts its packaging, so always check the official n8n pricing page before budgeting — don’t rely on screenshots or third-party recaps, including this one, for the current number.

    What matters more than the sticker price is understanding what counts as an execution, because that’s where budgets quietly blow up.

    Hidden Costs to Watch For

    The advertised monthly price is rarely the full story. Here’s what actually drives your bill up over time:

  • Execution counting — every workflow run counts as an execution, even failed ones and ones triggered by a single webhook ping. A workflow that polls an API every minute burns through your allowance fast — that’s 1,440 executions/day from one workflow alone.
  • Overage charges — once you exceed your plan’s execution cap, you’re either rate-limited, forced to upgrade, or charged per-execution overage fees depending on the plan.
  • Active workflow limits — cloud plans often cap how many workflows can be active (not just built) at once. Testing and staging workflows can eat into that limit if you’re not archiving unused ones.
  • Data retention — execution history and logs beyond a certain window may require a higher tier to retain, which matters if you need to debug something that happened two weeks ago.
  • Add-on seats — team collaboration features are often priced per additional user once you go beyond the base seat count.
  • If your automations are lightweight — a few daily Slack notifications, a handful of CRM syncs — cloud pricing is genuinely reasonable. If you’re running high-frequency polling workflows or processing large data batches, the execution model starts working against you.

    Self-Hosting n8n: The Cost Comparison

    Because n8n ships as an open-source Docker image, you can run the exact same automation engine on your own server for the price of the VPS — no execution caps, no per-seat charges, no overage billing. The tradeoff is that you’re responsible for uptime, backups, and updates.

    Here’s a minimal but production-usable docker-compose.yml to get n8n running with persistent data and a Postgres backend:

    version: "3.8"
    
    services:
      postgres:
        image: postgres:16
        restart: unless-stopped
        environment:
          POSTGRES_USER: n8n
          POSTGRES_PASSWORD: change_this_password
          POSTGRES_DB: n8n
        volumes:
          - postgres_data:/var/lib/postgresql/data
    
      n8n:
        image: n8nio/n8n:latest
        restart: unless-stopped
        ports:
          - "5678:5678"
        environment:
          DB_TYPE: postgresdb
          DB_POSTGRESDB_HOST: postgres
          DB_POSTGRESDB_DATABASE: n8n
          DB_POSTGRESDB_USER: n8n
          DB_POSTGRESDB_PASSWORD: change_this_password
          N8N_HOST: automation.yourdomain.com
          N8N_PROTOCOL: https
          WEBHOOK_URL: https://automation.yourdomain.com/
          GENERIC_TIMEZONE: UTC
        volumes:
          - n8n_data:/home/node/.n8n
        depends_on:
          - postgres
    
    volumes:
      postgres_data:
      n8n_data:

    Bring it up with:

    docker compose up -d

    Then put it behind a reverse proxy (Caddy or Nginx) for TLS termination, and you have a fully functional, unlimited-execution n8n instance. We cover the full reverse-proxy and TLS setup in our Docker Compose deployment guide if you want the complete walkthrough.

    Real Numbers: Cloud vs. Self-Hosted

    To put this in perspective, a small 2GB/2vCPU VPS from a provider like DigitalOcean or Hetzner typically costs between $6 and $12/month — and can comfortably run n8n plus Postgres for low-to-moderate workflow volume. Compare that to a cloud Pro-tier plan, and the break-even point usually arrives faster than people expect, especially once you factor in execution overages.

    The catch is that self-hosting shifts responsibility onto you:

  • You manage OS updates, Docker image updates, and security patching.
  • You need your own backup strategy for the Postgres volume.
  • You need uptime monitoring, since there’s no vendor SLA watching your instance for you.
  • You handle scaling manually if execution volume grows.
  • That last point is where a lot of self-hosted n8n setups quietly fail — nobody notices the container crashed until a client complains their automation didn’t fire. Setting up external uptime monitoring through something like BetterStack closes that gap cheaply, pinging your webhook endpoint or n8n health check on a schedule and alerting you the moment it goes down.

    When Cloud Makes More Sense Than Self-Hosting

    Self-hosting isn’t automatically the right call. Cloud is usually the better option when:

  • You don’t have DevOps bandwidth to manage patching and backups.
  • You need enterprise features like SSO/SAML out of the box.
  • Your execution volume is genuinely low and predictable.
  • You want a support contract and SLA rather than being your own on-call engineer.
  • Self-hosting wins when execution volume is high, you already run infrastructure for other services, or you want full control over data residency and retention — which matters a lot if you’re processing anything sensitive through your workflows.

    If you’re already running other self-hosted tools on a VPS, adding n8n to the same box (or a small dedicated one) is usually the most cost-efficient path. We go deeper into stacking multiple self-hosted services efficiently in our guide to self-hosting your DevOps toolchain.

    Migrating Between Cloud and Self-Hosted

    One underrated advantage of n8n’s architecture is that migration isn’t a one-way door. Workflows are portable JSON exports, so you can:

    1. Build and prototype on n8n Cloud while your automation needs are small.
    2. Export workflows once execution volume or costs justify moving.
    3. Import them into a self-hosted instance without rebuilding from scratch.

    This makes it low-risk to start on cloud and migrate later once you understand your actual execution patterns — which is honestly the smartest way to approach the pricing decision rather than guessing upfront.

    Choosing a VPS for Self-Hosted n8n

    If you decide to self-host, the VPS choice matters more than people assume. You want:

  • At least 2GB RAM (n8n plus Postgres is lightweight but not trivial under load)
  • SSD-backed storage for database performance
  • A provider with snapshot/backup support baked in
  • Reasonable network latency to the APIs your workflows call most often
  • DigitalOcean and Hetzner both offer droplets/servers in this range at low monthly cost, with straightforward Docker support and one-click backups. If you want managed database hosting instead of running Postgres in the same container stack, that’s a worthwhile upgrade once your automation becomes business-critical.

    Recommended: Want to explore DigitalOcean yourself? DigitalOcean is a direct vendor link (not an affiliate/tracked link).

    FAQ

    Does n8n Cloud have a free plan?
    n8n typically offers a free trial period rather than a permanent free tier for cloud hosting. For a genuinely free option long-term, self-hosting the open-source Docker image on your own server is the way to go — you only pay for the VPS.

    What counts as an “execution” in n8n pricing?
    Each time a workflow runs from start to finish counts as one execution, regardless of whether it succeeds, fails, or is triggered manually, by webhook, or by schedule. A single workflow polling an API every few minutes can consume thousands of executions per month on its own.

    Is self-hosted n8n really free?
    The software itself is free under n8n’s fair-code license, but you still pay for the server it runs on, plus your own time for maintenance, updates, and monitoring. “Free” software isn’t the same as “zero-cost” infrastructure.

    Can I switch from n8n Cloud to self-hosted later?
    Yes. Workflows export as JSON and import cleanly into a self-hosted instance, so you can start on cloud and migrate once your execution volume justifies running your own server.

    How many executions does a typical small business need per month?
    It varies enormously, but a business running a handful of automations — form submissions, CRM syncs, notification workflows — often stays in the low thousands of executions per month, which usually fits comfortably within entry-level cloud tiers.

    Is n8n cloud pricing worth it compared to Zapier or Make?
    n8n generally offers more generous execution limits per dollar than Zapier, and more flexibility for custom logic than Make, but the comparison depends heavily on your specific workflow complexity — it’s worth testing all three against your actual use case before committing.

    Final Take

    n8n cloud pricing works well for teams that want a managed, zero-maintenance automation platform and have moderate, predictable execution volume. Once your workflows scale into high-frequency polling or large data batches, the execution-based model starts costing more than a self-hosted VPS running the same open-source image. Start with cloud if you’re prototyping, track your execution counts honestly, and migrate to self-hosting once the math tips in that direction — the workflow export format makes that switch far less painful than most SaaS-to-self-hosted migrations.

  • n8n Template Guide: Deploy & Customize Workflows Fast

    The Complete n8n Template Guide for Self-Hosted Automation

    Disclosure: This post contains one or more links to providers we have a real, registered affiliate/referral relationship with. We may earn a commission at no extra cost to you if you sign up through them.

    If you’re running DevOps pipelines, monitoring alerts, or internal tooling, chances are you’ve hit the point where Zapier gets too expensive and too limited. That’s where n8n comes in — a fair-code workflow automation tool you can self-host on your own VPS. The fastest way to get productive with it is to start from an existing n8n template rather than building every workflow from scratch.

    This guide covers what an n8n template actually is, how to self-host n8n with Docker, where to find production-ready templates, and how to adapt them for real infrastructure work like log monitoring, deployment notifications, and backup automation.

    What Is an n8n Template?

    An n8n template is a pre-built workflow — exported as JSON — that you can import directly into your n8n instance. Instead of manually wiring together nodes (HTTP requests, triggers, conditionals, database writes), you import a template and just plug in your credentials and endpoints.

    Templates cover common automation patterns:

  • Slack/Discord alerts when a server goes down
  • Syncing data between a database and a spreadsheet
  • Automated backups pushed to S3-compatible storage
  • CI/CD deployment notifications
  • RSS-to-social-media posting pipelines
  • Because n8n workflows are just JSON under the hood, templates are portable, version-controllable, and easy to share across teams.

    Why Templates Matter for Automation Teams

    Writing a workflow from a blank canvas takes time — you have to know which nodes exist, how credentials are scoped, and how error handling should be structured. A template shortcuts that learning curve. For sysadmins managing a fleet of servers, a solid n8n template library means you can deploy monitoring and alerting automation in minutes instead of hours.

    This matters even more if you’re self-hosting n8n rather than using n8n Cloud — self-hosting gives you full data control, which is often a requirement for infrastructure teams handling internal credentials and server access tokens.

    Setting Up n8n with Docker

    Before you can use any template, you need a running n8n instance. The cleanest way to do this is with Docker Compose, which also makes it trivial to back up and migrate later.

    version: "3.8"
    
    services:
      n8n:
        image: n8nio/n8n:latest
        restart: unless-stopped
        ports:
          - "5678:5678"
        environment:
          - N8N_HOST=n8n.yourdomain.com
          - N8N_PROTOCOL=https
          - N8N_PORT=5678
          - WEBHOOK_URL=https://n8n.yourdomain.com/
          - GENERIC_TIMEZONE=UTC
          - N8N_BASIC_AUTH_ACTIVE=true
          - N8N_BASIC_AUTH_USER=admin
          - N8N_BASIC_AUTH_PASSWORD=changeme
        volumes:
          - n8n_data:/home/node/.n8n
    
    volumes:
      n8n_data:

    Bring it up with:

    docker compose up -d

    Once it’s running, n8n is available on port 5678. If you’re new to Docker networking basics, our Docker Compose guide walks through volumes, restart policies, and multi-service stacks in more depth.

    Docker Compose Configuration for Production

    For production, don’t expose port 5678 directly to the internet. Put n8n behind a reverse proxy with TLS termination — Nginx or Traefik both work well, and our Nginx reverse proxy walkthrough covers the exact config you need for HTTPS with Let’s Encrypt.

    A few production hardening steps worth doing immediately:

  • Change the default basic auth credentials before exposing any port
  • Set N8N_ENCRYPTION_KEY explicitly so credential encryption survives container recreation
  • Use a managed Postgres database instead of the default SQLite for anything beyond light personal use
  • Put the instance behind Cloudflare for DDoS protection and a free SSL layer
  • If you’re picking infrastructure to host this on, a small VPS from DigitalOcean or Hetzner is more than enough — n8n is lightweight and rarely needs more than 1-2 vCPUs unless you’re running very high workflow volume.

    Finding and Importing n8n Templates

    Once your instance is live, the fastest path to a working automation is importing an existing template rather than building one node-by-node.

    Using the n8n Template Library

    n8n maintains an official template library at n8n.io/workflows, with thousands of community-submitted workflows organized by category — DevOps, marketing, sales ops, data sync, and more. Each listing shows the exact nodes used, so you can vet a template for security before importing it (especially important if it touches credentials or webhooks).

    Search for terms relevant to your use case — “server monitoring,” “Slack alert,” “database backup” — and you’ll typically find several variations to compare.

    Importing Templates via JSON

    Every n8n template can be imported two ways:

    1. Direct import from the library — click “Use this workflow” and it opens directly in your instance if you’re logged in.
    2. Manual JSON import — copy the workflow JSON, then in your n8n editor click the three-dot menu → Import from File/URL and paste it in.

    Here’s a minimal example of what a template’s JSON structure looks like — this one triggers on a webhook and posts to Slack:

    {
      "nodes": [
        {
          "parameters": {
            "path": "server-alert",
            "httpMethod": "POST"
          },
          "name": "Webhook",
          "type": "n8n-nodes-base.webhook",
          "position": [250, 300]
        },
        {
          "parameters": {
            "channel": "#alerts",
            "text": "={{$json["message"]}}"
          },
          "name": "Slack",
          "type": "n8n-nodes-base.slack",
          "position": [500, 300]
        }
      ],
      "connections": {
        "Webhook": {
          "main": [[{ "node": "Slack", "type": "main", "index": 0 }]]
        }
      }
    }

    After importing, you’ll need to reattach your own credentials — templates never include live API keys or tokens, since those are stored separately in n8n’s credential vault.

    Building Your Own Custom Template

    Once you’re comfortable with imported templates, the next step is building your own and saving it for reuse across projects. A good pattern:

    1. Build the workflow using real nodes and test it end-to-end
    2. Remove or placeholder any credentials so it’s shareable
    3. Export via the three-dot menu → Download
    4. Store the JSON in a git repo alongside your infrastructure-as-code so it’s versioned like everything else

    This turns your automation into something reproducible — if you rebuild your server, you re-import the template instead of rebuilding logic from memory.

    Best n8n Templates for DevOps and Monitoring

    A few template categories are especially useful for infrastructure-focused teams:

  • Uptime alerting — poll an endpoint or integrate with BetterStack and push failures to Slack/Discord/PagerDuty
  • Deployment notifications — trigger on GitHub Actions or GitLab CI webhooks and post deploy status to a team channel
  • Log digesting — pull logs from a source, filter by severity, and summarize into a daily digest
  • Automated backups — schedule a cron trigger to dump a database and push it to S3-compatible object storage
  • SSL expiry checks — hit your domains on a schedule and alert before certificates expire
  • If you’re already using an uptime monitor, BetterStack pairs particularly well with n8n since it exposes webhooks you can wire directly into an alerting workflow — no custom polling logic required.

    Scaling n8n in Production

    As your workflow count grows, a single Docker container running SQLite will start to show its limits. At that point:

  • Move to Postgres for the database backend
  • Split execution workers from the main process using n8n’s queue mode (Redis-backed)
  • Monitor container health with something like our self-hosted monitoring tools guide
  • Set resource limits in your Compose file so a runaway workflow can’t take down the host
  • For teams running dozens of active workflows, a dedicated 2-4 vCPU instance is a reasonable baseline — both DigitalOcean and Hetzner offer droplets/VPS instances in that range for a few dollars a month, which is dramatically cheaper than most SaaS automation tiers once you’re past a handful of workflows.

    Recommended: Ready to put this into practice? DigitalOcean is a tool we use for exactly this, and we have a real, disclosed affiliate relationship with them.

    FAQ

    What is an n8n template exactly?
    It’s a pre-built n8n workflow exported as JSON that you can import into your own instance, then customize with your own credentials and endpoints instead of building the workflow from scratch.

    Are n8n templates free to use?
    Most templates in the official n8n.io/workflows library are free and open. Some community creators publish paid templates for more complex use cases, but the majority you’ll need for DevOps automation are free.

    Do I need to self-host n8n to use templates?
    No — templates work on both n8n Cloud and self-hosted instances. Self-hosting via Docker gives you more control over data and credentials, which matters if your workflows touch internal infrastructure secrets.

    Can a template include credentials?
    No. Exported templates never include live credential values. You reconnect credentials manually after import, which keeps shared templates safe to publish or hand off to teammates.

    What’s the difference between a workflow and a template?
    They’re the same underlying structure — a template is simply a workflow that’s been exported, generalized, and shared for reuse rather than kept private in one instance.

    How do I back up my n8n templates?
    Export each workflow as JSON and store it in a git repository. This gives you version history and lets you redeploy workflows quickly if you rebuild your n8n instance.

    Wrapping Up

    Starting from an n8n template rather than a blank canvas is the single biggest time-saver for teams adopting workflow automation. Get a Docker-based instance running behind a reverse proxy, pull a few templates relevant to your infrastructure needs, and start customizing from there. Once you outgrow SQLite and single-container deployments, moving to Postgres and queue mode keeps things stable as your automation footprint grows.

  • n8n Self Hosted: Full Docker Installation Guide 2026

    n8n Self Hosted: The Complete Docker Setup and Hardening Guide

    Disclosure: This post contains one or more links to providers we have a real, registered affiliate/referral relationship with. We may earn a commission at no extra cost to you if you sign up through them.

    If you’re tired of paying monthly fees for Zapier or Make, running n8n self hosted on your own VPS is one of the highest-leverage moves you can make as a developer or sysadmin. You get unlimited workflow executions, full data ownership, and no per-task billing — for the cost of a $6/month server.

    This guide covers everything you need to run n8n self hosted in production: Docker Compose setup, reverse proxy configuration, database choice, backups, and scaling with queue mode.

    Why Self-Host n8n

    n8n is an open-source workflow automation tool — think Zapier, but with a visual node editor and the ability to write custom JavaScript inside any node. The hosted cloud version is convenient, but it caps executions and charges per active workflow. Self-hosting removes both limits.

    When you run n8n self hosted, you also get:

  • Full control over data retention and where your credentials are stored
  • No vendor lock-in — export/import workflows as JSON anytime
  • The ability to run community and custom nodes that aren’t approved for n8n Cloud
  • Direct network access to internal services (databases, internal APIs) without exposing them publicly
  • The tradeoff is that you own the ops burden: updates, backups, uptime, and security. This guide addresses all four.

    n8n vs Zapier and Make

    If you’re evaluating whether to self-host at all, the deciding factor is usually volume and complexity. Zapier and Make are fine for a handful of simple, low-frequency automations. Once you’re running dozens of workflows with branching logic, webhooks, and custom code, the per-task pricing on those platforms gets expensive fast, and n8n self hosted becomes the more economical option, especially at scale.

    Prerequisites

    Before you start, you’ll need:

  • A VPS with at least 1 vCPU and 2GB RAM (n8n is fairly lightweight, but PostgreSQL and Redis add overhead if you use queue mode)
  • Docker and Docker Compose installed
  • A domain name you control, with DNS pointed at your server’s IP
  • Basic familiarity with the command line
  • If you don’t already have a VPS, DigitalOcean and Hetzner both offer affordable droplets/instances that are more than capable of running n8n comfortably — a $12/month droplet handles most single-user workloads without issue.

    Installing n8n with Docker

    The fastest, most maintainable way to run n8n self hosted is with Docker Compose. This keeps n8n, its database, and any supporting services isolated and easy to upgrade.

    First, create a project directory and the required subfolders:

    mkdir -p ~/n8n-stack/local-files
    cd ~/n8n-stack

    Docker Compose Setup

    Create a docker-compose.yml file with the following contents:

    version: '3.8'
    
    services:
      postgres:
        image: postgres:16
        restart: unless-stopped
        environment:
          - POSTGRES_USER=n8n
          - POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
          - POSTGRES_DB=n8n
        volumes:
          - postgres_data:/var/lib/postgresql/data
        healthcheck:
          test: ['CMD-SHELL', 'pg_isready -U n8n']
          interval: 5s
          timeout: 5s
          retries: 10
    
      n8n:
        image: docker.n8n.io/n8nio/n8n:latest
        restart: unless-stopped
        ports:
          - '127.0.0.1:5678:5678'
        environment:
          - N8N_HOST=${DOMAIN_NAME}
          - N8N_PROTOCOL=https
          - N8N_PORT=5678
          - WEBHOOK_URL=https://${DOMAIN_NAME}/
          - DB_TYPE=postgresdb
          - DB_POSTGRESDB_HOST=postgres
          - DB_POSTGRESDB_DATABASE=n8n
          - DB_POSTGRESDB_USER=n8n
          - DB_POSTGRESDB_PASSWORD=${POSTGRES_PASSWORD}
          - N8N_ENCRYPTION_KEY=${N8N_ENCRYPTION_KEY}
          - GENERIC_TIMEZONE=UTC
        volumes:
          - n8n_data:/home/node/.n8n
          - ./local-files:/files
        depends_on:
          postgres:
            condition: service_healthy
    
    volumes:
      postgres_data:
      n8n_data:

    Next, create a .env file to hold your secrets — never commit this to version control:

    cat > .env << 'EOF'
    DOMAIN_NAME=n8n.yourdomain.com
    POSTGRES_PASSWORD=change_this_to_a_long_random_string
    N8N_ENCRYPTION_KEY=change_this_to_another_long_random_string
    EOF

    Generate strong random values instead of the placeholders:

    openssl rand -hex 32

    Run that command twice and paste the outputs into POSTGRES_PASSWORD and N8N_ENCRYPTION_KEY respectively. The encryption key is critical — it encrypts stored credentials, and if you lose it, you lose access to every saved credential in your workflows. Back it up somewhere safe outside the server.

    Now bring the stack up:

    docker compose up -d

    Check that both containers are healthy:

    docker compose ps
    docker compose logs -f n8n

    At this point n8n is running on 127.0.0.1:5678, bound only to localhost. It isn’t reachable from the internet yet — that’s intentional. We’ll expose it safely through a reverse proxy in the next section.

    Setting Up a Reverse Proxy with HTTPS

    Running n8n behind a reverse proxy with TLS is non-negotiable for anything beyond local testing, since n8n handles webhooks and stored credentials. Nginx with Let’s Encrypt is the simplest combination.

    Install Nginx and Certbot on the host:

    sudo apt update
    sudo apt install -y nginx certbot python3-certbot-nginx

    Create a server block at /etc/nginx/sites-available/n8n:

    server {
        listen 80;
        server_name n8n.yourdomain.com;
    
        location / {
            proxy_pass http://127.0.0.1:5678;
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection 'upgrade';
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
            proxy_cache_bypass $http_upgrade;
        }
    }

    Enable the site and issue a certificate:

    sudo ln -s /etc/nginx/sites-available/n8n /etc/nginx/sites-enabled/
    sudo nginx -t && sudo systemctl reload nginx
    sudo certbot --nginx -d n8n.yourdomain.com

    Certbot will automatically rewrite the config for HTTPS and set up renewal. If you’d rather use Traefik with automatic Let’s Encrypt via Docker labels, our Traefik reverse proxy guide walks through that setup in detail, and it pairs well with the Compose file above.

    Database Configuration: SQLite vs PostgreSQL

    By default, n8n ships with SQLite, which is fine for quick testing but not recommended for production. SQLite struggles under concurrent writes, and a corrupted database file can silently break your entire instance with no easy recovery path. PostgreSQL, as configured in the Compose file above, handles concurrent workflow executions far more reliably and makes backups straightforward with standard tooling like pg_dump.

    If you’re already running PostgreSQL for other services, you can point n8n at that instance instead of running a dedicated container — just make sure the DB_POSTGRESDB_HOST environment variable resolves correctly from inside the n8n container’s network.

    Securing Your n8n Instance

    A self-hosted automation tool that stores API keys and OAuth tokens for every service you connect is a high-value target if compromised. Treat it accordingly.

  • Enable n8n’s built-in user management (Settings → Users) so the instance isn’t wide open to anyone with the URL
  • Restrict SSH access to key-based auth only and disable root login
  • Keep the Docker image updated regularly — docker compose pull && docker compose up -d pulls the latest patched release
  • Set N8N_BLOCK_ENV_ACCESS_IN_NODE=true to prevent workflow code nodes from reading host environment variables
  • Put the server behind a firewall that only allows ports 22, 80, and 443
  • For a full server hardening checklist beyond n8n specifically, see our guide on securing a self-hosted VPS, which covers fail2ban, unattended upgrades, and SSH hardening in more depth.

    If uptime matters for your automations — for example, workflows that process incoming webhooks from payment providers — it’s worth pairing your instance with an external monitoring service. BetterStack offers uptime monitoring and status pages that will alert you immediately if your n8n instance or its webhook endpoint goes down, which is far better than discovering a failed workflow days later.

    Backups

    Backups need to cover two things: the PostgreSQL database (your workflow definitions and execution history) and the n8n_data volume (which holds the encryption key and local file storage).

    A simple daily backup script:

    #!/bin/bash
    set -euo pipefail
    
    BACKUP_DIR=/root/n8n-backups
    TIMESTAMP=$(date +%Y%m%d-%H%M%S)
    mkdir -p "$BACKUP_DIR"
    
    docker compose exec -T postgres pg_dump -U n8n n8n | gzip > "$BACKUP_DIR/n8n-db-$TIMESTAMP.sql.gz"
    docker run --rm -v n8n-stack_n8n_data:/data -v "$BACKUP_DIR":/backup alpine 
      tar czf "/backup/n8n-data-$TIMESTAMP.tar.gz" -C /data .
    
    find "$BACKUP_DIR" -type f -mtime +14 -delete

    Schedule it with cron:

    crontab -e
    # Add the following line:
    0 3 * * * /root/n8n-backup.sh >> /var/log/n8n-backup.log 2>&1

    Copy the backups off the server itself — to object storage or another machine — since a backup that lives only on the server it’s protecting isn’t a real backup.

    Scaling n8n with Queue Mode

    If you’re running high-volume or long-running workflows, the default setup can become a bottleneck because a single n8n process handles both the UI and execution. Queue mode splits this: a main process handles the editor and webhook triggers, while separate worker processes pull jobs from a Redis-backed queue and execute them.

    To enable it, add a Redis service to your Compose file and set these additional environment variables on the n8n service:

      redis:
        image: redis:7-alpine
        restart: unless-stopped

    EXECUTIONS_MODE=queue
    QUEUE_BULL_REDIS_HOST=redis

    Then run a separate worker container using the same image with the command n8n worker. This lets you scale workers horizontally as workflow volume grows, without touching the main instance.

    Monitoring and Observability

    Once n8n is handling anything business-critical, blind spots become expensive. At minimum, track:

  • Container health via docker compose ps and restart policies
  • Disk usage on the volumes, since execution history can grow unbounded if you don’t configure EXECUTIONS_DATA_PRUNE
  • HTTP response codes on the reverse proxy for failed webhook deliveries
  • Our Docker Compose monitoring guide shows how to wire up Prometheus and Grafana against a Compose stack like this one if you want dashboards rather than just log tailing.

    Recommended: Want to explore DigitalOcean yourself? DigitalOcean is a direct vendor link (not an affiliate/tracked link).

    FAQ

    Is n8n free to self-host?
    Yes. The core n8n software is fair-code licensed and free to self-host with no execution limits. You only pay for the server it runs on and optional enterprise features if you need SSO or advanced permissions.

    How much does it cost to run n8n self hosted?
    A small VPS with 1-2 vCPUs and 2GB of RAM, typically $6-12/month from providers like DigitalOcean or Hetzner, is enough for most individual or small-team workloads.

    Do I need PostgreSQL, or can I stick with SQLite?
    SQLite works for testing, but PostgreSQL is strongly recommended for production because it handles concurrent executions more reliably and supports standard backup tooling.

    Can I run n8n without Docker?
    Yes, n8n can be installed via npm directly on the host, but Docker is recommended because it isolates dependencies, simplifies upgrades, and matches the environment n8n is tested against.

    How do I update a self-hosted n8n instance safely?
    Back up your database and volumes first, then run docker compose pull followed by docker compose up -d. Check the n8n release notes for breaking changes before upgrading across major versions.

    Is it safe to expose n8n’s webhook URLs publicly?
    Yes, that’s expected usage, but only expose them behind HTTPS via a reverse proxy, and enable user authentication on the editor UI so the workflow builder itself isn’t publicly accessible.

    Final Thoughts

    Running n8n self hosted takes maybe 30 minutes of setup time and gives you a private, unlimited automation platform that competes with tools costing hundreds of dollars a month at scale. Start with the Docker Compose setup above, put it behind HTTPS, automate your backups, and you’ll have a production-grade instance before the end of the afternoon.

  • SEO Automation Platform Guide for DevOps Teams 2026

    SEO Automation Platform: Build a Self-Hosted Stack for Scalable Search Growth

    Disclosure: This post contains one or more links to providers we have a real, registered affiliate/referral relationship with. We may earn a commission at no extra cost to you if you sign up through them.

    If you manage SEO for more than a handful of sites, you already know the pain: five different SaaS dashboards, five different invoices, and none of them talk to each other. A self-hosted SEO automation platform solves that by pulling rank tracking, crawling, log analysis, and alerting into one stack you control. This guide walks through the actual components, the Docker configuration, and the cron jobs that keep it running without babysitting.

    Why Most Teams Outgrow SaaS-Only SEO Tools

    Commercial rank trackers and audit tools are fine when you’re managing one site with a small keyword list. The moment you’re running audits across a portfolio of domains, tracking thousands of keywords, or need custom alerting tied to deploys, the per-seat and per-keyword pricing model starts eating your margin. Worse, most SaaS tools don’t expose raw data easily — you get a dashboard, not a database you can query.

    The Hidden Cost of Per-Seat SEO Subscriptions

    The sticker price is rarely the real cost. Once you factor in the extras, a “simple” SEO stack adds up fast:

  • Rank tracking tiers that charge per keyword, not per site
  • Crawl tools that cap URLs per audit and charge overages
  • Separate uptime/alerting tools with their own billing
  • No API access without an enterprise plan
  • Vendor lock-in on historical ranking data
  • A self-hosted platform trades a few hours of setup for full ownership of that data and no recurring per-keyword fees.

    What “Automation” Actually Means Here

    Automation in this context isn’t a single tool — it’s a pipeline. Containers crawl your sites on a schedule, write results to a shared database, a script diffs the results against the previous run, and alerts fire only when something meaningful changes (a ranking drop, a spike in 404s, a broken canonical tag). The goal is to never manually open a spreadsheet to check if something broke.

    Core Components of a Self-Hosted SEO Automation Platform

    Before writing any config, it helps to know what you’re actually assembling. A functional platform needs:

  • A rank-tracking service (open-source or API-driven)
  • A crawler for technical audits (broken links, duplicate titles, canonical issues)
  • A log-file parser for crawl-budget analysis
  • A scheduler (cron or a job runner) to trigger everything
  • A database to store historical results
  • An alerting layer for regressions
  • Each of these can run as its own Docker container, which keeps the stack portable across any VPS provider.

    Rank Tracking Container

    Most teams start here because it’s the highest-visibility metric. Instead of paying per keyword, you can run an open-source tracker or a lightweight scraper against Google’s SERPs via a licensed API, storing results in Postgres. Here’s a minimal docker-compose.yml for the tracking layer:

    version: "3.9"
    services:
      rank-tracker-db:
        image: postgres:16
        environment:
          POSTGRES_USER: seo
          POSTGRES_PASSWORD: changeme
          POSTGRES_DB: rankings
        volumes:
          - rank_data:/var/lib/postgresql/data
    
      rank-tracker:
        image: ghcr.io/example/rank-tracker:latest
        depends_on:
          - rank-tracker-db
        environment:
          DB_HOST: rank-tracker-db
          SEARCH_API_KEY: ${SEARCH_API_KEY}
        volumes:
          - ./keywords.csv:/app/keywords.csv:ro
    
    volumes:
      rank_data:

    The keywords.csv file holds your tracked terms and target URLs; the container runs on a schedule (set via the platform’s own cron config or an external scheduler) and writes daily snapshots to Postgres.

    Automated Crawling and Technical Audits

    For technical audits, Screaming Frog SEO Spider has a headless CLI mode that runs well inside a container, exporting CSVs you can diff programmatically. A simple wrapper script triggered nightly looks like this:

    #!/usr/bin/env bash
    set -euo pipefail
    
    SITE_URL="https://example.com"
    OUTPUT_DIR="/data/audits/$(date +%F)"
    mkdir -p "$OUTPUT_DIR"
    
    docker run --rm 
      -v "$OUTPUT_DIR:/output" 
      screamingfrog/seo-spider:latest 
      --crawl "$SITE_URL" 
      --headless 
      --output-folder /output 
      --save-crawl 
      --export-tabs "Internal:All,Response Codes:All"
    
    echo "Audit complete: $OUTPUT_DIR"

    Pipe the CSV output into a small parser that compares today’s broken-link count against yesterday’s, and you have automated regression detection without opening a single dashboard.

    Log-File Analysis for Crawl Budget

    Rank position tells you what happened; server logs tell you why. Parsing raw access logs for Googlebot hits shows you exactly which pages get crawled, how often, and which ones are being ignored. A basic pipeline uses goaccess or a custom Python script to filter user-agent strings matching Googlebot, bucket requests by path, and flag pages that haven’t been crawled in 30+ days — often a sign of weak internal linking or a noindex mistake.

    Deploying the Stack on a VPS

    All of this needs somewhere to run. For a stack this size — a handful of containers, a database, and scheduled jobs — a mid-tier VPS is plenty. We’ve had good results running similar Docker stacks on Hetzner and DigitalOcean droplets; both give you predictable pricing and fast NVMe storage, which matters when Postgres is writing ranking snapshots daily. If you’re comparing options, our guide on choosing a VPS for Docker workloads breaks down CPU and IOPS tradeoffs in more detail.

    Provisioning is straightforward once Docker and Compose are installed:

    curl -fsSL https://get.docker.com | sh
    sudo usermod -aG docker $USER
    mkdir -p /opt/seo-stack && cd /opt/seo-stack
    git clone https://github.com/example/seo-automation-stack.git .
    docker compose up -d

    Scheduling Jobs with Cron and Webhooks

    With containers running, the scheduler ties everything together. A basic crontab on the host handles daily and weekly jobs:

    # Rank tracking - daily at 3am
    0 3 * * * docker exec rank-tracker /app/run.sh
    
    # Technical audit - weekly on Monday at 4am
    0 4 * * 1 /opt/seo-stack/scripts/run-audit.sh
    
    # Log parsing - daily at 5am
    0 5 * * * /opt/seo-stack/scripts/parse-logs.sh

    For anything that needs to run outside the schedule — say, right after a deploy — a webhook endpoint (a small Flask or Express route) can trigger the same scripts on demand, which is useful for re-auditing right after you ship a template change.

    Alerting When Rankings or Uptime Drop

    Automation is only useful if failures reach a human. Pair the stack with an uptime and alerting service so you’re notified the moment a monitored page starts returning errors or a crawl job fails silently. We use BetterStack for this layer — it handles both uptime monitoring and log-based alerting, and integrates cleanly via webhook with the scripts above so a failed audit or a sudden ranking drop pages you directly instead of sitting unnoticed in a CSV file.

    Reporting Without Manual Spreadsheet Work

    Once data is flowing into Postgres, a lightweight Python script can generate weekly summary reports automatically instead of someone manually exporting charts:

    import psycopg2
    import csv
    from datetime import date, timedelta
    
    conn = psycopg2.connect(host="rank-tracker-db", dbname="rankings", user="seo")
    cur = conn.cursor()
    
    last_week = date.today() - timedelta(days=7)
    cur.execute(
        "SELECT keyword, position, checked_at FROM rankings WHERE checked_at >= %s ORDER BY keyword",
        (last_week,)
    )
    
    with open("weekly_report.csv", "w", newline="") as f:
        writer = csv.writer(f)
        writer.writerow(["keyword", "position", "checked_at"])
        writer.writerows(cur.fetchall())
    
    print("Report generated: weekly_report.csv")

    Schedule this alongside the other cron jobs and pipe the CSV into whatever BI tool or Slack integration your team already uses. The point isn’t a fancier chart — it’s removing the manual export step entirely.

    Scaling and Hardening the Platform

    As the stack grows past a couple of sites, put a CDN and WAF in front of anything web-facing, including your reporting dashboard. Cloudflare is a solid default here — free tier DNS proxying and basic firewall rules stop most scraper and bot noise before it ever reaches your VPS, which matters since your crawler containers are themselves generating meaningful outbound traffic that can look suspicious to your own hosting provider if unmanaged.

    If you eventually need managed rank-tracking data alongside your self-hosted audits — for competitor tracking at scale, for instance — a tool like SE Ranking can complement the stack rather than replace it, feeding its API output into the same Postgres database so all your historical data lives in one place regardless of source. For teams already running a broader observability setup, our self-hosted monitoring stack guide covers how to fold these SEO containers into the same Grafana dashboards you’re using for uptime.

    Before scaling further, double check container resource limits in your Compose file — an unthrottled crawler can consume all available memory during a large site audit and take down the rank-tracker container alongside it. Setting mem_limit and cpus per service avoids that failure mode entirely.

    Recommended: Ready to put this into practice? SE Ranking is a tool we use for exactly this, and we have a real, disclosed affiliate relationship with them.

    FAQ

    Do I need to be a developer to run a self-hosted SEO automation platform?
    Basic comfort with the command line and Docker is enough. You don’t need to write the tools from scratch — most of the stack is existing open-source containers wired together with cron and a shared database.

    How much does it cost to run compared to SaaS tools?
    A mid-tier VPS running the full stack typically costs $20–$40/month, regardless of how many keywords or sites you track. Compare that to per-keyword SaaS pricing, which scales linearly with your portfolio size.

    Can this replace tools like Ahrefs or SEMrush entirely?
    Not fully — those tools include massive backlink indexes that are expensive to replicate. This platform is best for rank tracking, technical audits, and log analysis; keep a SaaS subscription for backlink data if you rely on it heavily.

    What happens if a container crashes overnight?
    That’s what the alerting layer is for. With BetterStack or a similar webhook-based monitor watching job completion, a failed cron run pages you instead of silently skipping a day of data.

    Is it safe to run crawlers against my own production site?
    Yes, as long as you rate-limit requests and respect your own robots.txt. Set a reasonable crawl delay in the Screaming Frog config to avoid triggering your own WAF or rate limiter.

    How do I back up the ranking history?
    Since everything lives in Postgres, a nightly pg_dump to object storage (or a snapshot through your VPS provider) is sufficient. Add it as another line in the same crontab used for the rest of the pipeline.

  • Redis Docker Compose: The Complete Setup Guide

    Redis Docker Compose: The Complete Setup and Caching Guide

    Disclosure: This post contains one or more links to providers we have a real, registered affiliate/referral relationship with. We may earn a commission at no extra cost to you if you sign up through them.

    Adding a cache layer used to mean provisioning a separate service, wiring up network rules, and hoping the versions matched. A Redis Docker Compose setup collapses all of that into a few lines of YAML: one service block, one shared network, and Redis is reachable from every other container in your stack by name.

    This guide walks through a production-ready Redis Docker Compose configuration, how to persist data across restarts, how to secure the connection with a password, and the mistakes that cause the most confusion when people wire Redis into an existing Postgres Docker Compose stack.

    What a Redis Docker Compose Setup Actually Gives You

    At its simplest, a Redis Docker Compose service is just an image reference and a port:

    yaml
    services:
    cache:
    image: redis:7-alpine
    ports:
    - "6379:6379"
    `

    That's enough to get Redis running, but a real Redis Docker Compose stack needs three more things most tutorials skip: a named volume for persistence, a password, and a healthcheck so dependent services don't start before Redis is actually ready to accept connections.

    A Complete Redis Docker Compose Configuration

    Here's a realistic Redis Docker Compose file for an API that uses Redis for session storage and caching, alongside Postgres for durable data:

    `yaml
    services:
    api:
    build: .
    environment:
    - REDIS_URL=redis://:$REDIS_PASSWORD@cache:6379
    depends_on:
    cache:
    condition: service_healthy

    cache:
    image: redis:7-alpine
    command: redis-server --requirepass $REDIS_PASSWORD --appendonly yes
    volumes:
    - redis_data:/data
    healthcheck:
    test: [CMD, redis-cli, --raw, -a, $REDIS_PASSWORD, ping]

    volumes:
    redis_data:
    `

    Every other service resolves the cache by its Compose service name, cache:6379, because Compose gives the project its own internal bridge network automatically. That's the core advantage of a Redis Docker Compose deployment over running docker run redis by hand.

    Why the Healthcheck Matters

    Without a healthcheck, depends_on only waits for the Redis container to start, not for the Redis server inside it to finish loading. The condition: service_healthy clause blocks the api service until redis-cli ping actually succeeds.

    Persisting Redis Data with Docker Compose Volumes

    By default, Redis is an in-memory store, restart the container and everything is gone. A Redis Docker Compose setup that cares about surviving restarts needs a named volume plus one of Redis's two persistence modes.

  • RDB snapshotting: periodic point-in-time dumps, smaller files, faster restarts, but you can lose the last few minutes of writes
  • AOF append-only file: logs every write operation, safer, larger on disk, slightly slower recovery on huge datasets
  • Checking That Persistence Is Actually Working

    `bash
    docker compose exec cache redis-cli -a $REDIS_PASSWORD set healthcheck ok
    docker compose down
    docker compose up -d
    docker compose exec cache redis-cli -a $REDIS_PASSWORD get healthcheck
    `

    If the second get returns ok, your Redis Docker Compose volume is correctly mounted and persistence is active.

    Securing a Redis Docker Compose Service

    Redis binds with no authentication by default, which is fine on an isolated Compose network but dangerous the moment a port gets accidentally exposed to the host or the internet.

  • Set –requirepass so every client needs a password, injected via a .env file
  • Drop the ports: mapping entirely unless you need to reach Redis from outside the Compose network
  • Common Redis Docker Compose Patterns

  • Session store: Express, Django, and Rails all have first-party Redis session adapters
  • Rate limiting: token-bucket limiters backed by Redis's atomic INCR and EXPIRE commands
  • Job queues: libraries like BullMQ or Celery use Redis as the broker between web and worker processes
  • Full-page and query caching: cache expensive database results with a TTL
  • Troubleshooting a Redis Docker Compose Stack

  • Connection refused from the app container: add the healthcheck shown above instead of a fixed sleep
  • NOAUTH Authentication required: confirm the connection string includes the password segment
  • Data disappears after docker compose down: check the volumes: mapping, or you ran docker compose down -v
  • High memory usage: set maxmemory and maxmemory-policy allkeys-lru in the command: block
  • Hosting a Redis Docker Compose Stack in Production

    Redis is memory-hungry by design. If you're outgrowing a budget droplet, DigitalOcean makes it easy to resize RAM, and Hetzner is a solid budget option. BetterStack gives you uptime monitoring for a wedged Redis container, and Cloudflare keeps DNS and TLS predictable in front of your API.

    Once your Redis Docker Compose service is stable, docker compose down is the safe way to stop it without touching the redis_data volume. Our guide to Docker Compose environment variables covers .env file precedence in more depth.

    Monitoring a Redis Docker Compose Service

    Once a Redis Docker Compose stack is running in production, memory pressure is the metric that matters most, since Redis keeps its entire working set in RAM. Two commands make quick diagnostics easy:

    `bash
    docker compose exec cache redis-cli -a $REDIS_PASSWORD info memory
    docker compose exec cache redis-cli -a $REDIS_PASSWORD info stats
    `

    The info memory output shows used_memory_human and maxmemory_human; if the two are converging, it's time to either raise the container's memory limit or tighten your maxmemory-policy. The info stats output includes keyspace_hits and keyspace_misses — a rising miss ratio over time usually means your cache TTLs are too short or your working set has outgrown the box.

    Setting Resource Limits in the Compose File

    A Redis Docker Compose service without a memory ceiling can consume all available host RAM during a traffic spike, starving the database and app containers sharing the same VPS. Pin it explicitly:

    `yaml
    cache:
    image: redis:7-alpine
    deploy:
    resources:
    limits:
    memory: 512M
    `

    Combined with maxmemory 400mb inside the command: block, this keeps a single Redis Docker Compose service from taking down its neighbors when key eviction alone isn't fast enough.

    Redis Docker Compose with Multiple Logical Databases

    Redis ships with 16 numbered logical databases (0-15) inside a single instance, selectable with SELECT n from redis-cli. For a small Redis Docker Compose stack running a cache and a job queue side by side, putting each on its own numbered database avoids a FLUSHDB in one context accidentally wiping the other. For anything beyond a handful of services, most teams prefer key prefixing (session:, queue:, cache:) over numbered databases, since prefixes show up clearly in monitoring dashboards while a bare database number does not.

    Recommended: Want to explore DigitalOcean yourself? DigitalOcean is a direct vendor link (not an affiliate/tracked link).

    FAQ

    Do I need a separate Dockerfile for Redis in a Docker Compose stack?
    No. Use the official
    redis:7-alpine image directly in your Redis Docker Compose file — you only need a custom Dockerfile if you're baking in custom modules that can't be passed via command: flags.

    Does Redis Docker Compose data survive docker compose down?
    Yes, as long as you're using a named volume and skip the
    -v flag. Plain docker compose down removes containers and networks but leaves volumes, and therefore your Redis data, intact.

    What's the difference between RDB and AOF persistence in a Redis Docker Compose setup?
    RDB snapshots recover faster but can lose a few minutes of recent writes; AOF logs every write and is safer but slightly slower to replay on restart. Many production Redis Docker Compose stacks enable both for defense in depth.

    Can multiple services share one Redis Docker Compose instance?
    Yes — use separate numbered Redis databases or key prefixing per service, so a cache flush in one service doesn't wipe session data for another.

    Is it safe to run one Redis Docker Compose instance for both caching and a job queue?
    Yes, as long as you isolate them with separate logical databases or key prefixes and set eviction policies that only apply to cache keys — you don't want
    allkeys-lru evicting queue jobs still waiting to be processed.

    How much RAM does a small Redis Docker Compose service actually need?
    For a low-traffic app using Redis purely for sessions and light caching, 128-256MB is typically enough; job queues holding large payloads need considerably more, and
    info memory is the fastest way to check actual usage rather than guessing.

    Wrapping Up

    A Redis Docker Compose setup is a handful of YAML lines once you know the pieces that matter: a named volume for persistence, –requirepass plus a .env` file for security, a healthcheck so dependent services wait for Redis to be truly ready, and a memory limit so one noisy cache doesn’t starve the rest of the stack. Get those right and Redis becomes a boring, reliable part of your stack instead of a source of cold-start races and vanished cache data.

  • n8n vs Make: Workflow Automation Comparison Guide 2026

    n8n vs Make: Which Workflow Automation Tool Should You Choose?

    Disclosure: This post contains one or more links to providers we have a real, registered affiliate/referral relationship with. We may earn a commission at no extra cost to you if you sign up through them.

    If you’ve spent any time evaluating workflow automation platforms, you’ve hit the n8n vs Make debate. Both tools let you connect APIs, automate repetitive tasks, and build integration pipelines without writing a full application from scratch — but they take fundamentally different approaches to deployment, pricing, and extensibility.

    This guide breaks down n8n vs Make from a developer and sysadmin perspective: what each tool actually does, how they handle self-hosting, what the real costs look like at scale, and which one you should pick for your specific use case.

    What Is n8n?

    n8n is an open-source, node-based workflow automation tool. It’s built in TypeScript, ships as a Docker image, and can be self-hosted on your own infrastructure or used via n8n Cloud. The name stands for “nodemation,” and the core pitch is fair-code licensing — you can view and modify the source, self-host for free, and only pay if you use their managed cloud offering or exceed certain usage thresholds under the Sustainable Use License.

    For developers, the killer feature is the Code node, which lets you drop raw JavaScript (or Python via a community node) directly into a workflow step. That means you’re never fully boxed in by the visual builder — if a connector doesn’t exist, you write it.

    Core n8n Features

  • Visual, node-based workflow editor with branching logic and error workflows
  • Native Docker support and official Helm charts for Kubernetes deployment
  • 400+ built-in integrations plus HTTP Request nodes for anything else
  • Self-hosted instances have no artificial execution caps — you’re limited by your own server resources
  • Built-in credential encryption and support for external secret managers (Vault, AWS Secrets Manager)
  • What Is Make (formerly Integromat)?

    Make is a cloud-only visual automation platform, rebranded from Integromat in 2022. It uses a “scenario” model — modules connected on a canvas — and is generally considered more polished and beginner-friendly than n8n out of the box. Make does not offer a self-hosted version; everything runs on their infrastructure, and pricing is based on “operations” consumed per billing cycle.

    Make shines for non-technical teams who want drag-and-drop automation without touching a terminal. It has strong error-handling visualizations, a cleaner UI for mapping data between apps, and a large library of pre-built app connectors maintained by Make’s own team.

    Core Make Features

  • Visual scenario builder with real-time execution mapping
  • 2,000+ pre-built app integrations
  • Operation-based pricing (each module execution consumes one “operation”)
  • No self-hosting option — vendor lock-in is part of the deal
  • Built-in scheduling, webhooks, and API endpoint creation per scenario
  • n8n vs Make: Pricing Comparison

    This is where the two tools diverge hardest. Make bills you per operation, which sounds fine until a workflow runs thousands of times a day across multiple modules — costs scale directly with usage. n8n’s self-hosted tier is free (you only pay for your own server), while n8n Cloud uses execution-based pricing that’s generally more generous at the entry tier.

  • Make Free plan: 1,000 operations/month, limited to 2 active scenarios
  • Make Core plan: starts around $9/month for 10,000 operations
  • n8n self-hosted: $0 licensing cost, only infrastructure spend (a $6-$12/month VPS easily handles moderate workloads)
  • n8n Cloud Starter: starts around $20/month with generous execution limits
  • If you’re running high-volume workflows — say, syncing data every few minutes across multiple systems — self-hosted n8n is almost always cheaper long-term, because you’re paying for compute, not per-execution metering. We cover how to size that compute correctly in our guide to choosing a VPS for Docker workloads.

    Self-Hosting and Deployment

    This is the single biggest differentiator in the n8n vs Make comparison: n8n can run entirely on infrastructure you control, Make cannot.

    A minimal self-hosted n8n deployment with Docker Compose looks like this:

    yaml
    version: "3.8"
    services:
    n8n:
    image: n8nio/n8n:latest
    restart: unless-stopped
    ports:
    - "5678:5678"
    environment:
    - N8N_HOST=automation.yourdomain.com
    - N8N_PORT=5678
    - N8N_PROTOCOL=https
    - NODE_ENV=production
    - N8N_ENCRYPTION_KEY=${N8N_ENCRYPTION_KEY}
    - DB_TYPE=postgresdb
    - DB_POSTGRESDB_HOST=postgres
    - DB_POSTGRESDB_DATABASE=n8n
    - DB_POSTGRESDB_USER=n8n
    - DB_POSTGRESDB_PASSWORD=${POSTGRES_PASSWORD}
    volumes:
    - n8n_data:/home/node/.n8n
    depends_on:
    - postgres

    postgres:
    image: postgres:16-alpine
    restart: unless-stopped
    environment:
    - POSTGRES_USER=n8n
    - POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
    - POSTGRES_DB=n8n
    volumes:
    - postgres_data:/var/lib/postgresql/data

    volumes:
    n8n_data:
    postgres_data:
    `

    Run it with:

    `bash
    export N8N_ENCRYPTION_KEY=$(openssl rand -hex 24)
    export POSTGRES_PASSWORD=$(openssl rand -hex 16)
    docker compose up -d
    `

    Put this behind a reverse proxy with TLS (Caddy or Traefik both work well) and you have a production-grade automation server for the cost of a small VPS. We walk through the full reverse-proxy setup in our Docker Compose deployment guide.

    For reliable uptime on a self-hosted instance, a provider like DigitalOcean or Hetzner gives you predictable pricing and enough headroom to run n8n plus Postgres comfortably on a $6-$12/month droplet or CX22 instance. If uptime matters for business-critical automations, pair your instance with BetterStack for uptime monitoring and incident alerting — self-hosting means you own the reliability, so monitor accordingly.

    Make, by contrast, requires zero infrastructure knowledge. You sign up, connect your apps via OAuth, and build. That simplicity is the entire value proposition — but it also means you're fully dependent on Make's uptime, rate limits, and pricing changes.

    Ease of Use and Learning Curve

    Make generally wins on initial onboarding. Its module-mapping interface makes data transformation between apps visually intuitive — you click a field, drag it to the target, done. n8n's node editor is powerful but assumes more comfort with JSON, expressions, and occasionally raw code.

    That said, n8n's expression editor ({{ $json.fieldName }} syntax) is straightforward once you've used it a few times, and the built-in Code node means you're never stuck waiting for an official connector — you can just call the API directly:

    `javascript
    const response = await this.helpers.httpRequest({
    method: 'GET',
    url: 'https://api.example.com/v1/status',
    headers: {
    Authorization:
    Bearer ${$credentials.apiToken}
    }
    });

    return [{ json: response }];
    `

    Teams with at least one developer tend to outgrow Make's constraints faster than they outgrow n8n's, simply because n8n gives you an escape hatch into real code.

    Integrations and Extensibility

    Make has a larger catalog of pre-built connectors out of the box — roughly 2,000+ compared to n8n's 400+. But raw connector count is misleading: n8n's HTTP Request node combined with the Code node means you can integrate with virtually any REST or GraphQL API even without a dedicated node, whereas Make's no-code-only model makes truly custom integrations more cumbersome.

    n8n also supports community nodes — npm packages that extend functionality — and because it's open source, you can fork it and build entirely custom nodes for internal tools. Make has no equivalent; you're limited to what Make's team builds or what you can construct with their generic HTTP module.

    Performance and Scalability

    For high-throughput automation (thousands of executions per hour), self-hosted n8n scales with your infrastructure — you can run it in queue mode with Redis and multiple worker containers to horizontally scale execution:

    `bash
    docker run -d
    -e EXECUTIONS_MODE=queue
    -e QUEUE_BULL_REDIS_HOST=redis
    -e N8N_ENCRYPTION_KEY=$N8N_ENCRYPTION_KEY
    n8nio/n8n worker
    `

    Make's scalability is entirely dictated by your plan tier and their platform's rate limits. You can't add compute to speed up execution — you upgrade your plan or wait. For teams with unpredictable or bursty automation needs, that's a real constraint.

    Maintainability: Who's on the Hook When Something Breaks

    This is the tradeoff pricing comparisons usually skip. Make is fully managed: uptime, scaling, and platform updates are Make's responsibility, not yours. Self-hosted n8n moves that responsibility to you — you patch the Docker image, monitor the container, back up the Postgres database, and diagnose it yourself at 2am if a workflow silently stops firing. n8n Cloud sits in between: same n8n interface and self-hosting portability if you ever want to migrate off it, but Google/Make-style managed uptime.

    In practice, this means self-hosted n8n has a real ongoing time cost that a monthly Make subscription doesn't — acceptable if you (or your team) already run other Dockerized services and this is one more thing to monitor, less acceptable if automation is the only infrastructure you're maintaining.

    Common Use Cases for Each

  • Internal ops automation (Slack/email notifications, ticket routing, approval workflows): either tool works well; Make's polish wins for non-technical teams building these themselves.
  • Data sync between SaaS tools (CRM → spreadsheet → email tool): both handle this natively; n8n's Code node helps when the two tools' data models don't map cleanly.
  • Custom API integrations with unusual auth or pagination: n8n's ability to drop in raw JavaScript makes it noticeably easier when a service doesn't have a clean pre-built connector.
  • High-volume, scheduled batch jobs (thousands of executions/day): self-hosted n8n avoids Make's per-operation cost scaling; sizing your own server becomes the tradeoff instead.
  • Regulated or compliance-sensitive data: self-hosted n8n keeps data on infrastructure you control, which matters when a workflow touches data that can't leave a specific environment.
  • Which Should You Choose?

    The honest answer depends on who's running the automation:

  • Choose Make if you have a non-technical team, want zero infrastructure responsibility, and your automation volume is moderate and predictable.
  • Choose n8n if you have any DevOps or engineering capacity, want to avoid per-operation billing at scale, need custom code paths, or care about data residency and self-hosting for compliance reasons.
  • Choose n8n Cloud if you want n8n's flexibility without managing servers yourself, and you're fine paying for that convenience.
  • For most readers of a technical blog like this one — people comfortable with Docker, VPS management, and reverse proxies — self-hosted n8n is the more cost-effective and future-proof choice. You're not locked into a vendor's pricing changes, and you retain full control over your data and execution environment.

    If you're just getting started, spin up a test instance on a cheap VPS this week, connect two or three of your actual tools, and see how far the Code node takes you before deciding whether Make's polish is worth the recurring operation costs.

    Recommended: Ready to put this into practice? DigitalOcean is a tool we use for exactly this, and we have a real, disclosed affiliate relationship with them.

    FAQ

    Is n8n really free?
    Self-hosted n8n is free to run under its Sustainable Use License — you can use it for internal business automation without paying license fees. You only pay for the server it runs on. n8n Cloud (their managed hosting) is a separate paid product.

    Can I migrate workflows from Make to n8n?
    There's no automated migration tool between the two platforms since they use different scenario/workflow formats. You'll need to manually rebuild workflows, though the underlying logic (triggers, actions, conditionals) usually maps over conceptually without much friction.

    Is Make more reliable than self-hosted n8n?
    Make's infrastructure is professionally managed with high uptime SLAs on paid plans. A self-hosted n8n instance is only as reliable as the server and monitoring you set up around it — which is why pairing it with uptime monitoring is important for production use.

    Does n8n support webhooks like Make does?
    Yes. n8n has native Webhook trigger nodes that generate a unique URL per workflow, functionally equivalent to Make's webhook modules. You can test one instantly with a simple curl request:
    curl -X POST https://your-n8n-instance.com/webhook/test-id -d ‘{“key”:”value”}’`.

    Which tool is better for a solo developer?
    n8n tends to win for solo developers because it’s free to self-host, gives full code-level control, and doesn’t penalize experimentation with per-operation billing. Make’s free tier caps out quickly once you’re testing multiple workflows.

    Can I run n8n on Kubernetes instead of Docker Compose?
    Yes — n8n publishes official Helm charts, and the same queue-mode architecture used for Docker scaling applies to Kubernetes deployments, letting you run separate worker pods for execution load.

  • Complete Guide: Install n8n with Docker and HTTPS on Ubuntu 24.04

    Complete Guide: Install n8n with Docker and HTTPS on Ubuntu 24.04

    Installing n8n with Docker and HTTPS on Ubuntu 24.04 gets you a production-ready, self-hosted automation server in about 15 minutes: Docker for isolation, Postgres for persistent workflow storage, and Caddy for automatic, zero-config SSL.

    Why Use n8n with Docker on Ubuntu 24.04

    Running n8n in Docker instead of a bare-metal install keeps upgrades to a single docker compose pull && docker compose up -d, isolates n8n’s Node.js runtime from the rest of the system, and makes the whole stack reproducible if you ever need to rebuild the server. Pairing it with Postgres instead of the default SQLite avoids the file-locking issues SQLite runs into once you have more than a couple of concurrent workflow executions.

    Prerequisites Before Installing n8n

    You’ll need an Ubuntu 24.04 server with root or sudo access, a domain name pointed at the server’s IP (an A record), and ports 80 and 443 open in your firewall/security group — Caddy needs both to complete the automatic HTTPS certificate challenge.

    Install Docker on Ubuntu 24.04

    sudo apt update
    sudo apt install docker.io docker-compose-plugin -y
    sudo systemctl enable docker
    sudo systemctl start docker
    docker --version

    Create Docker Compose for n8n and Postgres

    Create a docker-compose.yml defining both services. n8n talks to Postgres over the internal Docker network, so only n8n’s port needs to be exposed to the host.

    version: "3.8"
    
    services:
      postgres:
        image: postgres:16
        restart: always
        environment:
          POSTGRES_USER: n8n
          POSTGRES_PASSWORD: strongpassword
          POSTGRES_DB: n8n
        volumes:
          - ./data/postgres:/var/lib/postgresql/data
    
      n8n:
        image: n8nio/n8n
        restart: always
        ports:
          - "5678:5678"
        environment:
          DB_TYPE: postgresdb
          DB_POSTGRESDB_HOST: postgres
          DB_POSTGRESDB_DATABASE: n8n
          DB_POSTGRESDB_USER: n8n
          DB_POSTGRESDB_PASSWORD: strongpassword
        depends_on:
          - postgres

    Replace strongpassword with a real generated password (e.g. openssl rand -base64 24) before you start the stack — don’t leave the placeholder in a production file.

    Configure HTTPS with Caddy

    Caddy issues and renews Let’s Encrypt certificates automatically the first time it sees a request for the domain, with no manual certbot step. Point your domain’s A record at the server first, then add a Caddyfile:

    automation.yourdomain.com {
        reverse_proxy localhost:5678
    }

    If n8n and Caddy are both containers on the same Docker network, use the n8n service name instead of localhost: reverse_proxy n8n:5678.

    Start n8n with Docker Compose

    docker compose up -d
    docker compose logs -f

    Verifying the Installation

    Run docker compose ps and confirm both n8n and postgres show a status of Up (or healthy if you’ve added healthchecks). Then open https://automation.yourdomain.com in a browser — you should land on n8n’s setup screen with a valid padlock/certificate, not a security warning. If the page doesn’t load, check the Troubleshooting section below before assuming the install failed; the two services almost always start correctly, and the reverse proxy or DNS record is the more common culprit.

    Quick Reference Commands

    # Check container status
    docker compose ps
    
    # Follow n8n logs only
    docker compose logs -f n8n
    
    # Restart just n8n after a config change
    docker compose restart n8n
    
    # Pull the latest n8n image and recreate
    docker compose pull n8n
    docker compose up -d n8n
    
    # Export all workflows before an update
    docker compose exec n8n n8n export:workflow --all --output=/home/node/backup.json

    Troubleshooting

    n8n container restarts in a loop: run docker compose logs n8n — this is almost always a database connection failure. Confirm DB_POSTGRESDB_PASSWORD in the n8n service matches POSTGRES_PASSWORD in the postgres service exactly, and that depends_on is giving Postgres enough time to accept connections before n8n’s first attempt.

    Browser shows a certificate warning or “connection refused”: this is a Caddy/DNS problem, not an n8n problem. Check docker compose logs caddy (or Caddy’s own log if it’s running outside Docker) — a failed ACME challenge usually means the domain’s A record isn’t pointing at this server yet, or port 80/443 is blocked by a firewall or cloud security group.

    Webhooks aren’t firing from external services: n8n needs to know its own public URL to generate correct webhook URLs. Set the WEBHOOK_URL environment variable (e.g. WEBHOOK_URL=https://automation.yourdomain.com/) on the n8n service and restart it.

    Security, Backups, and Maintenance

    Back up the ./data/postgres volume regularly — that’s where every workflow, credential, and execution history lives. A simple approach is docker compose exec postgres pg_dump -U n8n n8n > backup.sql on a cron schedule, or exporting workflows with the CLI command shown above before any update. Test upgrades on a non-production instance first: pull the new n8n image, check the release notes for breaking changes, then update production during a low-traffic window. Because Caddy terminates HTTPS in front of n8n, your login credentials and webhook payloads are encrypted in transit by default — there’s no separate TLS configuration to maintain on the n8n side.

    FAQ

    Do I need Postgres, or can I use the default SQLite?
    SQLite works for light personal use, but it locks the whole database file during writes, which causes errors once you have multiple workflows executing concurrently. Postgres is the recommended default for anything beyond a single-user test instance.

    Can I run this on a subdomain I already use for something else?
    Yes — Caddy can reverse-proxy multiple domains/subdomains from one Caddyfile, each with its own block. n8n itself doesn’t need a dedicated server.

    What Ubuntu versions besides 24.04 does this work on?
    The same Docker Compose file works on any Ubuntu LTS release with Docker installed (20.04, 22.04, 24.04) — 24.04 is used here because it’s the current LTS at time of writing.

    Final Thoughts

    Docker Compose, Postgres, and Caddy together give you a self-hosted n8n instance that’s easy to back up, easy to upgrade, and secured with HTTPS by default — a solid foundation whether you’re automating a handful of personal workflows or running production integrations. Once it’s running, the natural next steps are exploring pre-built n8n templates to speed up building your first workflows, or reading how n8n compares to hosted alternatives if you’re still deciding on a platform.