How to Reduce Your SaaS Bill with Self-Hosted Alternatives

Tools #selfhosted#saas#opensource#devops#savings
How to Reduce Your SaaS Bill with Self-Hosted Alternatives

What You’ll Need

  • n8n Cloud or self-hosted n8n for workflow automation
  • Hetzner VPS or Contabo VPS for hosting your self-hosted stack
  • Namecheap if you need a custom domain for your applications
  • DigitalOcean as an alternative hosting option
  • Basic familiarity with Docker and command-line interfaces
  • Around 30 minutes to set up your first stack

Table of Contents

  1. The Real Cost of SaaS Bloat
  2. Where Self-Hosted Wins
  3. Building Your First Self-Hosted Stack
  4. Migration Strategy: Moving Off Expensive Tools
  5. Security and Maintenance
  6. Getting Started

The Real Cost of SaaS Bloat

I used to have 14 active SaaS subscriptions. Slack at $8/user, GitHub at $4/user, Zapier for automation, Make for backup, text-to-speech services, image processing tools, video editing APIs—the list went on. At the end of every month, I’d watch roughly $2,400 disappear from my company’s account.

The problem isn’t that these tools are bad. They’re excellent. But when you’re running a small team or bootstrapped startup, that money could be reinvested in actual product development or hiring. I decided to audit which tools were actually worth their cost and which ones I could replace.

Here’s what I discovered: about 70% of what I was paying for could be handled by self-hosted open-source alternatives running on a $10-30/month VPS. The remaining 30% were genuinely specialized services worth keeping (like Stripe for payments—there’s no good self-hosted alternative).

This shift didn’t happen overnight, but in 12 months, I cut my SaaS bill from $2,400 to under $400 monthly. More importantly, I gained complete control over my data and workflows.

Where Self-Hosted Wins

Not every SaaS should be replaced. But certain categories are ripe for disruption:

Workflow Automation: Tools like Zapier charge based on tasks or users. When you move to self-hosted n8n , you pay once for the server and can create unlimited workflows. If you’re trying to decide between cloud and self-hosted, our detailed comparison of self-hosted n8n vs n8n Cloud breaks down exactly when each makes sense.

Media Processing: APIs for text-to-speech, video transcoding, and image manipulation are absurdly expensive at scale. I replaced multiple services with Edge TTS, which is completely free and requires no API keys . For video work, ffmpeg is a beast —command-line based but incredibly powerful once you learn the syntax.

Internal Tools: Databases, documentation systems, project management dashboards—these don’t need the polish of SaaS alternatives when you’re the only one using them.

Data Processing: ETL pipelines, data warehousing, and analytics. Self-hosted solutions let you avoid per-query or per-gigabyte pricing.

Email Infrastructure: Postfix + Dovecot are ancient but rock-solid for managing your own mail server, though this one requires the most operational attention.

Building Your First Self-Hosted Stack

Let me walk you through setting up a practical stack that handles workflow automation, text-to-speech, and basic media processing on a single VPS.

Step 1: Choose and Configure Your VPS

I recommend Hetzner VPS for cost-effectiveness or Contabo VPS if you want more storage. For most workflows, a 4-core/8GB RAM instance is sufficient. Here’s what I deploy:

#!/bin/bash
# Run this as root after SSH into your fresh VPS

# Update system packages
apt update && apt upgrade -y

# Install Docker
apt install -y apt-transport-https ca-certificates curl software-properties-common
curl -fsSL https://download.docker.com/linux/debian/gpg | apt-key add -
add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/debian $(lsb_release -cs) stable"
apt install -y docker-ce docker-ce-cli containerd.io

# Install Docker Compose
curl -L "https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
chmod +x /usr/local/bin/docker-compose

# Create app directory
mkdir -p /opt/selfhosted
cd /opt/selfhosted

# Install Nginx as reverse proxy
apt install -y nginx

# Create Nginx config directory
mkdir -p /etc/nginx/sites-available

Step 2: Deploy n8n for Automation

n8n is where the magic happens. Create a docker-compose.yml:

version: '3.8'

services:
  n8n:
    image: n8nio/n8n:latest
    container_name: n8n
    environment:
      - N8N_HOST=workflow.yourdomain.com
      - N8N_PORT=5678
      - N8N_PROTOCOL=https
      - NODE_ENV=production
      - GENERIC_TIMEZONE=UTC
      - DB_TYPE=postgres
      - DB_POSTGRESDB_HOST=postgres
      - DB_POSTGRESDB_PORT=5432
      - DB_POSTGRESDB_DATABASE=n8n
      - DB_POSTGRESDB_USER=n8n
      - DB_POSTGRESDB_PASSWORD=your_secure_password_here
      - N8N_SECURE_COOKIE=true
    ports:
      - "5678:5678"
    volumes:
      - n8n_data:/home/node/.n8n
    depends_on:
      - postgres
    networks:
      - n8n_network
    restart: unless-stopped

  postgres:
    image: postgres:15-alpine
    container_name: n8n_postgres
    environment:
      - POSTGRES_USER=n8n
      - POSTGRES_PASSWORD=your_secure_password_here
      - POSTGRES_DB=n8n
    volumes:
      - postgres_data:/var/lib/postgresql/data
    networks:
      - n8n_network
    restart: unless-stopped

volumes:
  n8n_data:
  postgres_data:

networks:
  n8n_network:
    driver: bridge

Start the stack:

cd /opt/selfhosted
docker-compose up -d

💡 Fast-Track Your Project: Don’t want to configure this yourself? I build custom n8n pipelines and bots. Message me with code SYS3-HUGO.

Step 3: Add Text-to-Speech and Media Processing

Create a separate service for media utilities. Since we’re handling text-to-speech, here’s a simple Express app that wraps Edge TTS:

cd /opt/selfhosted
mkdir -p tts-service
cd tts-service

Create package.json:

{
  "name": "tts-service",
  "version": "1.0.0",
  "description": "Self-hosted TTS and media service",
  "main": "server.js",
  "dependencies": {
    "express": "^4.18.2",
    "edge-tts": "^0.3.0",
    "fluent-ffmpeg": "^2.1.2",
    "multer": "^1.4.5-lts.1",
    "dotenv": "^16.3.1"
  },
  "scripts": {
    "start": "node server.js"
  }
}

Create server.js:

const express = require('express');
const { execFile } = require('child_process');
const path = require('path');
const fs = require('fs');

const app = express();
const PORT = 3000;

app.use(express.json());

// TTS endpoint
app.post('/tts', async (req, res) => {
  const { text, voice, outputFormat } = req.body;

  if (!text) {
    return res.status(400).json({ error: 'Text is required' });
  }

  const voiceChoice = voice || 'en-US-AriaNeural';
  const format = outputFormat || 'mp3';
  const outputFile = path.join('/tmp', `tts_${Date.now()}.${format}`);

  try {
    execFile('edge-tts', [
      '--text', text,
      '--voice', voiceChoice,
      '--write-media', outputFile
    ], (error, stdout, stderr) => {
      if (error) {
        console.error('TTS Error:', error);
        return res.status(500).json({ error: 'TTS generation failed' });
      }

      res.download(outputFile, `output.${format}`, (err) => {
        if (err) console.error('Download error:', err);
        fs.unlink(outputFile, (unlinkErr) => {
          if (unlinkErr) console.error('Cleanup error:', unlinkErr);
        });
      });
    });
  } catch (err) {
    console.error('Error:', err);
    res.status(500).json({ error: 'Server error' });
  }
});

// Health check
app.get('/health', (req, res) => {
  res.json({ status: 'healthy' });
});

app.listen(PORT, () => {
  console.log(`TTS Service running on port ${PORT}`);
});

Create a Dockerfile:

FROM node:18-alpine

RUN apk add --no-cache \
    python3 \
    ffmpeg \
    curl \
    git

WORKDIR /app

COPY package.json package-lock.json ./
RUN npm install

RUN pip install edge-tts

COPY server.js .

EXPOSE 3000

CMD ["npm", "start"]

Add this service to your docker-compose.yml:

  tts-service:
    build: ./tts-service
    container_name: tts_service
    ports:
      - "3000:3000"
    networks:
      - n8n_network
    restart: unless-stopped
    environment:
      - NODE_ENV=production

Rebuild and restart:

docker-compose up -d --build

Step 4: Set Up Nginx Reverse Proxy

Create /etc/nginx/sites-available/selfhosted.conf:

upstream n8n {
    server localhost:5678;
}

upstream tts {
    server localhost:3000;
}

server {
    listen 80;
    server_name workflow.yourdomain.com tts.yourdomain.com;

    location / {
        return 301 https://$host$request_uri;
    }
}

server {
    listen 443 ssl http2;
    server_name workflow.yourdomain.com;

    ssl_certificate /etc/letsencrypt/live/workflow.yourdomain.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/workflow.yourdomain.com/privkey.pem;

    ssl_protocols TLSv1.2 T

Want to automate this yourself?

Start with n8n Cloud (free tier available) or self-host on a Hetzner VPS for full control.

📬 Get Weekly Automation Tips

One email per week with tutorials, tools, and workflows. No spam, unsubscribe anytime.

Subscribe Free →