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.

  1. Go to Apps in the web UI
  2. Click Create Application
  3. Name it (e.g., “Server Monitoring”, “Backup Alerts”)
  4. 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.

  1. Go to Clients in the web UI
  2. Click Create Client
  3. 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:

PriorityBehavior (Android)Use Case
0No notificationLogging only
1-3Low priorityRoutine updates
4-7NormalStandard alerts
8-10High priority — overrides DNDCritical 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

ToolIntegration TypeNotes
Uptime KumaBuilt-in Gotify providerJust enter server URL and app token
Home AssistantBuilt-in integrationVia notify.gotify service
GrafanaWebhookSend alerts to Gotify’s message API
WatchtowerBuilt-in Gotify supportContainer update notifications
Custom scriptsHTTP POSTAny 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.