This Docker container provides a Telegram integration to notify you about Docker events. It can notify you when a container starts, stops (including details about exit codes) and when the healthcheck status of a Docker container changes. A restart is reported as a stop followed by a start, since that is what Docker emits. You have the flexibility to customize these notifications by modifying the templates.js file.
This fork was created to address security vulnerabilities and add support for
linux/arm64andlinux/arm/v7in addition tolinux/amd64.
Note
The image is built on node:22-slim rather than node:lts-slim. Node 24 dropped
support for 32-bit ARM, so tracking the lts tag would silently drop linux/arm/v7.
Node 22 receives security support until April 2027; linux/arm/v7 support will be
reconsidered before then.
If you encounter any issues, please feel free to contribute by fixing them and opening a pull request or reporting a new issue.
- 1. Basic setup
- 2. Advanced setup
- 3. Notification messages customization
- 4. Securing the docker socket
- Credits
-
Set up a Telegram bot
- create a Telegram bot and obtain the Bot Token
- optionally add the bot to a group and allow it to post messages
- extract the Chat ID
-
Run the container
using
docker-compose.yamlservices: telegram-notifier: image: lorcas/docker-telegram-notifier:latest volumes: - /var/run/docker.sock:/var/run/docker.sock:ro # for local instance environment: TELEGRAM_NOTIFIER_BOT_TOKEN: <bot_token> TELEGRAM_NOTIFIER_CHAT_ID: <chat_id>
using
docker rundocker run -d \ --env TELEGRAM_NOTIFIER_BOT_TOKEN=<bot_token> \ --env TELEGRAM_NOTIFIER_CHAT_ID=<chat_id> \ --volume /var/run/docker.sock:/var/run/docker.sock:ro \ --hostname my_host \ lorcas/docker-telegram-notifier
-
Add a healthcheck to your container (optional)
example: image: hello-world healthcheck: test: ["CMD", "curl", "-sS", "http://127.0.0.1:8545", "||", "exit", "1"] interval: 30s timeout: 10s retries: 3
This setup will start the container and notify you about Docker events. For more advanced configuration, see the Advanced setup section.
The following options are available to customize the behavior of the notifier. Examples are provided for docker-compose.yaml but are also applicable to docker run. Only the changes are shown, make sure to include the rest from the Basic setup section.
Use TELEGRAM_NOTIFIER_TOPIC_ID or TELEGRAM_NOTIFIER_THREAD_ID for specific topics/threads:
services:
telegram-notifier:
environment:
TELEGRAM_NOTIFIER_TOPIC_ID: <topic_id> # optional use only one
TELEGRAM_NOTIFIER_THREAD_ID: <thread_id> # optional use only oneDisable notifications for specific containers:
services:
example:
image: hello-world
labels:
telegram-notifier.monitor: falsedocker run
docker run -d --label telegram-notifier.monitor=false hello-worldReceive notifications only from whitelisted containers by setting ONLY_WHITELIST=true and labeling desired containers. The variable is off unless set to a value other than false, 0, no or off:
services:
telegram-notifier:
environment:
ONLY_WHITELIST: true
example:
image: hello-world
labels:
telegram-notifier.monitor: truedocker run
docker run -d --label telegram-notifier.monitor=true hello-worldConfigure different channels/threads per container:
services:
example:
image: hello-world
labels:
# Channel override (optional)
telegram-notifier.chat-id: "-100123456789"
# Thread/Topic override (optional - use only one)
telegram-notifier.topic-id: "12345"
# : "false" # would explicitely override to use NONE
telegram-notifier.thread-id: "12345"
# : "" # would also explicitely override to use NONEImportant
When leaving away a .topic-id / .thread-id label, but having one defined globally as per Topics and Threads, then that will be used automatically as fallback.
If that is unintended, you have to explicitely set the label to EMPTY (or false).
- Example scenario: when for example the per container
chat-iddiffers from the global, and has NO Topics / Threads support.
docker run
docker run -d --label telegram-notifier.chat-id=-100123456789 --label telegram-notifier.topic-id=12345 hello-worldBy default notifier connects to a local docker instance (don't forget to specify --volume /var/run/docker.sock:/var/run/docker.sock:ro for this case). But if you have monitoring and the service on the same host, you will not receive notifications if the host goes down. So I recommend to have monitoring separately.
Notifier accepts usual DOCKER_HOST and DOCKER_CERT_PATH environment variables to specify remote instance. For http endpoint you need to specify only --env DOCKER_HOST=tcp://example.com:2375 (make sure to keep such instances behind the firewall). For https, you'll also need to mount a volume with https certificates that contains ca.pem, cert.pem, and key.pem: --env DOCKER_HOST=tcp://example.com:2376 --env DOCKER_CERT_PATH=/certs --volume $(pwd):/certs.
A tutorial on how to generate docker certs can be found here.
services:
telegram-notifier:
volumes:
# disable for remote ONLY monitoring
# - /var/run/docker.sock:/var/run/docker.sock:ro
- ./certs:/certs # for remote instance
environment:
DOCKER_HOST: tcp://example.com:2376 # http/https is detected by port number
DOCKER_CERT_PATH: /certs # should contain ca.pem, cert.pem, key.pemEnvironment variables are readable by anyone who can run docker inspect on the container. To keep the bot token out of them, append _FILE to the variable name and point it at a file:
services:
telegram-notifier:
image: lorcas/docker-telegram-notifier:latest
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
environment:
TELEGRAM_NOTIFIER_BOT_TOKEN_FILE: /run/secrets/telegram_bot_token
TELEGRAM_NOTIFIER_CHAT_ID: <chat_id>
secrets:
- telegram_bot_token
secrets:
telegram_bot_token:
file: ./telegram_bot_token.txtTELEGRAM_NOTIFIER_CHAT_ID_FILE works the same way. A trailing newline in the file is ignored. If the file cannot be read, the container stops immediately with exit code 100 and names the file it tried to open.
If the host reaches the internet through a proxy, set HTTPS_PROXY and the notifier will send its Telegram requests through it:
services:
telegram-notifier:
image: lorcas/docker-telegram-notifier:latest
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
environment:
TELEGRAM_NOTIFIER_BOT_TOKEN: <bot_token>
TELEGRAM_NOTIFIER_CHAT_ID: <chat_id>
HTTPS_PROXY: http://proxy.example.com:8080docker run
docker run -d \
--env TELEGRAM_NOTIFIER_BOT_TOKEN=<bot_token> \
--env TELEGRAM_NOTIFIER_CHAT_ID=<chat_id> \
--env HTTPS_PROXY=http://proxy.example.com:8080 \
--volume /var/run/docker.sock:/var/run/docker.sock:ro \
lorcas/docker-telegram-notifierThe lowercase https_proxy is accepted as well. This only affects the connection to Telegram; the docker socket is not reached over the network. An unusable proxy URL stops the container with exit code 100.
-
Adapt the template: download and modify the message strings from
templates.jsaccording to your needs. -
Bind your customized file to the container:
using
docker-compose.yamlservices: notifier: volumes: # Bind customized file to templates.js in the container: - ./my-template.js:/usr/src/app/templates.js:ro environment: # ...
docker run
docker run -d \ --env TELEGRAM_NOTIFIER_BOT_TOKEN=token \ --env TELEGRAM_NOTIFIER_CHAT_ID=chat_id \ --volume /var/run/docker.sock:/var/run/docker.sock:ro \ --volume ./my-template.js:/usr/src/app/templates.js:ro \ --hostname my_host \ lorcas/docker-telegram-notifier
Here are some variables available to customize the notification messages.
| Variable | Description |
|---|---|
${e.Actor.ID} |
Container ID (full, 64 characters) |
${e.Actor.Attributes.name} |
Container name |
${e.Actor.Attributes.image} |
Container image used |
${e.Actor.Attributes.exitCode} |
Container exit code (die events only) |
${e.Actor.Attributes.execDuration} |
Seconds the container ran (die events only) |
Beyond those, every label on the container is available under the same path, which is what makes custom container information work. That includes the labels docker compose adds by itself, listed below.
Attributes is a plain object, so values are undefined when the event does not carry them β exitCode on a start event, for instance. The authoritative list of what an event can contain is the Docker Engine API; the notifier passes it through unchanged, apart from HTML-escaping the values.
Example:
container_start: e =>
`✅ Container Started\n` +
`Name: <b>${e.Actor.Attributes.name}</b>\n` +
`Image: <code>${e.Actor.Attributes.image}</code>\n` +
`ID: <code>${e.Actor.ID.slice(0, 12)}</code>`π’ Container Started
Name: my-container
Image: nginx:latest
ID: abc123def456
The following variables are only available if the container was started using docker compose
| Variable | Description |
|---|---|
${e.Actor.Attributes['com.docker.compose.container-number']} |
Compose container Number |
${e.Actor.Attributes['com.docker.compose.project']} |
Compose Project Name |
${e.Actor.Attributes['com.docker.compose.service']} |
Compose Service Name |
${e.Actor.Attributes['com.docker.compose.version']} |
Compose Version |
Compose adds more than these β com.docker.compose.config-hash, com.docker.compose.image, com.docker.compose.oneoff, com.docker.compose.project.config_files and com.docker.compose.project.working_dir are present as well. They are ordinary labels, so they are reached the same way.
Example:
container_start: e =>
`✅ Container Started\n` +
`Project: <b>${e.Actor.Attributes['com.docker.compose.project']}</b>\n` +
`Service: <b>${e.Actor.Attributes['com.docker.compose.service']}</b> (#${e.Actor.Attributes['com.docker.compose.container-number']})\n` +
`Image: <code>${e.Actor.Attributes.image}</code>\n` +
`Compose Version: <code>${e.Actor.Attributes['com.docker.compose.version']}</code>`π’ Container Started
Project: myproject
Service: webserver (#1)
Image: nginx:latest
Compose Version: 2.17.2
Leverage the labels: defintion on docker services to make custom information available to notification messages:
-
Add custom labels to a container:
using
docker-compose.yamlservices: example: image: hello-world labels: # Monitor control telegram-notifier.monitor: true # Custom defined labels and information mycustom.telegram.container-info: "Access via http://myhost.com/"
using
docker run:docker run -d \ --label "telegram-notifier.monitor=true" \ --label "mycustom.telegram.container-info=Access via http://myhost.com/" \ hello-world -
Adapt your customized messages template:
container_start: e => `▶️ <b>${e.Actor.Attributes.name}</b> started\n` + `Image: <code>${e.Actor.Attributes.image}</code>` + ( e.Actor.Attributes['mycustom.telegram.container-info'] ? `\nNOTE: ${e.Actor.Attributes['mycustom.telegram.container-info']}` : '' )
The basic setup mounts the docker socket read-only:
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro:ro protects the socket file, not the API behind it. Anything that can reach the docker socket can create a container, mount any host path into it and run it as root β so it can take over the host, read-only mount or not. That applies to every tool that reads docker events this way, this one included.
This notifier only needs four read-only endpoints: version, info, ping and events. A socket proxy is a small container that exposes exactly those and refuses everything else:
services:
docker-socket-proxy:
image: tecnativa/docker-socket-proxy:latest
environment:
EVENTS: 1 # the event stream itself
INFO: 1 # host details in the start-up message
PING: 1 # the healthcheck's liveness probe
VERSION: 1 # docker version in the start-up message
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
restart: unless-stopped
telegram-notifier:
image: lorcas/docker-telegram-notifier:latest
depends_on:
- docker-socket-proxy
environment:
DOCKER_HOST: tcp://docker-socket-proxy:2375
TELEGRAM_NOTIFIER_BOT_TOKEN: <bot_token>
TELEGRAM_NOTIFIER_CHAT_ID: <chat_id>
restart: unless-stoppedThe notifier gets no volume at all in this setup β it talks HTTP to the proxy, and the proxy is the only container holding the socket. Everything the proxy does not explicitly allow is refused, so a compromised notifier cannot create containers.
Keep the proxy off any published port. It has no authentication, so anything that can reach it inherits its permissions.
This combination is tested: with those four permissions enabled and everything else at its default of off, both notifications and the healthcheck work.
PINGis easy to miss β the healthcheck uses it to tell a live daemon from a stalled event stream.
This container is based on the container by poma, originally an idea of arefaslani.