| Filename | Latest commit message | Latest commit date |
|---|---|---|
|
|
||
| docker-compose.yml | ||
| README.md | ||
| renovate.json | ||
coolify-ntfy
Self-hosted ntfy for Coolify, with messages persisted in PostgreSQL and optional outgoing SMTP.
Deploy
-
Coolify → + New → Docker Compose Empty, point it at this repo.
-
Deploy. Coolify generates the domain, the Postgres password, and the TLS cert.
-
Create the first user — the stack ships closed (
deny-all), so until you do this every request is rejected:docker exec -it <ntfy-container> ntfy user add --role=admin adminFind the container name under the service in Coolify, or
docker ps | grep ntfy. -
Log in at the generated domain.
Then grant access per topic, e.g. read-write on alerts for everyone:
docker exec -it <ntfy-container> ntfy access '*' alerts rw
Configuration
Config is split across two places, and deliberately does not overlap. ntfy's precedence is CLI > environment > config file, so a setting present in both would have the env value silently win.
Host config file — /var/coolify/ntfy/server.yml
Almost everything lives here. The ntfy-config sidecar writes this file on the
first deploy only if it does not already exist, then exits; every later
deploy leaves it alone. Edit it directly on the host and restart the ntfy
service to apply — your changes are not overwritten.
| Setting | Default | Purpose |
|---|---|---|
cache-duration |
720h |
How long messages stay fetchable via ?poll=1 / ?since=. 0 disables storage entirely. |
auth-default-access |
deny-all |
read-write opens the server to anonymous publish and read. |
enable-login |
true |
Web UI login. |
enable-signup |
false |
Self-service account creation. Leave off on a public domain. |
behind-proxy |
true |
Trust Traefik's forwarded IP for rate limiting. |
listen-http |
:80 |
Container listen address. |
attachment-cache-dir |
/var/cache/ntfy/attachments |
Bound to /var/coolify/ntfy/attachments on the host. |
attachment-total-size-limit |
5G |
Total attachment storage cap. |
attachment-file-size-limit |
15M |
Per-file cap. |
attachment-expiry-duration |
3h |
How long uploads survive before ntfy prunes them. |
ntfy takes full control of the attachment directory and deletes expired files from it — keep it dedicated to attachments and store nothing else there.
To change a default for new deployments, edit the heredoc in
docker-compose.yml. To change a running instance, edit the host file.
Environment variables — Coolify UI
Only what genuinely cannot live in the config file:
| Variable | Why it must be env |
|---|---|
SERVICE_FQDN_NTFY_80 |
Declaration that makes Coolify assign the domain and route Traefik. |
NTFY_BASE_URL |
Comes from SERVICE_URL_NTFY; changes if you rename the deployment, so it must not be frozen into a generate-once file. |
NTFY_DATABASE_URL |
Holds the Coolify-generated Postgres password; keeps the secret out of a plaintext host file. |
SMTP_ADDR |
host:port of the SMTP server. Empty = email sending off. |
SMTP_USER / SMTP_PASS |
SMTP credentials — kept out of the host file. PLAIN auth over STARTTLS only. |
SMTP_FROM |
Sender address on outgoing mail. |
TZ |
Container-level variable — ntfy has no config-file equivalent. Defaults to Europe/Helsinki. |
Coolify generates SERVICE_PASSWORD_POSTGRES and the SERVICE_FQDN_NTFY_80 /
SERVICE_URL_NTFY pair itself — don't set them by hand.
Note that ntfy's precedence is CLI > environment > config file, so any NTFY_*
variable you add in the Coolify UI silently overrides the host file. That is why
the two surfaces are kept disjoint.
Sending email
Set the four SMTP_* variables in Coolify's Environment Variables tab and
redeploy. Only PLAIN auth over STARTTLS is supported. Then:
curl -d "backup finished" -H "X-Email: you@example.com" https://<your-domain>/alerts
Backups
The message store is the postgres service, so Coolify's scheduled database
backups cover it. Add one under the Postgres service → Backups. The ntfy
container holds no state and needs no backup.
Notes
docker-compose.ymlis Coolify-only and will not run under plaindocker compose—exclude_from_hcis a Coolify extension, and vanilla compose rejects it withadditional properties 'exclude_from_hc' not allowed. Delete that one line to run the stack locally; it exists so Coolify does not count the exitedntfy-configsidecar against overall stack health.- The sidecar reuses the ntfy image rather than pulling busybox, so there is no
second image to keep patched. It runs as root, so the generated
server.ymlis root-owned — edit it withsudo. - Generate-once cuts both ways. New settings added to the heredoc in
docker-compose.ymldo not reach an instance that already has aserver.yml— the sidecar sees the file and leaves it alone. After pulling a change that adds config keys (theattachment-*block, for one), either copy the new lines into/var/coolify/ntfy/server.ymlby hand, or delete the file and redeploy to regenerate it from scratch. - ntfy pings Postgres once at startup and exits
FATALif the database isn't up yet — it has no retry loop.depends_onhandles both halves of this (condition: service_healthyfor the first boot,restart: trueso ntfy is relaunched whenever Postgres restarts). If you do see adatabase ping failedline in the logs right after a Postgres backup or upgrade, that is this race and it self-heals. - Attachments and web push are off. Both need extra config (
attachment-cache-dirplus a volume; VAPID keys viantfy webpush keys) and neither was asked for. - SQLite is the one-container alternative: drop the
postgresservice, swapNTFY_DATABASE_URLforNTFY_CACHE_FILE/NTFY_AUTH_FILEon a volume. You lose Coolify's managed database backups.