Tutorial: WordPress + Telegram webhook¶
This tutorial publishes two services through one certify-reverse instance:
wordpress.<DOMAIN>-> a WordPress container;telegram.<DOMAIN>-> a minimal Telegram webhook receiver.
The example also includes MariaDB, persistent volumes, health checks, a webhook secret, and an optional webhook-registration script.
internet or LAN clients
|
v
Caddy / certify-reverse
| |
v v
wordpress:80 telegram-bot:8080
|
v
wordpress-db:3306
The database never becomes a public upstream.
What this example teaches¶
By the end, you will have practiced:
- adding several services to the same Compose project;
- routing by Docker service name instead of container IP;
- keeping application secrets separate from the DNS token;
- checking internal health before testing HTTPS;
- registering a Telegram webhook without printing the bot token;
- cleaning up containers while preserving or removing volumes deliberately.
1. Prepare certify-reverse¶
Start from a working clone and create the main configuration files:
Edit the root .env:
DOMAIN=example.net
ACME_EMAIL=admin@example.net
DNS_PROVIDER=desec
DNS_TOKEN=REPLACE_WITH_YOUR_REAL_DNS_TOKEN
CADDY_DNS_PLUGIN_TOKEN_FIELD=token
CADDY_VERSION=latest
DNSMASQ_ADDRESS_MODE=manual
DNSMASQ_ADDRESS_IP=192.168.1.20
Replace every example value. The DNS token belongs only in the root .env.
2. Prepare application secrets¶
Copy the example application environment file:
Generate three independent random values. One portable option is:
python3 - <<'PY'
import secrets
for label in ("WORDPRESS_DB_PASSWORD", "WORDPRESS_DB_ROOT_PASSWORD", "TELEGRAM_WEBHOOK_SECRET"):
print(f"{label}={secrets.token_urlsafe(32)}")
PY
Paste those values into examples/wordpress-telegram/.env.
Leave TELEGRAM_BOT_TOKEN as a placeholder until you want to connect a real bot.
Do not reuse secrets
The WordPress database password, MariaDB root password, Telegram bot token, DNS token, and webhook secret protect different systems. Do not use the same value for more than one field.
3. Review the upstream configuration¶
The provided upstreams.yml contains:
The ip values are Compose service names. They work because the overlay adds the
application services to the same default network as Caddy.
4. Validate before starting¶
Preview the Caddyfile:
Confirm that it contains both public hosts:
Validate the complete Compose model:
docker compose \
--env-file examples/wordpress-telegram/.env \
-f docker/docker-compose.yml \
-f examples/wordpress-telegram/compose.override.yml \
config --quiet
Expected: both commands return successfully and no secret value appears in the printed Caddyfile.
5. Start the complete stack¶
docker compose \
--env-file examples/wordpress-telegram/.env \
-f docker/docker-compose.yml \
-f examples/wordpress-telegram/compose.override.yml \
up -d --build
Inspect state:
docker compose \
--env-file examples/wordpress-telegram/.env \
-f docker/docker-compose.yml \
-f examples/wordpress-telegram/compose.override.yml \
ps
MariaDB may remain in health: starting briefly. WordPress waits for the database
health check.
Follow logs in separate terminals:
docker compose \
--env-file examples/wordpress-telegram/.env \
-f docker/docker-compose.yml \
-f examples/wordpress-telegram/compose.override.yml \
logs --follow wordpress telegram-bot wordpress-db
6. Check the services inside Docker first¶
Before involving DNS or HTTPS, confirm that Caddy can reach both upstreams.
WordPress:
docker compose \
--env-file examples/wordpress-telegram/.env \
-f docker/docker-compose.yml \
-f examples/wordpress-telegram/compose.override.yml \
exec caddy wget -qO- http://wordpress:80/ | head
Telegram webhook health endpoint:
docker compose \
--env-file examples/wordpress-telegram/.env \
-f docker/docker-compose.yml \
-f examples/wordpress-telegram/compose.override.yml \
exec caddy wget -qO- http://telegram-bot:8080/health
Expected: WordPress returns HTML and the bot returns
{"ok":true,"service":"telegram-webhook"}.
When these checks fail, the problem is inside the Compose stackānot public DNS.
7. Test HTTPS routing¶
Assume the proxy host is 192.168.1.20 and the domain is example.net.
WordPress:
Telegram health endpoint:
Open https://wordpress.example.net in a browser after normal DNS points the name
to the proxy host. Complete the WordPress installer and create an administrator
account with a strong password.
8. Test webhook secret validation locally¶
Load the secret from the example environment without printing it:
Send a small synthetic update:
curl --resolve telegram.example.net:443:192.168.1.20 \
-H "Content-Type: application/json" \
-H "X-Telegram-Bot-Api-Secret-Token: ${TELEGRAM_WEBHOOK_SECRET}" \
--data '{"update_id":1,"message":{"chat":{"id":12345},"text":"test"}}' \
https://telegram.example.net/webhook
Expected: {"ok":true} and a log entry containing only the update ID and chat
ID. The example receiver deliberately does not log message text or secrets.
Try the same request without the secret header:
curl --resolve telegram.example.net:443:192.168.1.20 \
-H "Content-Type: application/json" \
--data '{"update_id":2}' \
https://telegram.example.net/webhook
Expected: HTTP 403 with {"ok":false,"error":"invalid secret"}.
9. Connect a real Telegram bot¶
Create a bot using BotFather and place its token in:
Ensure normal public DNS resolves telegram.<DOMAIN> to the proxy and that the
certificate is valid from the public internet. Then run:
The script reads:
DOMAINfrom the root.env;TELEGRAM_BOT_TOKENfrom the example.env;TELEGRAM_WEBHOOK_SECRETfrom the example.env.
It sends those values directly to Telegram's webhook API and does not echo the bot token.
Check the receiver logs:
docker compose \
--env-file examples/wordpress-telegram/.env \
-f docker/docker-compose.yml \
-f examples/wordpress-telegram/compose.override.yml \
logs --follow telegram-bot
The example acknowledges updates but does not reply to messages. Its purpose is to demonstrate secure ingress and routing, not a complete bot framework.
10. Operate the stack¶
Useful commands:
./caddy-docker.sh status
./caddy-docker.sh config
./caddy-docker.sh data
./caddy-docker.sh check-updates
Application-specific logs:
docker compose \
--env-file examples/wordpress-telegram/.env \
-f docker/docker-compose.yml \
-f examples/wordpress-telegram/compose.override.yml \
logs --tail=100 wordpress telegram-bot wordpress-db
Back up at least these named volumes before upgrades:
wordpress-data;wordpress-db-data.
The exact Compose project prefix may be added to the actual volume names. Use
docker volume ls to identify them.
11. Stop or remove the example¶
Stop containers but keep data:
docker compose \
--env-file examples/wordpress-telegram/.env \
-f docker/docker-compose.yml \
-f examples/wordpress-telegram/compose.override.yml \
stop
Remove containers and networks but keep named volumes:
docker compose \
--env-file examples/wordpress-telegram/.env \
-f docker/docker-compose.yml \
-f examples/wordpress-telegram/compose.override.yml \
down
Remove the example data as well:
docker compose \
--env-file examples/wordpress-telegram/.env \
-f docker/docker-compose.yml \
-f examples/wordpress-telegram/compose.override.yml \
down --volumes
--volumes deletes WordPress and database data
Use it only for a disposable tutorial environment or after a verified backup.
Production follow-up¶
Before treating this example as production-ready:
- pin container images by digest;
- place WordPress and database backups on a tested schedule;
- protect the status dashboard with network policy or authentication;
- add monitoring independent of the browser dashboard;
- implement real Telegram update handling with replay, rate, and business-logic controls;
- review firewall rules for ports 53, 80, and 443;
- rotate all tutorial secrets.