If you're like me and have been hooked on running Claude Code on your phone, running multiple sessions in parallel like Boris, you'll have noticed that it's easy to lose track of what's going on in all those sessions. You can walk away for a second, get distracted by Minecraft parkour videos, and forget that Claude is waiting for your opinion.
ideas
Claude Code comes with a notification hook. Some terminals support it natively (iTerm2, Kitty, Ghostty), but most don't, and even when they do, it's a system notification that's easy to miss if you walk away.
The idea is to receive a phone notification when Claude Code needs your opinion. I considered a few options and ended up choosing ntfy as the notification provider.
To make sure everything stays private, I decided to host ntfy on my machine and use Tailscale as my private network.
I was also tired of dealing with bash scripts. I kept having compatibility issues between Mac, Linux and Windows, so I built a little tool to solve them (but you can still use bash).
Requirements
All you need is a Tailscale and Docker account for that. If you want to use bash, it helps to have jq
installed.
Step 0: Project Structure
These are the files you will need:
mi-infra/
├── .env
├── compose.yml
└── configuration/
└── ntfy.json
Step 1: Configure Backscaling ACL
Go to the ACL editor and add a tag: container
tag:
"Tag owners": {
"tag:container": ["autogroup:admin"]
}
Step 2 – Create an OAuth credential
Go to Trust and Credentials to generate a new OAuth credential.
- Click Credential → OAuth
- Scholarship
authentication_keys
writable scope - Select Tag
tag:container
- Copy the client secret (
tskey-client-...
)
OAuth works better because typical authentication keys expire between 1 and 90 days. OAuth client credentials do not expire and the container is automatically re-authenticated upon reboot.
Now add the OAuth key to your .env
:
TS_AUTHKEY=...
Step 3: Writing Docker
Your wording will look like below. Use tailscale/tailscale
and binwiederhier/ntfy
images and is based on the Tailscale sidecar pattern where you expose your Docker containers as machines on the back network. This is really useful because you can access the Docker container directly by name, the sidecar will render the request, handle HTTPS, etc.
name: mi-infra
services:
ts-ntfy:
image: queue scale/queue scale:latest
container_name: ts-ntfy
hostname: ntfy
restart: unless stopped
environment:
- TS_AUTHKEY=${TS_AUTHKEY}?ephemeral=false
- TS_EXTRA_ARGS=--advertise-tags=tag:container --reset
- TS_SERVE_CONFIG=/config/ntfy.json
- TS_STATE_DIR=/var/lib/backscale
- TS_USERSPACE=false
volumes:
- ts-ntfy-state:/var/lib/tailscale
- ./config:/config
devices:
- /dev/net/tun:/dev/net/tun
cap_add:
-net_admin
ntfy:
image: binwiederhier/ntfy
container_name: ntfy
restart: unless stopped
command: serve
environment:
NTFY_BASE_URL: "https://ntfy.<your-tailnet>.ts.net"
NTFY_UPSTREAM_BASE_URL: "https://ntfy.sh"
network_mode: service:ts-ntfy
depends_on:
-ts-ntfy
volumes:
ts-ntfy-status:
Note NTFY_UPSTREAM_BASE_URL
configuration. This forwards push notifications through ntfy.sh's Firebase/APN infrastructure for instant mobile delivery. Without it, notifications can be delayed by minutes or hours.
Step 4: Tailscale Service Configuration
config/ntfy.json
- this tells Tailscale to send HTTPS to ntfy port 80:
{
"TCP": {
"443": {
"HTTPS": true
}
},
"Website": {
"${TS_CERT_DOMAIN}:443": {
"Handlers": {
"/": {
"Proxy": "http://127.0.0.1:80"
}
}
}
}
}
Step 5: Get started
docker compose -d
Wait ~15 seconds for the TLS certificate to be provisioned. ntfy is now available at https://ntfy.<your-tailnet>.ts.net
from any device in your tailnet.
Tip: the name of your tailnet (the taila2944f
part) can be changed to something more readable in DNS settings. Also make sure "HTTPS Certificates" are enabled.
Step 6: Subscr