Why Run Your Own Notification Server#
If you have even a minimal homelab, you’ve probably run into this: notifications pour in from everywhere - Proxmox, PBS backups, security systems, and so on. All of it lands in one shared stream in your messenger of choice - in my case, a personal Telegram bot, mixed in with regular messages. Sooner or later something actually important gets lost in that pile - a disk that failed a SMART test, or someone scanning your ports. And beyond that, it just starts registering as noise your brain eventually tunes out.
The fix is to set up a separate channel just for what happens in the homelab.
Gotify is a lightweight self-hosted service built for exactly that: simple, quick to deploy, flexible to configure, and it doesn’t drag in a pile of dependencies.
On top of everything else, a Telegram bot has no message priorities, no separate per-application sources, and no built-in severity levels - everything just flows into one feed. That’s one more reason to move away from it.
How Gotify Works#
The workflow is dead simple and fits into a single diagram:
flowchart LR
A1[Proxmox VE] -->|push| G((Gotify server))
A2[CrowdSec] -->|push| G
A3[Any other service
Linux / Windows] -->|push| G
G --> W[Web UI]
G --> AND[Official Android client]
G --> IOS[Unofficial iOS client
iGotify]
- Applications are the apps or services that send notifications to the Gotify server. Windows/Linux in the diagram just refers to the operating systems these sender apps can run on - the Gotify server itself is a single instance running in a Docker container.
- Gotify server is the central component that receives messages and delivers them to subscribed clients.
- Clients are what you use to read notifications: the web UI, the official Android app, or the unofficial (but working) iGotify app for iOS/iPadOS, which isn’t listed on the official site because it’s maintained by an enthusiast, not the project team.
Step 1. Installing via Docker Compose#
Gotify can be deployed as a binary or in Docker - the latter is more convenient if you already run everything else in containers.
The official docker-compose.yml is hidden on the project’s site behind a docker run setup, tucked into a collapsible section. Below is my actual working file - already paired with the Komodo alerter I’ll cover in step 6, which is why both services sit in the same Docker network:
services:
gotify:
image: gotify/server
container_name: gotify
volumes:
- /home/stilicho/docker/gotify:/app/data
#ports:
# - "xxxx:80"
restart: unless-stopped
security_opt:
- no-new-privileges:true
networks:
- gotify
- proxy
environment:
- TZ=${TZ}
- GOTIFY_OIDC_ENABLED=true
- GOTIFY_OIDC_ISSUER=${GOTIFY_OIDC_ISSUER}
- GOTIFY_OIDC_CLIENTID=${GOTIFY_OIDC_CLIENTID}
- GOTIFY_OIDC_CLIENTSECRET=${GOTIFY_OIDC_CLIENTSECRET}
- GOTIFY_OIDC_REDIRECTURL=${GOTIFY_OIDC_REDIRECTURL}
- GOTIFY_OIDC_LINK_BY_USERNAME=true
labels:
- "traefik.enable=true"
- "traefik.http.routers.gotify.entrypoints=web"
- "traefik.http.routers.gotify.rule=Host(`gotify.stilicho.ru`)"
- "traefik.http.middlewares.gotify-https-redirect.redirectscheme.scheme=https"
- "traefik.http.routers.gotify.middlewares=gotify-https-redirect"
- "traefik.http.routers.gotify-secure.entrypoints=websecure"
- "traefik.http.routers.gotify-secure.rule=Host(`gotify.stilicho.ru`)"
- "traefik.http.routers.gotify-secure.tls=true"
- "traefik.http.routers.gotify-secure.service=gotify"
- "traefik.http.services.gotify.loadbalancer.server.port=80"
- "traefik.docker.network=proxy"
komodo-gotify:
container_name: gotify-alerter
image: foxxmd/komodo-gotify-alerter:latest
environment:
- GOTIFY_URL=${GOTIFY_URL}
- GOTIFY_APP_TOKEN=${GOTIFY_APP_TOKEN}
networks:
- gotify
ports:
- "7000:7000"
depends_on:
- gotify
networks:
gotify:
external: true
proxy:
external: trueAll the ${TZ}, ${GOTIFY_OIDC_ISSUER}-style variables are substituted by compose from a .env file that needs to sit next to docker-compose.yml:
# Gotify / Authentik OIDC
TZ=Europe/Moscow
GOTIFY_OIDC_ISSUER=https://authentik.stilicho.ru/application/o/gotify/
GOTIFY_OIDC_CLIENTID=your_client_id_from_authentik
GOTIFY_OIDC_CLIENTSECRET=your_client_secret_from_authentik
GOTIFY_OIDC_REDIRECTURL=https://gotify.stilicho.ru/auth/oidc/callback
GOTIFY_URL=https://gotify.stilicho.ru
GOTIFY_APP_TOKEN=komodo_application_token_from_gotifyGOTIFY_OIDC_CLIENTSECRET and GOTIFY_APP_TOKEN are real secrets: the first grants access to the OIDC client in Authentik, the second lets someone send messages into Gotify on behalf of the application. Don’t commit .env to git - add it to .gitignore right next to the compose file, and if a secret ever does leak somewhere (say, it accidentally ends up in a chat, a log, or a public repo), rotate it right away: recreate the OIDC client secret in Authentik, and delete and recreate the application token in Gotify.
Let’s go through what matters here:
volumes- a bind mount is used instead of a named volume: the host path is spelled out explicitly, which makes backups and peeking into the database easier.ports- commented out: direct port access is disabled, and Gotify is only exposed externally through Traefik (see thelabelsblock below). If you’re not using Traefik, uncommentportsand droplabels- the service will then be reachable directly athttp://<server-ip>:<port>.security_opt: no-new-privileges:true- if the container is ever compromised, the attacker can’t escalate privileges or break out of it.networks: gotifyandproxy- both networks are external (external: true), meaning compose doesn’t create them itself and expects them to already exist.gotifyis a dedicated network just for Gotify and its alerter,proxyis Traefik’s shared network. Both need to be created ahead of time if you haven’t already set them up for other services:docker network create gotify docker network create proxyTZ- the timezone, pulled from.env: Gotify deals with notifications and needs to know exactly when something happened.GOTIFY_OIDC_*- enables SSO through an external provider (Authentik, in my case), values also from.env. If you don’t have your own OIDC provider, feel free to drop this whole block of four variables - Gotify works perfectly fine with a plain local login/password too.GOTIFY_OIDC_LINK_BY_USERNAME=truelinks an OIDC identity to an already-existing local Gotify user with a matching username, rather than creating a new one.labels- the standard set for publishing through Traefik: an HTTP router that redirects to HTTPS, an HTTPS router with TLS, and a load balancer pointed at the container’s internal port 80. Replace thegotify.stilicho.rudomain with your own.komodo-gotify- this is the Komodo alerter from step 6; it’s deployed in the same file, in the samegotifynetwork, so in Komodo you can just point the endpoint at the container name, no IP needed.GOTIFY_URLandGOTIFY_APP_TOKENalso come from.env.
Bring the stack up:
docker compose up -dThe service is lightweight and starts up almost instantly - it doesn’t need any dependencies like a separate database (SQLite is used by default).
You can pass the admin login and password straight through environment variables (GOTIFY_DEFAULTUSER_NAME / GOTIFY_DEFAULTUSER_PASS), but it’s more convenient and safer to just log in with the default admin/admin and change the password right away through the web UI - that way the password doesn’t end up sitting in plain text in the compose file.
Step 2. First Login and Changing the Password#
Open the address where Gotify is running - in the example above that’s the domain behind Traefik (https://gotify.stilicho.ru), or, if you uncommented ports instead of labels, http://<server-ip>:<port> - and log in with admin/admin.
If you enabled GOTIFY_OIDC_* (SSO through Authentik or another provider), an extra SSO login button will show up on the login page. The local admin/admin account doesn’t go anywhere - it’s a separate, independent way to log in.
Right after logging in:
- Open the Users section.
- Click on the
adminuser. - Set a new password (or a new name, if you want) and save.
You can also create additional users here if needed, via the Create User button - with or without admin rights, if you want to give someone else access.
Step 3. Applications and Clients#
Gotify has two independent concepts that are easy to mix up:
- Apps - sources that send notifications. Each application has its own token and its own default priority (0 to 10, where 10 is the most important).
- Clients - recipients through which you read notifications (web browser, mobile app). Each client also has its own token.
Create a test application:
- Go to Apps → Create Application.
- Give it a name, e.g.
Test, and an optional short description. - Set
Default Priority- a middle value is fine at this stage. - Copy the generated application token - any service sending messages through Gotify will need it.
The process for a mobile client is similar, just under Clients:
- Clients → Create, give it a name (e.g. after the phone it’s for).
- Copy the client token.
- In the app on your phone (Android - the official client, iOS/iPadOS - iGotify), enter your Gotify server URL and this token.
The “Copy to clipboard” button in the Gotify UI doesn’t always work reliably - if the token doesn’t copy, just select and copy it manually.
Step 4. Notifications From Proxmox VE#
Starting with Proxmox VE 8.x, Gotify support is built in out of the box - no separate agent needed.
- In the Proxmox UI, go to Datacenter → Notifications → Notification Targets.
- Click Add → Gotify.
- Fill in:
- Name - any name for the target, e.g.
homelab; - Server URL - your Gotify server’s address (
https://gotify.example.com, or just the IP if the service isn’t exposed externally); - API Token - the application token created in the previous step (it’s handy to create a dedicated application, e.g.
Proxmox, with priority 10).
- Name - any name for the target, e.g.
- Click Add - the target is created.
- Immediately test the connection with the Test button: a test message should arrive, visible in All Messages in the Gotify UI.
Next, you need to create a Notification Matcher - a rule that links Proxmox events to the target you just created:
- Datacenter → Notifications → Notification Matchers → Add.
- Give it a name, e.g.
homelab-matcher, and check Enable. - In the rules section click Add, choose the
Match Severitytype, and leave all levels checked (info, notice, warning, error, unknown) - this way notifications will come in for everything happening on the hypervisor. - Under Target, pick the target you created earlier (
homelab). - Save.
The easiest way to check that it works is with a real event - for example, by running a manual backup of an LXC container or a VM. A message with the node name, container ID, status, and duration will show up in Gotify almost immediately.
Step 5. Notifications From CrowdSec#
If you’re running CrowdSec (say, alongside Traefik), notifications about port scan attempts or ban triggers are exactly the kind of thing that shouldn’t get lost in the noise.
CrowdSec supports Gotify through its built-in http plugin. There’s one gotcha that makes this easy to break - the plugin name in two files has to match exactly.
- Go to the directory where CrowdSec is deployed, into the
configsubfolder. - Open
profiles.yamland uncomment (or add) two lines:notifications:- http_default(the name of your http plugin -http-defaultby default)
- In the
notificationssubfolder, open (or create) thehttp.yamlfile. Pay attention to thename:line at the top of the file - it has to match word for word what you put inprofiles.yaml. - In that same
http.yaml, fill in:url:- your Gotify server address with the application token included (create a dedicated application in Gotify for CrowdSec, with maximum priority); the URL format and request structure are given in CrowdSec’s official documentation for the Gotify http plugin;- when copying the config from the site, double-check the YAML spacing and indentation - one extra or missing character, and the container will start up fine, but the notification plugin just won’t work.
- Restart the stack:
docker compose restartIf the plugin name doesn’t match between profiles.yaml and http.yaml (say, http-goify in one file and http-default in the other), CrowdSec will start up without a single error in the logs, but notifications simply won’t arrive. If your whole traffic path runs through Traefik → CrowdSec → Authentik, that chain won’t break because of failed notifications - but you just won’t find out in time that CrowdSec has stopped working properly.
It’s hard to test the plugin without a false trigger - there’s no “internal” test event. The easiest option is to temporarily ban your own IP by hand via cscli decisions add - a ban notification should show up in Gotify almost instantly.
Step 6. Notifications From Komodo#
If you manage your stacks through Komodo (a Portainer replacement), it has no native Gotify support - only a Custom alerter type that sends a webhook to an arbitrary HTTP endpoint. The “translator” role between Komodo and Gotify is handled by a small third-party service - komodo-gotify-alerter by FoxxMD. It receives the webhook from Komodo and forwards it on to Gotify as a regular push message.
The komodo-gotify service is already described in the shared compose file from step 1 - it comes up alongside Gotify itself, in the same gotify network, and listens on port 7000. Here’s a reminder of which variables matter for it:
GOTIFY_URLandGOTIFY_APP_TOKEN- your Gotify server’s address and an application token; create a dedicated application in Gotify for this, e.g.Komodo.- optionally you can add
GOTIFY_OK_PRIORITY,GOTIFY_WARNING_PRIORITY,GOTIFY_CRITICAL_PRIORITY- mapping Gotify notification priority to Komodo’s alert severity level; - as well as
UNRESOLVED_TIMEOUT_TYPESandUNRESOLVED_TIMEOUT- a handy option for noisy alert types likeServerCpuorServerMem: if the state returns to normal within the given time (in milliseconds), no notification gets sent at all. I don’t have these in my file, but they can be added toenvironmentthe same way as the other variables, if you want them.
The rest of the setup is entirely on Komodo’s side:
- In the Komodo UI, go to Alerters → Create Alerter.
- Type - Custom.
- In the endpoint field, enter the alerter container’s address and port 7000. Since Gotify and the alerter sit in the same
gotifyDocker network, you can just use the container name:http://gotify-alerter:7000(if Komodo is running in a different network, use the host’s IP instead). - Optionally restrict which Alert Types should go to Gotify.
- Save and click Test Alerter - a test message should arrive in Gotify just like any other push notification.
Since this adds one more HTTP hop to the chain (Komodo → alerter → Gotify), it’s worth setting up a dedicated application in Gotify for this alerter with a clear name and a high priority - that way you can immediately tell in the message list that a notification came from Komodo specifically, and not from the Docker host itself.
What Else Is Worth Connecting#
Out of everything that can send notifications into Gotify out of the box, three services matter most for a home infrastructure:
- Proxmox VE - backup status and hypervisor events (see above);
- CrowdSec - port scan attempts and ban triggers (see above);
- Uptime Kuma - any monitored service going down; Gotify is supported natively, and the setup is the same idea - your URL and application token in the Notifications section.
Everything else is optional at this point, but Gotify has a fairly large community: the GitHub contrib page has ready-made plugins for Zabbix, MQTT, email ingestion, Slack, and dozens of other scenarios, or you can write your own in Go.
Telegram and email aren’t among the services with built-in Gotify support yet - if you need those too, you’ll still have to set up separate integrations for them.
Bottom Line#
Gotify solves a narrow but very common problem: getting push notifications only about what’s happening on your own infrastructure, without mixing them into your personal chats. Deployment takes a few minutes, the config is minimal, and thanks to native support in Proxmox VE, CrowdSec, and Uptime Kuma, you can quickly cover the three most critical event sources in a home lab.
Official project documentation: gotify.net/docs Source code: github.com/gotify/server





