Contents

Rallly: Self-Hosted Scheduling Without Doodle's Data Harvesting

Find a date everyone can do, without feeding a SaaS your contacts

Contents

There is a special kind of dread that comes from trying to pin six people down to a single evening. Reply-all threads spiral, someone proposes a date three others have already vetoed, and eventually somebody mutters the word “Doodle”. And Doodle does work. It also works very hard at watching everyone who clicks your link, sprinkling ad-tech all over a page whose entire job is to collect your friends’ availability and, increasingly, their attention. I host my own instead. The tool is called Rallly, and it is one of the few self-hosted things I actually recommend without a long list of caveats.

What Rallly actually is

Advertisement

Rallly is a group scheduling tool: the “when are you free?” grid, open-source, that you run yourself. You create a poll, give it a handful of date and time options, and share a single link. Participants open it, tick the slots that suit them, and you get the familiar grid showing which option has the most green ticks. You pick the winner. That is the whole loop. No accounts required for the people you invite, no app to install, no upsell.

The privacy angle is the reason it earns a place on my server. When you send a Doodle link, every participant is handed off to a third party that has commercial reasons to know who they are. With Rallly on your own box, the only party that sees the responses is you. There are no tracking pixels, no “improve our services” data sharing, no ad network learning that your book club meets on Tuesdays. For something as socially graphed as “who is free when”, that matters more than it first appears. Availability data is quietly revealing — it maps your relationships, your routines, and who you spend time with — and it is exactly the sort of low-stakes-feeling information that people hand over without a second thought and that aggregators are delighted to hoover up.

Standing it up

Rallly ships as a Docker image and wants a PostgreSQL database behind it. Here is a docker-compose.yml that gets you a working instance:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
services:
  rallly:
    image: lukevella/rallly:latest
    depends_on:
      - db
    ports:
      - "3000:3000"
    environment:
      # openssl rand -hex 32 — used to encrypt sessions
      SECRET_PASSWORD: "replace-with-a-long-random-string"
      DATABASE_URL: "postgresql://rallly:changeme@db:5432/rallly"
      NEXT_PUBLIC_BASE_URL: "https://rallly.example.com"
      # SMTP — required for the verification and notification emails
      SMTP_HOST: "smtp.example.com"
      SMTP_PORT: "587"
      SMTP_SECURE: "false"
      SMTP_USER: "[email protected]"
      SMTP_PWD: "your-smtp-password"
      SUPPORT_EMAIL: "[email protected]"
    restart: unless-stopped

  db:
    image: postgres:16
    environment:
      POSTGRES_USER: "rallly"
      POSTGRES_PASSWORD: "changeme"
      POSTGRES_DB: "rallly"
    volumes:
      - ./data/db:/var/lib/postgresql/data
    restart: unless-stopped

Run openssl rand -hex 32 to generate the SECRET_PASSWORD, put a real reverse proxy in front of it so NEXT_PUBLIC_BASE_URL is served over HTTPS, and you are essentially done. The Postgres data lives in ./data/db, which is the one directory you must back up.

A word on that NEXT_PUBLIC_BASE_URL: it is baked into the links Rallly generates and into how it validates requests, so it must match the address people actually visit, scheme and all. Set it to http://localhost:3000 while you try things out and to your real HTTPS URL the moment you go public. Getting it wrong is the source of a whole class of “the links point to the wrong place” confusion later.

Putting a reverse proxy in front

Advertisement

Exposing port 3000 straight to the internet is not the plan. You want TLS, and you want the app to sit behind a proxy that terminates HTTPS and forwards cleanly. A minimal Caddy config does the lot, certificate and all, because Caddy fetches and renews Let’s Encrypt certificates automatically:

1
2
3
rallly.example.com {
    reverse_proxy localhost:3000
}

That is genuinely the whole file. If you prefer Nginx or Traefik the shape is the same — forward rallly.example.com to 127.0.0.1:3000, terminate TLS at the proxy, and make sure the proxy passes the Host header through so Rallly sees the name it expects. Once HTTPS is live, drop the ports: mapping from the compose file and bind Rallly to the internal Docker network only, so the app is reachable exclusively through the proxy. There is no reason for the raw app port to be listening on a public interface.

The SMTP catch

The bit people trip over is email. Rallly is not a fire-and-forget container; it genuinely needs working SMTP. Poll creation sends a verification mail, and the comment and notification flow relies on email too. If you skip the SMTP block, the app will look like it is broken when in fact it is just waiting on a message it can never send. Point it at a transactional provider or your own mail relay, send yourself a test poll, and confirm the mail actually lands before you share a link with anyone who matters. This is the single most common reason a fresh install feels half-functional.

Two follow-on gotchas are worth pre-empting. First, if verification mails silently vanish, check your spam folder and then check whether your SMTP provider requires the From address to be one you have verified with them — a mismatched sender is a classic silent drop. Second, SMTP_SECURE trips people up: set it to true only for implicit TLS on port 465, and leave it false for STARTTLS on port 587, which is what most relays actually want. Getting that pair wrong produces a connection that either hangs or is refused outright.

Backups and keeping it boring

Everything that matters lives in Postgres, so a good backup is a Postgres dump on a schedule, not a copy of the container. A one-liner in a nightly cron job is enough:

1
docker compose exec -T db pg_dump -U rallly rallly | gzip > "rallly-$(date +%F).sql.gz"

Rotate those dumps, keep at least one copy off the machine, and actually test a restore once — an untested backup is a hope, not a backup. If you already run monitoring, point a check at the Rallly URL so you find out it is down before your guests do; I use the approach I describe in Uptime Kuma self-hosted monitoring for exactly this sort of low-drama, always-on service.

Troubleshooting

Blank page or “invalid URL” errors after setup. Almost always NEXT_PUBLIC_BASE_URL disagreeing with the address you are visiting, or your proxy stripping the Host header. Line them up and the ghost problems disappear.

Poll creation never completes. The verification email is not arriving. This is the SMTP issue above wearing a different hat — the app is waiting on a mail it cannot send. Fix the mail path and creation works instantly.

Database connection refused on first boot. The rallly container can start faster than Postgres is ready to accept connections. depends_on waits for the container to start, not for the database to be ready, so on a cold boot Rallly occasionally races ahead. restart: unless-stopped papers over it — the app crashes, restarts, and by then the database is up — but if it persists, add a healthcheck on the db service and gate rallly on it.

Upgrades. Pull the new image, docker compose up -d, and Rallly runs any needed database migrations on start. Take a pg_dump first, every time, so a bad migration is a five-minute rollback rather than a bad evening.

Living with it day to day

Once it is up, the flow is pleasant precisely because it is unremarkable. You create a poll from the web interface, add your candidate dates and times, and copy the share link. Participants land on a clean grid, tick what suits them — no sign-up, no download, no cookie wall — and you watch the columns fill in. When a clear winner emerges, you finalise the poll and Rallly can notify the participants of the chosen slot. There is a gentle admin link that lets you edit or delete a poll after the fact, which is worth bookmarking, because the public link deliberately does not expose those controls.

The contrast with the hosted incumbents is sharpest in what is absent. No participant is asked to create an account to answer. No banner nags them toward a paid tier. No third-party script loads in the background to fingerprint their browser. The page does exactly what it says and nothing else, and because you control the instance, you also control retention — old polls are yours to purge on whatever schedule you like, rather than sitting indefinitely in a vendor’s database being mined for aggregate “insights”. For a tool whose entire purpose is to collect a small, intimate graph of who spends time with whom, that restraint is the whole point.

It is worth saying plainly that Rallly is a focused, single-purpose tool. It schedules group meetings and it does nothing else. There is no calendar, no task list, no creeping ambition to become your whole productivity suite. I find that refreshing. It does one job and it does it cleanly, which also means it stays small, easy to update, and dull to operate — exactly what you want from something running unattended. That narrowness is a feature, not a limitation: a tool that never grows new surface area is a tool that never grows new ways to break, and after years of running it I can count the times it has demanded my attention on one hand.

Is hosting it yourself worth it?

Honestly, sometimes it is overkill. If you organise one group event a year, the free hosted version of Rallly or even a Doodle link costs you nothing but a bit of dignity, and spinning up a Postgres database is effort out of all proportion to the payoff. Self-hosting earns its keep when scheduling is a regular fixture: a recurring committee, a band, a gaming group, a team that books things constantly, or anyone who simply objects on principle to routing their friends’ data through an ad business.

If you already run a small server with a reverse proxy and a mail relay, adding Rallly is twenty minutes of work and one more pleasantly boring container. If you do not, treat this as a gentle nudge to build that foundation, because Rallly is a lovely first tenant for it — and once the proxy, the mail relay, and the backup habit are in place, every subsequent self-hosted tool costs a fraction of the effort. That compounding is the real payoff, and it is a big part of why I keep escaping the SaaS trap one small service at a time. And the maintenance cost, once it is running, is close to nil: a nightly database dump, the occasional image pull, and otherwise a container that sits quietly doing its one job for months on end without a single complaint. That is about as good as self-hosting gets. Either way, the next time the reply-all thread starts spiralling, you will have a link to send that does not sell anyone out to find a free Thursday, does not hand your friends’ calendars to an ad broker, and does not ask a single one of them to make an account just to say they are busy.

Advertisement
Advertisement
Smarc
Written by Smarc

Founder and editor of vo.rs. A lifelong tinkerer who self-hosts far more than is sensible, hardens Linux boxes for fun, and prods the latest AI tools to see what they can really do. The how-to guides here are the notes Smarc wishes had existed the first time round.