Made Open

Run Your Own Instance

Get Made Open running as a production system on your own infrastructure. This guide is for anyone who wants to operate the platform — not develop it. No coding required.


What You're Setting Up

Made Open runs as six containers on a single server, backed by a managed database:

                    Internet
                       |
                   [ Traefik ]        ← reverse proxy, auto-HTTPS
                   /         \
            [ Hub API ]   [ Web App ]  ← your backend + frontend
               |    |         
          [ NATS ]  [ Redis ]          ← event bus + cache
               |
         [ Meilisearch ]               ← full-text search
               |
     ────── Supabase Cloud ──────      ← database, auth, file storage
ContainerWhat It Does
HubThe backend — runs all services, processes events, talks to integrations
WebThe browser UI — dashboard, settings, conversations, rules
NATSMessage broker — services communicate through events, not direct calls
RedisCache layer — speeds up frequent lookups, rate limiting
MeilisearchSearch engine — instant, typo-tolerant search across all your data
TraefikReverse proxy — routes traffic to Hub and Web, handles HTTPS certificates automatically

Supabase Cloud runs externally as a managed service. It provides the PostgreSQL database, user authentication, encrypted credential storage (Vault), file storage, and real-time subscriptions.


What You Need

Three things before you start:

WhatWhyHow Long
A server with DockerRuns the six containers5 minutes to provision
A Supabase Cloud accountManaged database + auth5 minutes to create
A domain nameFor HTTPS and clean URLsAlready have one, or 2 minutes to buy

Total setup time: ~30 minutes.


Step 1: Create a Supabase Project

Supabase is the database and authentication layer. You need a cloud project.

  1. Go to supabase.com/dashboard and create an account
  2. Click New Project
  3. Choose a name (e.g., made-open) and set a strong database password
  4. Select a region close to your server
  5. Wait for the project to provision (~2 minutes)

Once ready, go to Settings > API and copy these four values — you'll need them in Step 4:

ValueWhere to Find ItWhat It's For
Project URLSettings > API > Project URLSUPABASE_URL — how the hub connects to the database
anon public keySettings > API > Project API keysSUPABASE_ANON_KEY — safe for browser use, respects row-level security
service_role keySettings > API > Project API keysSUPABASE_SERVICE_ROLE_KEY — full database access, keep this secret
Connection stringSettings > Database > Connection string (URI)DATABASE_URL — direct PostgreSQL connection

Apply Database Migrations

You need to run the schema migrations once. Install the Supabase CLI on any computer:

# Install Supabase CLI
npm install -g supabase

# Log in to your account
supabase login

# Clone the repo (just for migrations — you won't run code from it)
git clone https://github.com/drdropout/made-open.git
cd made-open

# Link to your cloud project
supabase link --project-ref <your-project-ref>

# Push all 53 migrations
supabase db push

The <your-project-ref> is the alphanumeric ID in your Supabase project URL (e.g., abcdefghijklmnop from https://abcdefghijklmnop.supabase.co).

After this, you can delete the cloned repo if you like — the migrations are in the database now.

Supabase Pricing

TierCostWhat You Get
Free$0/mo500 MB database, 1 GB storage, 50k monthly active users, shared compute
Pro$25/mo8 GB database, 100 GB storage, unlimited users, daily backups, dedicated compute

The Free tier works fine for personal use. Upgrade to Pro when you need backups or more storage.


Step 2: Provision a Server

You need a Linux server with Docker installed. Any cloud provider works — here are three popular options:

Option A: Hetzner (Best Value)

  1. Go to hetzner.com/cloud and create an account
  2. Create a new server:
    • Location: Choose closest to you
    • Image: Ubuntu 24.04
    • Type: CX22 (2 vCPU, 4 GB RAM) — $4.50/mo
    • SSH key: Add your public key (or create one)
  3. Note the server's IP address
# SSH into your new server
ssh root@<your-server-ip>

# Install Docker
curl -fsSL https://get.docker.com | sh

Option B: DigitalOcean

  1. Go to digitalocean.com and create an account
  2. Create a Droplet:
    • Image: Ubuntu 24.04
    • Size: Basic, 2 vCPU / 4 GB RAM — $24/mo
    • Region: Choose closest to you
    • Authentication: SSH key
  3. Note the Droplet's IP address
ssh root@<your-droplet-ip>
curl -fsSL https://get.docker.com | sh

Option C: AWS Lightsail

  1. Go to lightsail.aws.amazon.com and sign in
  2. Create an instance:
    • Platform: Linux/Unix
    • Blueprint: Ubuntu 24.04
    • Plan: 2 GB RAM — $10/mo (or 4 GB — $20/mo)
  3. Note the static IP address
ssh ubuntu@<your-lightsail-ip>
curl -fsSL https://get.docker.com | sh

Server Sizing Reference

Server SizeHandlesCost Range
2 vCPU / 4 GB RAMPersonal use, 1-5 users$4-24/mo
4 vCPU / 8 GB RAMSmall team, heavier AI usage$10-48/mo
8 vCPU / 16 GB RAMPower user with many integrations$20-96/mo

Step 3: Point Your Domain

Create two DNS A records pointing to your server's IP address. Do this wherever you manage your domain (Cloudflare, Namecheap, Porkbun, Route 53, etc.).

Record TypeNameValue
Aapi.yourdomain.com<your-server-ip>
Aapp.yourdomain.com<your-server-ip>

Examples by registrar:

  • Cloudflare: Dashboard > your domain > DNS > Add record. Set proxy status to "DNS only" (grey cloud) so Traefik handles TLS.
  • Namecheap: Domain List > Manage > Advanced DNS > Add New Record.
  • Porkbun: DNS Records > Add. Type: A, Host: api or app, Answer: your server IP.

DNS propagation usually takes 1-5 minutes, but can take up to an hour.


Step 4: Configure Your Server

SSH into your server and set up the deployment:

ssh root@<your-server-ip>

# Create a directory for Made Open
mkdir -p /opt/made-open && cd /opt/made-open

# Download the production compose file and env template
curl -O https://raw.githubusercontent.com/drdropout/made-open/main/docker-compose.prod.yml
curl -O https://raw.githubusercontent.com/drdropout/made-open/main/.env.prod.example

# Copy the template to .env
cp .env.prod.example .env

Now edit .env and fill in every value:

nano .env   # or vim, or any editor you prefer

Here's what each variable means:

# ── Your domains (from Step 3) ──
HUB_DOMAIN=api.yourdomain.com          # Where the API lives
WEB_DOMAIN=app.yourdomain.com          # Where the web UI lives
ACME_EMAIL=you@example.com             # Let's Encrypt sends expiry warnings here

# ── Image version ──
TAG=latest                             # Docker image tag — use 'latest' or a specific version like 'v1.2.0'

# ── Supabase keys (from Step 1) ──
SUPABASE_URL=https://abcdefgh.supabase.co
SUPABASE_SERVICE_ROLE_KEY=eyJ...       # The service_role key — KEEP THIS SECRET
SUPABASE_ANON_KEY=eyJ...              # The anon key — safe for browsers
DATABASE_URL=postgresql://postgres:your-password@db.abcdefgh.supabase.co:5432/postgres
NEXT_PUBLIC_SUPABASE_URL=https://abcdefgh.supabase.co       # Same as SUPABASE_URL
NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJ...  # Same as SUPABASE_ANON_KEY

# ── Security — generate unique random strings ──
JWT_SECRET=                            # Run: openssl rand -hex 32
MEILI_MASTER_KEY=                      # Run: openssl rand -hex 16
CORS_ORIGINS=https://app.yourdomain.com
NODE_ENV=production

# ── Optional (uncomment if you want these features) ──
# OPENROUTER_API_KEY=sk-or-...        # Cloud AI — get a key at openrouter.ai
# STRIPE_SECRET_KEY=sk_live_...       # Billing — from stripe.com dashboard
# STRIPE_WEBHOOK_SECRET=whsec_...     # Billing — from Stripe webhook settings

Generate the security secrets:

# Generate and paste these into your .env
openssl rand -hex 32    # → JWT_SECRET
openssl rand -hex 16    # → MEILI_MASTER_KEY

Step 5: Start Everything

cd /opt/made-open

# Pull the container images
docker compose -f docker-compose.prod.yml pull

# Start all services
docker compose -f docker-compose.prod.yml up -d

Watch the logs during first startup:

docker compose -f docker-compose.prod.yml logs -f

Traefik will automatically obtain HTTPS certificates from Let's Encrypt on the first request. This takes 30-60 seconds.


Step 6: Verify It's Working

# Check all containers are running
docker compose -f docker-compose.prod.yml ps

# Hub health check
curl https://api.yourdomain.com/health
# Expected: {"status":"ok","version":"0.0.1"}

# Detailed health (shows status of each internal service)
curl https://api.yourdomain.com/health/detailed

# Web app
curl -I https://app.yourdomain.com
# Expected: HTTP/2 200

Open https://app.yourdomain.com in your browser. You should see the Made Open login page.


Step 7: Create Your Account

  1. Open https://app.yourdomain.com in your browser
  2. Click Sign Up
  3. Enter your email and password
  4. Confirm your email (Supabase sends a verification link)
  5. Log in — you'll land on the dashboard

Step 8: Add Integrations

Integrations activate dynamically when you add credentials through Settings. Nothing is required — add what you need.

Voice and SMS (Twilio)

  1. Create a Twilio account at twilio.com
  2. Buy a phone number (~$1/month) — ensure it's voice and SMS capable
  3. Copy your Account SID and Auth Token from the Twilio Console
  4. In Made Open: Settings > Twilio > Connect
  5. Enter Account SID, Auth Token, and Phone Number
  6. Voice Calls and SMS Messaging appear in the dashboard

Email, Calendar, Contacts (Microsoft 365)

Requires a one-time Azure AD app registration. Full walkthrough: credential-setup.md

Quick version:

  1. Register an app at portal.azure.com > Azure Active Directory > App registrations
  2. Set redirect URI to https://api.yourdomain.com/api/oauth/microsoft/callback
  3. Add delegated permissions: User.Read, Contacts.Read, Calendars.Read, Mail.Read, Files.Read, Tasks.Read
  4. Create a client secret
  5. Add MICROSOFT_CLIENT_ID, MICROSOFT_CLIENT_SECRET, MICROSOFT_REDIRECT_URI, MICROSOFT_TENANT_ID to your .env
  6. Restart the hub: docker compose -f docker-compose.prod.yml restart hub
  7. In Made Open: Settings > Microsoft 365 > Connect — sign in with your Microsoft account

Email, Calendar, Contacts (Google Workspace)

Requires a Google Cloud project with OAuth. Full walkthrough: credential-setup.md

Quick version:

  1. Create a project at console.cloud.google.com
  2. Enable Gmail, Calendar, People, and Drive APIs
  3. Create OAuth 2.0 credentials with redirect URI https://api.yourdomain.com/api/oauth/google/callback
  4. Add client ID and secret to your .env
  5. Restart the hub
  6. In Made Open: Settings > Google Workspace > Connect

Cloud AI (OpenRouter)

  1. Create an account at openrouter.ai
  2. Go to Keys > Create Key
  3. Copy the API key (starts with sk-or-v1-)
  4. In Made Open: Settings > OpenRouter > Connect, paste the key
  5. AI features activate — assistant, summaries, smart replies

Pay-per-token pricing. Most queries cost fractions of a cent.

Local AI (Ollama)

Run AI models locally on your server — no API key, no cost, full privacy.

  1. Install Ollama on your server: curl -fsSL https://ollama.com/install.sh | sh
  2. Pull a model: ollama pull llama3.2
  3. In Made Open: Settings > Ollama > Connect
  4. Enter the URL (default: http://localhost:11434)

Works best with a GPU. CPU-only is possible but slower.


Optional Add-Ons Reference

IntegrationWhat It EnablesYour Own Account?Approximate Cost
TwilioVoice calls, SMS, voicemail, videoYes~$1/mo + $0.01/SMS, $0.013/min voice
Microsoft 365Email, calendar, contacts, OneDrive, TasksYes (Azure AD)Included with M365 subscription
Google WorkspaceGmail, calendar, contacts, Drive, MeetYes (Google Cloud)Free with Google account
OpenRouterCloud AI (Claude, GPT-4, Gemini, etc.)YesPay-per-token (~$0.001-0.01 per query)
OllamaLocal AI inference (Llama, Qwen, etc.)NoFree (self-hosted)
StripeBilling and subscriptionsYes2.9% + $0.30 per transaction
LightRAGKnowledge graph + entity extractionNoFree (add container to docker-compose)
Firebase FCMPush notifications to mobileYes (Firebase)Free tier is generous

What It Costs

Minimal (~$5/month)

For personal use — just you and basic features.

ItemProviderCost
ServerHetzner CX22 (2 vCPU, 4 GB)$4.50/mo
DatabaseSupabase Free tier$0/mo
DomainAlready own one$0/mo
Total~$5/mo

For daily use with backups and headroom.

ItemProviderCost
ServerDigitalOcean 4 GB Droplet$24/mo
DatabaseSupabase Pro$25/mo
DomainAny registrar~$1/mo
Total~$50/mo

Everything above plus communications and AI.

ItemProviderCost
Server + Database(same as Recommended)$50/mo
TwilioPhone number + usage~$2-10/mo
OpenRouterAI queries~$1-10/mo
Total~$55-70/mo

Maintenance

Updating

When a new version is released, update with two commands:

cd /opt/made-open
docker compose -f docker-compose.prod.yml pull
docker compose -f docker-compose.prod.yml up -d

To pin a specific version:

TAG=v1.2.0 docker compose -f docker-compose.prod.yml up -d

Backups

DataWhere It LivesBackup Strategy
Users, entities, messages, rulesSupabase CloudAutomatic daily backups (Pro tier). Manual backups via Dashboard.
NATS event historynats_data Docker volumeUse docker run --rm -v nats_data:/data -v /backups:/backup alpine tar czf /backup/nats.tar.gz /data
Search indexesmeili_data Docker volumeRebuilds from database on startup — backup optional
Redis cacheredis_data Docker volumeEphemeral — no backup needed
TLS certificatesletsencrypt Docker volumeAuto-renews — backup optional

Monitoring

The hub exposes monitoring endpoints:

EndpointWhat It Shows
GET /healthSimple OK/not-OK — use as a liveness probe
GET /health/detailedStatus of every internal service (NATS, Meilisearch, database)
GET /metricsPrometheus-compatible metrics — connect Grafana for dashboards

View live logs:

docker compose -f docker-compose.prod.yml logs -f hub

The hub outputs structured JSON logs via pino. Route container stdout to any log aggregator (Datadog, Loki, CloudWatch, etc.).


Troubleshooting

ProblemCheck
Containers won't startdocker compose -f docker-compose.prod.yml logs — look for missing env vars or connection errors
HTTPS not workingEnsure DNS A records point to the server IP and ports 80/443 are open. Check docker compose -f docker-compose.prod.yml logs traefik
"Connection refused" from hubCheck that NATS, Meilisearch, and Redis containers are running: docker compose -f docker-compose.prod.yml ps
Supabase connection failsVerify DATABASE_URL includes the correct password and hostname. Test with psql $DATABASE_URL
Login page shows but can't sign upCheck SUPABASE_URL and SUPABASE_ANON_KEY match your Supabase project. Ensure email confirmations are configured in Supabase Dashboard > Auth > Settings
Integrations don't appear after adding credentialsCheck hub logs for errors. Restart the hub: docker compose -f docker-compose.prod.yml restart hub

Further Reading