Deploying Gotify Push Notification Server with Docker
What You’ll Need
- Hetzner VPS or DigitalOcean running Ubuntu 22.04 LTS
- Namecheap for managing your custom domain and DNS records
- Docker Engine and Docker Compose V2 installed on your server
- n8n Cloud or self-hosted n8n for triggering automated alerts
Table of Contents
- 1. Server Preparation and Firewall Configuration
- 2. Deploying Gotify with Docker Compose
- 3. Setting Up Caddy Reverse Proxy with Automatic SSL
- 4. Sending Push Notifications Programmatically
- 5. Advanced Integration and Event Deduplication
- Getting Started
1. Server Preparation and Firewall Configuration
When building an automated operational workflow, fast push notifications are non negotiable. External services like Pushover or Pushbullet work well, but they introduce third party dependencies, rate limits, and recurring costs. Gotify is a self-hosted, open source push notification server that receives messages via a simple REST API and broadcasts them to Android clients or web UI subscribers in real time over WebSockets.
To host Gotify reliably, I recommend starting with an active cloud instance like a Hetzner VPS or a droplet from DigitalOcean. A single vCPU with 1GB of RAM is more than enough to handle millions of monthly notifications.
Before running any containerized services, ensure your underlying host operating system is locked down. If you have not done so already, complete our guide on Hardening SSH Server Security on Ubuntu VPS to restrict root access, enforce key-based authentication, and install Fail2ban.
Once connected to your server, update system packages and configure the Uncomplicated Firewall (UFW) to allow standard HTTP, HTTPS, and SSH traffic.
sudo apt update && sudo apt upgrade -y
sudo apt install ufw docker.io docker-compose-v2 -y
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow 22/tcp
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
Confirm that the UFW service is active and Docker is running:
sudo systemctl status docker --no-pager
sudo ufw status verbose
💡 Fast-Track Your Project: Don’t want to configure this yourself? I build custom n8n pipelines and bots. Message me with code SYS3-HUGO.
2. Deploying Gotify with Docker Compose
We will deploy Gotify inside an isolated Docker container, binding its web interface to localhost so that it remains accessible only through a reverse proxy. This architecture keeps direct HTTP access closed to the public internet while securing traffic with automatic TLS certificates.
Create a dedicated application directory at /opt/gotify and establish a volume mapping directory structure for persistent database storage and configuration options.
sudo mkdir -p /opt/gotify/gotify-data
cd /opt/gotify
Create a new configuration file named docker-compose.yml using your preferred text editor. We will set environment variables for default credentials, memory limits, and process timezones directly inside this file.
version: '3.8'
services:
gotify:
image: gotify/server:latest
container_name: gotify
restart: always
ports:
- "127.0.0.1:8080:80"
environment:
- GOTIFY_SERVER_PORT=80
- GOTIFY_SERVER_KEEPCONTAINERFRAMES=false
- GOTIFY_DATABASE_DIALECT=sqlite3
- GOTIFY_DATABASE_CONNECTION=data/gotify.db
- GOTIFY_DEFAULTUSER_NAME=admin
- GOTIFY_DEFAULTUSER_PASS=ChangeThisSecurePassword123!
- GOTIFY_PASSWORDBRUTEFORCEDELAY=500ms
- TZ=UTC
volumes:
- ./gotify-data:/app/data
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:80/health"]
interval: 30s
timeout: 5s
retries: 3
Now bring up the Gotify stack using Docker Compose:
sudo docker compose up -d
Verify that the container initialized successfully and is running on host port 8080:
sudo docker compose ps
curl http://127.0.0.1:8080/health
The output of the cURL command should return a {"health":"green"} JSON response.
3. Setting Up Caddy Reverse Proxy with Automatic SSL
Gotify relies heavily on persistent WebSocket connections for real time communication with client devices. Caddy is an ideal reverse proxy because it handles Let’s Encrypt SSL certificates automatically and proxies WebSockets without requiring complex custom header configurations.
First, navigate to your domain DNS provider, such as Namecheap, and create an A record pointing your domain or subdomain (for example push.yourdomain.com) directly to your VPS public IPv4 address.
Install Caddy on your Ubuntu host:
sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https curl
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list
sudo apt update
sudo apt install caddy -y
Now open /etc/caddy/Caddyfile and replace its existing content with the following reverse proxy block. Make sure to replace push.yourdomain.com with your actual domain name.
push.yourdomain.com {
encode gzip zstd
reverse_proxy 127.0.0.1:8080 {
header_up Host {host}
header_up X-Real-IP {remote_host}
header_up X-Forwarded-For {remote_host}
header_up X-Forwarded-Proto {scheme}
}
log {
output file /var/log/caddy/gotify_access.log
}
}
Validate your Caddy syntax and restart the service to initiate automated SSL provision:
sudo caddy validate --config /etc/caddy/Caddyfile
sudo systemctl restart caddy
You can now open https://push.yourdomain.com in your web browser. Log in using the default credentials specified in your docker-compose.yml file (admin and ChangeThisSecurePassword123!). Navigate immediately to the Users tab inside the Gotify web interface and update the administrator password to a long, randomly generated secret.
4. Sending Push Notifications Programmatically
Gotify uses token-based authentication for both client listening and application publishing. To publish alerts, you must create an Application inside the Gotify UI.
- Log into your Gotify dashboard.
- Click Apps in the top navigation menu.
- Click Create Application, enter a name like
Alert Automation, and click Create. - Copy the generated application token (which begins with an
Aprefix, such asA1a2B3c4D5e6F7g).
Let’s test publishing a notification using various client implementations.
Bash and cURL Implementation
Here is a robust, production ready shell script that posts high-priority security notifications directly to your Gotify server.
#!/usr/bin/env bash
set -euo pipefail
GOTIFY_URL="https://push.yourdomain.com"
GOTIFY_TOKEN="A1a2B3c4D5e6F7g"
send_gotify_notification() {
local title="$1"
local message="$2"
local priority="$3"
local response
response=$(curl -s -o /dev/null -w "%{http_code}" \
-X POST "${GOTIFY_URL}/message?token=${GOTIFY_TOKEN}" \
-H "Content-Type: application/json" \
-d "{
\"title\": \"${title}\",
\"message\": \"${message}\",
\"priority\": ${priority},
\"extras\": {
\"client::display\": {
\"contentType\": \"text/markdown\"
}
}
}")
if [ "$response" -eq 200 ]; then
echo "Notification delivered successfully."
else
echo "Failed to send notification. HTTP status code: ${response}"
exit 1
fi
}
send_gotify_notification "System Alert" "Backup job completed on server **prod-db-01**." 8
Python Implementation
For background backend workers, a Python script offers strong error handling capabilities. Save this file as send_alert.py:
import sys
import requests
GOTIFY_URL = "https://push.yourdomain.com"
APP_TOKEN = "A1a2B3c4D5e6F7g"
def push_message(title: str, message: str, priority: int = 5) -> bool:
endpoint = f"{GOTIFY_URL}/message"
params = {"token": APP_TOKEN}
payload = {
"title": title,
"message": message,
"priority": priority,
"extras": {
"client::notification": {
"click": {"url": "https://push.yourdomain.com"}
}
}
}
try:
response = requests.post(endpoint, params=params, json=payload, timeout=10)
response.raise_for_status()
print(f"Message sent! ID: {response.json().get('id')}")
return True
except requests.exceptions.RequestException as err:
print(f"Error publishing notification: {err}", file=sys.stderr)
return False
if __name__ == "__main__":
push_message(
title="Database CPU Spike",
message="CPU utilization exceeded 92% for 5 consecutive minutes.",
priority=10
)
Node.js Implementation
When implementing stateful authentication workflows in Node.js apps, securing token handling is paramount. If you are building administrative backends, refer to our complete guide on Implementing JWT Refresh Token Rotation In Express to complement your notification delivery layer.
Here is a zero-dependency Native Fetch implementation for Node.js 18 and newer:
const GOTIFY_URL = "https://push.yourdomain.com";
const APP_TOKEN = "A1a2B3c4D5e6F7g";
async function sendPushNotification(title, message, priority = 5) {
const url = new URL("/message", GOTIFY_URL);
url.searchParams.append("token", APP_TOKEN);
const payload = {
title: title,
message: message,
priority: priority
};
try {
const response = await fetch(url.toString(), {
method: "POST",
headers: {
"Content-Type": "json"
},
body: JSON.stringify(payload)
});
if (!response.ok) {
throw new Error(`Server returned status code ${response.status}`);
}
const data = await response.json();
console.log(`Notification sent successfully. Message ID: ${data.id}`);
} catch (error) {
console.error(`Failed to push notification: ${error.message}`);
process.exit(1);
}
}
sendPushNotification("Payment Gateway Alert", "Failed charge attempt recorded for Account #4091", 7);
5. Advanced Integration and Event Deduplication
When hooking Gotify into high-frequency distributed monitoring systems or webhook processors like n8n Cloud, alert storms can quickly overwhelm your Android device or mobile client. If a failing microservice retries every 500 milliseconds, your push service might attempt to dispatch thousands of redundant alerts per minute.
To resolve this issue, apply robust event throttling patterns. Read our detailed architectural analysis on Handling Webhook Event Deduplication in Distributed Systems to learn how Redis cache layers and sliding window locks prevent duplicate processing.
Below is an updated workflow pattern that demonstrates how to send an HTTP alert from an n8n pipeline into your deployed Gotify instance:
- Add an HTTP Request node into your workflow canvas.
- Set Method to
POST. - Set URL to
https://push.yourdomain.com/message. - Add Query Parameter: Key
token, ValueA1a2B3c4D5e6F7g. - Set Body Content Type to
JSON. - Define JSON Parameters using expression values dynamically retrieved from incoming Webhook payloads:
{
"title": "Workflow Failure: {{$json.workflow.name}}",
"message": "Execution ID {{$json.execution.id}} failed on node {{$json.node.name}}. Error: {{$json.error.message}}",
"priority": 8
}
By leveraging custom priorities (ranging from 1 for lowest up to 10 for critical alerts), Gotify allows you to customize Android notification channels. Priority 10 messages can bypass Do Not Disturb rules on connected clients, making sure critical infrastructure failures wake you up while routine background task notifications remain silent.
Getting Started
To spin up your own real time self-hosted alert platform today:
- Provision an affordable server instance on Hetzner VPS or DigitalOcean.
- Register a dedicated alert subdomain through Namecheap.
- Execute the Docker Compose deployment scripts detailed above.
- Download the Gotify client application from Google Play or F-Droid, scan your server’s QR code, and start capturing events directly from your automated systems or n8n Cloud pipelines.
Outsource Your Automation
Don’t have time? I build production n8n workflows, WhatsApp bots, and fully automated YouTube Shorts pipelines. Hire me on Fiverr, mention SYS3-HUGO for priority. Or DM at chasebot.online.
Want to automate this yourself?
Start with n8n Cloud (free tier available) or self-host on a Hetzner VPS for full control.
Want this engine running on your own VPS?
This blog publishes itself — daily, unattended, on free API tiers. The full engine, Hugo theme, and setup guide are available as System 3.
Get System 3