Self-hosted push notification service for personal apps, homelab services, and scripts to send alerts to iOS via HTTP POST, without requiring native APNs.
📌 Quick Reference
Component
Details
Service URL
Docker Path
~/docker/ntfy
CLI Path
~/bin/notify
Config File
~/docker/ntfy/config/server.yml
Primary Topic
parth-apps
Network
proxy (Docker external network)
🏗️ Architecture & Networking
Flow: Internet ➔ Cloudflare ➔ Cloudflare Tunnel (cloudflared) ➔ Docker proxy network ➔ http://ntfy:80 ➔ ntfy container.
Ports: ntfy does not expose host ports. It is only accessible via the proxy network.
Verification: Both cloudflared and ntfy must appear when running docker network inspect proxy.
Tunnel Target: http://ntfy:80
Cloudflare Access: Do NOT put CF Access authentication in front of ntfy; it breaks the iOS app's direct communication.
📂 Docker Configuration
Directory Structure (~/docker/ntfy/):
├── docker-compose.yml ├── cache/ │ ├── auth.db │ └── cache.db └── config/ └── server.yml
docker-compose.yml:
services: ntfy: image: binwiederhier/ntfy:latest container_name: ntfy command: - serve environment: TZ: Asia/Kolkata volumes: - ./cache:/var/cache/ntfy - ./config:/etc/ntfy restart: unless-stopped networks: - proxy
networks: proxy: external: true
⚙️ Server & iOS Configuration
server.yml key settings:
base-url: "https://ntfy.parthjain.tech" upstream-base-url: "https://ntfy.sh" cache-file: "/var/cache/ntfy/cache.db" auth-file: "/var/cache/ntfy/auth.db" auth-default-access: "deny-all"
Note: Both base-url and upstream-base-url are required for instant iOS push delivery.
🔐 Authentication & Tokens
Anonymous access is disabled. Authentication is required.
Primary User: parth (has read/write access to topic *)
Check Users: docker exec -it ntfy ntfy user list
Check ACLs: docker exec -it ntfy ntfy access
List Tokens: docker exec -it ntfy ntfy token list parth
Create Token: docker exec -it ntfy ntfy token add parth
Security Warning: Never commit access tokens (tk_...) to Git or place them directly in source code.
🗂️ Topics & Priorities
Topic Strategy:
parth-apps (Current): Reader, finance, scrapers, background jobs.
parth-homelab (Planned): Backups, docker, disk usage, updates.
parth-urgent (Planned): Security alerts, disk/backup failures (Immediate attention).
Supported Priorities:
min, low, default, high, max (Reserve max for critical failures).
💻 notify CLI Adapter
A lightweight Python adapter (~/bin/notify) is used to abstract the ntfy HTTP API. It requires no virtual environments or pip dependencies.
Required Environment Variables (e.g., in ~/.zshrc):
export NTFY_URL="https://ntfy.parthjain.tech" export NTFY_TOKEN="tk_REDACTED" export NTFY_TOPIC="parth-apps" export PATH="$HOME/bin:$PATH"
Basic Usage:
Bash
Standard notification
notify "Hello from the server"
With source and title
notify --source homelab --title "Backup complete" "Nightly backup completed successfully"
With priority and URL
notify --source reader --title "Article ready" --priority high --url "https://reader.parthjain.tech/articles/123" "Processing finished"
Cron & Script Usage:
Cron jobs must use the absolute path (/home/
Keep the source naming short and stable (e.g., backup, reader, docker). Titles render as [source] Title.
Direct API Testing (Without CLI):
Bash
curl -i -H "Authorization: Bearer $NTFY_TOKEN" -d "curl auth test" "$NTFY_URL/$NTFY_TOPIC"
🛠️ Troubleshooting
HTTP 403 Forbidden:
Verify the user's ACLs and token validity.
If curl works but the Python script fails, check the User-Agent. Cloudflare blocks default Python user agents. The CLI explicitly uses User-Agent: parth-notify/1.0 to bypass this.
Docker Compose Errors (not found):
Ensure you are executing commands from ~/docker/ntfy.
Alternatively use docker compose -f ~/docker/ntfy/docker-compose.yml logs.
Checking Logs & Status:
Logs: docker compose logs --tail=50 -f ntfy (Run from ~/docker/ntfy)
Status: docker ps --filter name=ntfy
📐 Design Rules
Notifications are side-effects: A failed notification must not cause the primary application/script to fail.
Keep it simple: Do not implement databases, delivery tracking, message brokers (Redis/RabbitMQ), or retry queues unless an application explicitly demands it. ntfy is the service.
Do not preemptively over-engineer: Features like separate app tokens, tags, actions, or Apprise routing should only be added when actively needed.