Skip to content

Call Baxter deployment with Caddy and Docker Compose

Scope

The infra/ layout is the minimal Call Baxter deployment:

  • call-baxter runtime;
  • tg-agent-cli projection support;
  • Caddy HTTPS ingress;
  • no bundled WordPress plugin, MariaDB, or WordPress service.

Use the full docker/ layout and Deploy → Telegram to WordPress with Docker for the complete bridge example.

Goal

Keep Call Baxter off the public TCP surface and expose only a narrow Telegram webhook path through Caddy.

Runtime layout

Telegram
  -> https://PUBLIC_DOMAIN${TELEGRAM_WEBHOOK_PATH}
  -> Caddy
  -> unix:///srv/run/cb.sock
  -> Call Baxter daemon
  -> Telegram plugin persistence + event emission
  -> tg-agent-cli projection database at /srv/data/tg.db

Bound host paths

infra/run/cb.sock
infra/data/cb.db
infra/data/tg.db
rules.d/
infra/caddy_data/
infra/caddy_config/

Why this split

  • Telegram webhook delivery requires a publicly reachable HTTPS endpoint.
  • Caddy terminates TLS and reverse-proxies only the configured webhook and health paths.
  • Call Baxter listens on a shared Unix socket rather than a public host port.
  • The proxy checks X-Telegram-Bot-Api-Secret-Token before forwarding a webhook payload.
  • Polling and webhook ingestion both normalize through the same Telegram plugin event path.

Configure the minimal stack

cd infra
cp .env.example .env

Edit .env and set:

TGCLI_BOT_TOKEN=123456:replace-me
PUBLIC_DOMAIN=bot.example.org
ACME_EMAIL=ops@example.org
TELEGRAM_WEBHOOK_PATH=/webhooks/telegram/main
TELEGRAM_WEBHOOK_SECRET=replace-with-long-random-token
TELEGRAM_POLL_ENABLED=false

Prepare writable bind mounts:

mkdir -p run data caddy_data caddy_config
sudo chown -R 10001:10001 run data caddy_data caddy_config

Validate and start:

docker compose config --quiet
docker compose up -d --build
docker compose ps
docker compose logs --tail=150 call-baxter caddy

Register the Telegram webhook

Load the env file:

set -a
. ./.env
set +a

Register the public URL:

curl --fail-with-body --silent --show-error \
  --request POST \
  "https://api.telegram.org/bot${TGCLI_BOT_TOKEN}/setWebhook" \
  --data-urlencode "url=https://${PUBLIC_DOMAIN}${TELEGRAM_WEBHOOK_PATH}" \
  --data-urlencode "secret_token=${TELEGRAM_WEBHOOK_SECRET}"

Inspect the result:

curl --fail-with-body --silent --show-error \
  "https://api.telegram.org/bot${TGCLI_BOT_TOKEN}/getWebhookInfo"

Operational checks

curl --fail --silent --show-error "https://${PUBLIC_DOMAIN}/healthz"

docker compose exec call-baxter \
  cb --config-file /srv/config/call-baxter.yml \
  --daemon-url http+unix:///srv/run/cb.sock \
  health

docker compose exec call-baxter \
  tg --database-url /srv/data/tg.db messages list

Upstream references