Build a 100% Free Self-Hosted Booking System — No Calendly, No Subscription
Build a 100% Free Self-Hosted Booking System — No Calendly, No Subscription
If you sell a system that runs on free software, your calendar shouldn't be the thing that costs you money. Calendly Business is ~$15/user/month. Cal.com self-hosted looks free — until you realize recipients get a 404 because it's only reachable on your laptop.
Here's the stack that actually works, for $0 in software: Rallly (open-source scheduling) behind a Cloudflare Tunnel on your own domain. This is exactly what ViewsAI runs on, and it's the DIY playbook our customers get.
Why self-hosted beats the SaaS calendar
- Free forever — no seats, no tiers, no upgrade nagging
- Your domain — bookings live at
booking.yourdomain.com, which builds trust (nobody wants to book on someone's subdomain of a SaaS) - Private — your data, your database
- Ownership — it's a component you can sell, teach, and hand over
Part 1 — Run Rallly (the booking app)
Rallly is a free, self-hosted scheduling/poll app: create a poll with time options, share the link, participants pick a slot. Two Docker services: the app and its PostgreSQL database.
# ~/docker/rallly/docker-compose.yml
version: '3.8'
services:
rallly:
image: lukevella/rallly:latest
ports: ["3009:3000"]
environment:
DATABASE_URL: "postgresql://rallly_user:rallly_pass@rallly-db:5432/rallly"
NEXTAUTH_SECRET: "openssl rand -hex 32"
NEXTAUTH_URL: "http://localhost:3009"
SECRET_PASSWORD: "openssl rand -hex 24" # required in new Rallly
NEXT_PUBLIC_BASE_URL: "https://booking.YOURDOMAIN.com" # required
SUPPORT_EMAIL: "[email protected]" # required
NEXT_PUBLIC_SELF_HOSTED: "true"
NODE_ENV: "production"
DATABASE_SSL: "false"
depends_on: [rallly-db]
rallly-db:
image: postgres:16-alpine
environment:
POSTGRES_USER: rallly_user
POSTGRES_PASSWORD: rallly_pass
POSTGRES_DB: rallly
volumes: [rallly-db-data:/var/lib/postgresql/data]
volumes:
rallly-db-data:Gotchas we hit: newer Rallly crashes at boot with "Invalid environment variables" if you're missing SECRET_PASSWORD, NEXT_PUBLIC_BASE_URL, or SUPPORT_EMAIL. And the healthcheck path /api/health 404s — point it at / instead.
Part 2 — Expose it with a Cloudflare Tunnel
A Cloudflare Tunnel gives your localhost a public URL with zero inbound ports open. Fully scriptable via API with a token that has Tunnel:Edit.
# 1) create the tunnel
curl -X POST "https://api.cloudflare.com/client/v4/accounts/$ACCOUNT/cfd_tunnel" \
-H "Authorization: Bearer $CF_TOKEN" -H "Content-Type: application/json" \
-d '{"name":"viewsai-booking","config_src":"local"}'
# 2) route hostname → local app (ingress)
curl -X PUT "https://api.cloudflare.com/client/v4/accounts/$ACCOUNT/cfd_tunnel/$TUNNEL_ID/configurations" \
-H "Authorization: Bearer $CF_TOKEN" -H "Content-Type: application/json" \
-d '{"config":{"ingress":[{"hostname":"booking.YOURDOMAIN.com","service":"http://localhost:3009"},{"service":"http_status:404"}]}}'
# 3) grab the tunnel token and run cloudflared with it
curl -H "Authorization: Bearer $CF_TOKEN" \
"https://api.cloudflare.com/client/v4/accounts/$ACCOUNT/cfd_tunnel/$TUNNEL_ID/token"
# → save it, run: cloudflared tunnel run --token <token> (as a launchd service on macOS)Part 3 — The one manual step: DNS
Your tunnel token can't edit DNS, so add one CNAME in the Cloudflare dashboard:
Type: CNAME Name: booking Target: <TUNNEL_ID>.cfargotunnel.com Proxy: Proxied (orange cloud)Part 4 — Verify and go
dig booking.YOURDOMAIN.com
curl https://booking.YOURDOMAIN.com # 200 → Rallly loadsThen drop the link into your outbound emails. In the ViewsAI pipeline, the CTA lives in the email template — instead of "book a call" with no destination, every email can carry a real, clickable booking link.
The money math
| Calendly Business | $15/user/mo |
| Cal.com cloud | starts at $20/mo |
| This stack | $0 software + your domain |
The only real costs in the whole ViewsAI system are the email domain and sending mailboxes. Everything else — scheduling, CRM, automation, lead pipeline — runs on free, self-hosted, open-source software you own.
Want this whole system? It's part of the ViewsAI stack — recruiting AI, outbound automation, and the free infrastructure that runs it all.