Self-Hosting Gotify with Docker Compose
What Is Gotify?
Gotify is a self-hosted push notification server written in Go. It provides a clean web UI for managing notification sources (applications) and receivers (clients), plus a simple REST API for sending messages. Unlike ntfy’s topic-based model, Gotify uses application tokens — each notification source gets its own token. Available on GitHub under the MIT license.
Docker Compose Configuration
Gotify has zero external dependencies — no database server, no cache, nothing. One container, one volume:
services:
gotify:
image: gotify/server:2.9.0
container_name: gotify
restart: unless-stopped
ports:
- "8080:80"
environment:
- GOTIFY_DEFAULTUSER_PASS=CHANGE_THIS_SECURE_PASSWORD # Admin password — set before first start
- TZ=UTC
volumes:
- gotify_data:/app/data
volumes:
gotify_data:
Start the stack:
docker compose up -d
Prerequisites
- A Linux server (Ubuntu 22.04+ recommended)
- Docker and Docker Compose installed (guide)
- 256 MB of free RAM (Gotify uses ~20-30 MB idle)
Initial Setup
Access the web UI at http://your-server:8080.
Default credentials are admin / whatever you set in GOTIFY_DEFAULTUSER_PASS. If you didn’t set the env var, the default is admin / admin — change it immediately.
Create an Application
Applications are notification sources. Each gets a unique token.
- Go to Apps in the web UI
- Click Create Application
- Name it (e.g., “Server Monitoring”, “Backup Alerts”)
- Copy the generated token
Send Your First Notification
curl "http://localhost:8080/message?token=YOUR_APP_TOKEN" \
-F "title=Test" \
-F "message=Gotify is working" \
-F "priority=5"
Create a Client
Clients receive notifications. The web UI itself is a client, but you can also connect the Android app.
- Go to Clients in the web UI
- Click Create Client
- Copy the client token — use this in the Android app
Configuration
Message Priorities
Gotify uses numeric priorities. Higher numbers = more urgent. The Android app maps these to notification channels:
| Priority | Behavior (Android) | Use Case |
|---|---|---|
| 0 | No notification | Logging only |
| 1-3 | Low priority | Routine updates |
| 4-7 | Normal | Standard alerts |
| 8-10 | High priority — overrides DND | Critical alerts |
Markdown Messages
Send rich notifications with Markdown formatting:
curl "http://localhost:8080/message?token=YOUR_APP_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"title": "Deploy Complete",
"message": "## Production Deploy\n\n- **Version:** v2.1.0\n- **Status:** Success\n- **Duration:** 45s",
"priority": 5,
"extras": {
"client::display": {
"contentType": "text/markdown"
}
}
}'
External Database (Optional)
For larger deployments, switch from SQLite to PostgreSQL or MySQL:
environment:
- GOTIFY_DATABASE_DIALECT=postgres
- GOTIFY_DATABASE_CONNECTION=host=db port=5432 user=gotify dbname=gotifydb password=secret sslmode=disable
For most self-hosters, SQLite is perfectly adequate. Only switch to an external database if you’re sending thousands of messages per day.
User Registration
By default, only the admin can create accounts. Enable self-registration:
environment:
- GOTIFY_REGISTRATION=true
Trusted Proxies
When behind a reverse proxy, configure trusted proxy IPs so Gotify sees real client IPs:
environment:
- GOTIFY_SERVER_TRUSTEDPROXIES=172.16.0.0/12 # Docker's default network range
Common Integrations
| Tool | Integration Type | Notes |
|---|---|---|
| Uptime Kuma | Built-in Gotify provider | Just enter server URL and app token |
| Home Assistant | Built-in integration | Via notify.gotify service |
| Grafana | Webhook | Send alerts to Gotify’s message API |
| Watchtower | Built-in Gotify support | Container update notifications |
| Custom scripts | HTTP POST | Any tool that can make HTTP requests |
Reverse Proxy
Gotify uses WebSockets for real-time message delivery. Your reverse proxy must forward WebSocket headers:
location / {
proxy_pass http://127.0.0.1:8080;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
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;
}
See Reverse Proxy Setup for Caddy and Traefik configurations.
Backup
All data lives in a single volume:
docker compose stop gotify
docker run --rm -v gotify_data:/data -v $(pwd):/backup alpine \
tar czf /backup/gotify-backup-$(date +%Y%m%d).tar.gz /data
docker compose start gotify
The volume contains the SQLite database (gotify.db), uploaded images, and plugins. See Backup Strategy.
Troubleshooting
WebSocket connection fails with 403
Symptom: Real-time notifications don’t arrive in the web UI or Android app. Browser console shows 403 on /stream.
Fix: Gotify verifies the Host header against the WebSocket Origin. Your reverse proxy must set proxy_set_header Host $host. Without it, the origin check fails.
WebSocket returns 400 “missing upgrade token”
Symptom: WebSocket connections fail with a 400 error mentioning “upgrade.”
Fix: Your reverse proxy is stripping WebSocket headers. Add proxy_set_header Upgrade $http_upgrade and proxy_set_header Connection "upgrade" to your Nginx config.
Default password doesn’t work
Symptom: Can’t log in with admin / admin or your configured password.
Fix: GOTIFY_DEFAULTUSER_PASS only takes effect on the first container start when the database is created. If you changed it after the database already exists, it’s ignored. Either delete the volume and recreate, or log in with the original password and change it in the web UI.
Messages accumulate and never expire
Symptom: Disk usage grows over time as messages pile up.
Fix: Gotify has no built-in message expiration. Delete old messages via the API: curl -X DELETE "http://localhost:8080/message" -H "X-Gotify-Key: YOUR_CLIENT_TOKEN". Consider a cron job for periodic cleanup.
Resource Requirements
- RAM: ~20-30 MB idle, ~50 MB under load
- CPU: Negligible — runs on a Raspberry Pi Zero
- Disk: ~30 MB Docker image. Data grows with message volume and uploaded images.
Verdict
Gotify is the simplest self-hosted notification server you can run. One container, one volume, zero dependencies, and a clean web UI. It’s ideal if you want a visual dashboard for managing notification sources and prefer token-based authentication over ntfy’s topic-based model.
The trade-off: no iOS app (Android only via F-Droid/GitHub), no built-in message expiration, and no SSO support. If you need iOS push notifications or a CLI-friendly pub/sub model, ntfy is the better choice. For most Android users who want a simple “send me alerts” server, Gotify is hard to beat.
Related
Get self-hosting tips in your inbox
New guides, comparisons, and setup tutorials — delivered weekly. No spam.