A community calendar
you actually own
Gather is a self-hosted calendar app for local communities. Collect, moderate, and share events — with federation, feeds, and no vendor lock-in.
Features
Everything your community needs
Event submission
Let your community submit events with optional moderation before publishing.
Moderation workflow
Role-based access control — user, editor, and admin roles with fine-grained permissions.
ActivityPub federation
Follow your calendar from Mastodon or any ActivityPub-compatible app.
RSS & iCal feeds
Subscribe to events in any feed reader or calendar app.
Location support
Location-based events using OpenStreetMap data — no Google Maps required.
Recurring events
Schedule weekly, monthly, and custom recurring events with ease.
Custom pages
Create About, FAQ, and Code of Conduct pages with full Markdown support.
Custom branding
Set your site name, favicon, SEO description, and custom CSS.
Tags & location filters
Filter events by tag or town. Visitors find what's relevant without scrolling.
Dark / light theme
Automatic theme switching based on system preference, with a manual toggle.
Single binary
One binary, no external database, no external dependencies. Runs anywhere.
Self-Hosting
Up and running in minutes
Create a docker-compose.yml
The quickest way to run Gather is with Docker Compose. Create the file below on your server:
services:
gather:
image: ghcr.io/grantstephens/gather:latest
container_name: gather
restart: unless-stopped
ports:
- "8090:8090"
volumes:
- gather_data:/app/pb_data
environment:
- PB_ADMIN_EMAIL=admin@example.com
- PB_ADMIN_PASSWORD=changeme
- BASE_URL=https://your-domain.com
volumes:
gather_data:
BASE_URL.
Start the container
docker compose up -d
Gather will be running at http://localhost:8090.
Put it behind a reverse proxy
For HTTPS in production, proxy port 8090 with Caddy, nginx, or Traefik.
Caddy (simplest — handles HTTPS automatically):
your-domain.com {
reverse_proxy localhost:8090
}
nginx:
server {
listen 443 ssl http2;
server_name your-domain.com;
location / {
proxy_pass http://localhost:8090;
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;
}
}
Create your admin account
Gather uses two separate account systems (a PocketBase limitation):
- PocketBase superuser — manages the database at
/_/, created automatically from your env vars. - App user — logs into the frontend at
/login. Register separately, then promote to admin via/_/> Collections > users > set role toadmin.
Once you have a frontend admin account, go to Admin > Settings to configure:
- Site name and subtitle
- Favicon (auto-converted to WebP)
- SEO description for search engines and social previews
- Moderation settings (anonymous submissions, require approval)
- ActivityPub federation toggle
- Custom CSS and tracking/head code
Alternative: Docker Run
docker run -d \
--name gather \
-p 8090:8090 \
-v gather-data:/app/pb_data \
-e PB_ADMIN_EMAIL=admin@example.com \
-e PB_ADMIN_PASSWORD=changeme \
-e BASE_URL=https://your-domain.com \
ghcr.io/grantstephens/gather:latest
Alternative: Build from Source
Requires Go 1.25+, Node.js 18+, and libwebp-dev.
git clone https://github.com/grantstephens/gather.git
cd gather
make build
./gather serve
Configuration
Environment variables
| Variable | Default | Description |
|---|---|---|
PB_ADMIN_EMAIL |
admin@example.com |
Superuser email for the PocketBase dashboard |
PB_ADMIN_PASSWORD |
changeme |
Superuser password — change this! |
BASE_URL |
http://localhost:8090 |
Public URL (used for ActivityPub and feed links) |
PB_ENCRYPTION_KEY |
empty | Encryption key for sensitive data (openssl rand -hex 32) |
/feed/events.rss, iCal at /ical/events.ics, and (when enabled) an ActivityPub actor at /ap/actor.
Custom Pages
Create static pages (About, FAQ, Code of Conduct, etc.) from the Admin > Pages tab. Pages can appear in the navigation bar, footer, or both, and support Markdown content.
PocketBase Admin
The PocketBase admin dashboard is available at /_/ for direct database access, collection management, and log viewing.
Operations
Updates & backups
Updating to the latest version
docker compose pull
docker compose down
docker compose up -d
Backing up your data
All data lives in the pb_data Docker volume. Back it up with:
docker run --rm \
-v gather_gather_data:/data \
-v $(pwd):/backup \
alpine tar czf /backup/gather-backup-$(date +%Y%m%d).tar.gz -C /data .