DriversRecommendedOutdated drivers can make a good PC feel brokenScan driver issues before chasing fixes manually.Scan NowHalloween DealsAmazon USHalloween week deals: useful picks before month-endAmazon US: current deals, useful picks and tech finds.Check DealsSlow PC?RecommendedPC slow today? Run a repair scan before it gets worseResolve common Windows issues and optimize system performance.Scan Now×
Skip to content
World desk12 min

Python Telegram Bot with Docker Compose: A Complete Deployment Guide

Build and operate a Python Telegram bot with Docker Compose using pinned dependencies, mounted secrets, restart recovery, healthchecks, persistent state, and a clear polling-or-webhook decision.

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Use Docker Compose to package your Python Telegram bot, inject its token as a mounted secret, and run it as a restartable service. For most small bots, python-telegram-bot’s run_polling() is the simplest production path: the container makes outbound connections to Telegram, so you do not need a public inbound endpoint. Choose a webhook only when you have a specific need for Telegram to call a public HTTPS URL.

This guide builds a reproducible Compose deployment, then hardens it for reboots, updates, persistence, dependent services, and rollback. Commands assume Docker Engine and the Compose plugin are installed on the host.

As an Amazon Associate I earn from qualifying purchases.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

What Compose is responsible for

Docker describes Compose as a tool for defining and running multi-container applications. In this deployment, the Compose project is the application boundary: it defines the bot image, secret mount, volumes, healthcheck, restart policy, and any database or queue the bot depends on. The same project gives you consistent commands for starting, stopping, rebuilding, inspecting, and updating the service.

Component Purpose Typical choice for a small bot
Bot service Runs one Python process containing the Telegram handlers. A pinned Python image built from a Dockerfile.
Token secret Supplies the Telegram bot token without baking it into an image. A Compose secret mounted at /run/secrets/telegram_token.
State storage Preserves data that must survive container replacement. A named volume for small local state, or an external database for shared workloads.
Healthcheck Reports whether the container is in a usable local state. A process-and-configuration check; it should not be presented as proof that Telegram is reachable.
Restart policy Recovers from process failure and allows the bot to return after a host reboot. unless-stopped for a continuously running bot.

Keep the bot as a single process unless your design genuinely needs additional workers. Compose can add a database, queue, reverse proxy, or log collector later without changing the bot’s packaging boundary.

Polling or webhook: make this decision first

python-telegram-bot (PTB) documents the distinction clearly: polling uses Telegram’s getUpdates API, while a webhook is a URL that Telegram calls whenever an update arrives. They are alternative delivery modes for one bot; do not run both for the same token.

Axis Polling Webhook
Network shape The bot opens outbound connections to Telegram. Telegram sends updates to your public URL.
Setup effort Lowest; no public inbound endpoint is required. Requires a public URL, routing, TLS termination, and webhook registration.
PTB entry point Application.run_polling() Application.run_webhook() or Updater.start_webhook()
Good fit Small deployments, development, and private hosts. A public production endpoint when there is a clear reason not to poll.
Extra security control Protect the token and the host. Set a webhook secret_token and validate the X-Telegram-Bot-Api-Secret-Token header.

When polling is the right default

Polling avoids DNS, certificate, firewall, and reverse-proxy work. The bot can sit behind a NAT or private firewall because all traffic is outbound. PTB’s run_polling() initializes the application, starts polling, and performs an orderly shutdown when the process receives a termination signal, which fits Docker’s stop-and-replace lifecycle.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

When a webhook is justified

Use a webhook when your architecture already exposes a reliable HTTPS endpoint, when inbound delivery is a deliberate requirement, or when you need the operational controls of a reverse proxy in front of PTB. Telegram must be able to resolve and reach the URL. A proxy can terminate TLS and forward plain HTTP to the bot container; PTB documents webhook operation without a certificate and key when another application handles TLS.

PTB documents max_connections as an API parameter from 1 to 100, with a default of 40. That is a Telegram webhook setting, not a promise of bot capacity. The webhook secret token is constrained to 1–256 characters. Store it like any other credential and rotate it if it is exposed.

Switching modes safely

Before starting polling, ensure no webhook remains registered for the bot. A configured webhook prevents getUpdates polling from receiving updates. When switching to a webhook, register the public URL, route the selected path to the bot, and configure PTB with the same path and secret. Decide explicitly whether pending updates should be discarded during the switch; do not leave that behavior to an accidental redeploy.

Prepare the project and pin what you run

Create a directory with the application module, dependency file, Dockerfile, Compose files, and a private secret file. A practical layout is:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
telegram-bot/
├── app.py
├── requirements.txt
├── Dockerfile
├── compose.yaml
├── compose.production.yaml
└── secrets/
    └── telegram_token.txt

Put the token alone on one line in secrets/telegram_token.txt. Restrict that directory on the host and add it to .gitignore. Never commit the token, a .env file containing it, private keys, or an image layer that includes either.

Pin both the Python base-image tag and the PTB release you have tested. PTB APIs can change between major versions, so record the exact dependency version in requirements.txt rather than installing an unconstrained latest release during every build. Review upgrades deliberately and rebuild the image when you approve one.

Write a single-process bot entry point

The process should read the secret file at startup, construct one PTB application, register handlers, and enter the selected delivery mode. This example uses polling and fails immediately if the mounted token is missing or empty:

import os
from telegram import Update
from telegram.ext import ApplicationBuilder, CommandHandler, ContextTypes


def read_token() -> str:
    path = os.environ.get('TELEGRAM_TOKEN_FILE', '/run/secrets/telegram_token')
    with open(path, encoding='utf-8') as secret:
        value = secret.read().strip()
    if not value:
        raise RuntimeError('Telegram token is empty')
    return value


async def start(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
    await update.message.reply_text('Bot is running.')


async def help_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
    await update.message.reply_text('Use /start to verify the deployment.')


def main() -> None:
    application = ApplicationBuilder().token(read_token()).build()
    application.add_handler(CommandHandler('start', start))
    application.add_handler(CommandHandler('help', help_command))
    application.run_polling()


if __name__ == '__main__':
    main()

Do not print the token while debugging. If you need diagnostics, log the file path, application version, and handler milestones, never credential contents. Keep the entry point synchronous at the top level and let PTB manage its event loop and shutdown sequence.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Build a small, repeatable image

A Dockerfile should install only the declared dependencies, copy application code, and run the bot as the container command. The example uses a concrete slim Python tag; keep that tag fixed to the version you test rather than floating to latest.

FROM python:3.12-slim

ENV PYTHONDONTWRITEBYTECODE=1 
    PYTHONUNBUFFERED=1

WORKDIR /app

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY app.py .

RUN useradd --create-home --uid 10001 botuser 
    && chown -R botuser:botuser /app
USER botuser

CMD ['python', 'app.py']

The non-root user limits the impact of an application compromise, but check volume ownership before enabling a writable state directory. Keep the application filesystem read-only where your bot permits it; Python may still need a writable temporary directory, so test that setting rather than assuming every library is read-only compatible.

Define the Compose service and secret

This baseline Compose file builds the image, grants the token only to the bot service, mounts a named volume for optional local state, and asks Docker to restart the service after a failure or host reboot. The healthcheck verifies that PID 1 still exists and that the token file is present; it does not claim that Telegram’s API is reachable.

services:
  bot:
    build:
      context: .
    restart: unless-stopped
    secrets:
      - telegram_token
    environment:
      TELEGRAM_TOKEN_FILE: /run/secrets/telegram_token
    volumes:
      - bot_state:/app/data
    healthcheck:
      test:
        - CMD-SHELL
        - kill -0 1 && test -s /run/secrets/telegram_token
      interval: 30s
      timeout: 5s
      retries: 3
      start_period: 10s

secrets:
  telegram_token:
    file: ./secrets/telegram_token.txt

volumes:
  bot_state:

Compose mounts a declared secret at /run/secrets/<secret_name> and grants it per service. Docker warns that environment variables can be exposed through process inspection or logs, so a mounted secret is preferable for the token. The file is read at startup; changing the host secret file requires recreating the container to deliver the new value.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

If the bot does not persist anything locally, remove the volume rather than creating storage you will never use. If it writes files under /app/data, make sure the non-root user can write there, or initialize the volume with suitable ownership in a one-off setup step.

Start locally and inspect the result

Validate the rendered configuration before launching:

docker compose config

Build and start the service in the foreground on the first run so startup errors are visible:

docker compose up --build

After the bot answers /start, stop it with Ctrl+C and run it detached for normal operation:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
docker compose up --build -d
docker compose ps
docker compose logs -f bot

docker compose ps should show the container as running and, after the healthcheck begins passing, healthy. The logs should contain your application’s normal startup messages but never the token. Use a one-off command for non-daemon maintenance, for example:

docker compose run --rm bot python -c 'import sys; print(sys.version)'

That command creates a temporary container with the same image and Compose settings, then removes it. It is useful for checking the pinned interpreter and dependencies without altering the long-running service.

Add databases or queues without startup races

If the bot depends on another Compose service, a container being created is not the same as that service being ready. Define a meaningful healthcheck on the dependency and use the long-form dependency condition:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
services:
  bot:
    depends_on:
      database:
        condition: service_healthy

The dependency must provide its own healthcheck, and the check must test the operation the bot needs, such as accepting authenticated connections. Do not use a fixed sleep as a substitute; it makes startup dependent on timing and hides real failures. Keep retry behavior in the bot as well, because a healthy dependency can still be restarted later.

Use a production override instead of editing the development file

Docker’s production guidance recommends a separate Compose file. Development conveniences such as source bind mounts, verbose logs, and exposed debugging ports should not silently become production behavior. A minimal override can set production restart and logging policy:

services:
  bot:
    restart: unless-stopped
    logging:
      driver: json-file
      options:
        max-size: 10m
        max-file: 5

Keep the production file free of source-code bind mounts so the running image is the artifact you built and reviewed. Add only the ports required by your chosen mode: polling normally needs no inbound port, while a webhook needs the internal listener reachable from the reverse proxy. Configure resource limits and an external log destination according to your host’s operations policy; Compose itself does not establish universal CPU, memory, uptime, or throughput guarantees.

Launch with both files, in order, so the second file overrides the first:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
docker compose -f compose.yaml -f compose.production.yaml up -d --build

Deploy a webhook behind a reverse proxy

A webhook deployment has three separate responsibilities:

  1. Expose a stable public HTTPS URL and terminate TLS at the proxy or at PTB.
  2. Route the webhook path to the bot container’s plain-HTTP listener on the private Compose network.
  3. Register that URL with Telegram and configure PTB with the same path, a secret token, and any chosen connection limit.

PTB’s optional webhook support requires the package extras documented for your installed PTB version. Install those extras in the pinned dependency file, then use Application.run_webhook() or the lower-level Updater.start_webhook(). If TLS is terminated by a separate proxy, PTB can listen without loading certificate and key files. Configure the proxy to pass the X-Telegram-Bot-Api-Secret-Token header unchanged and have PTB validate it; a URL that is merely hard to guess is not a substitute for this check.

Keep the webhook listener bound to the private network unless the host firewall and proxy design require otherwise. Test a complete request through the public URL, inspect the proxy access log and bot log, and then send a real Telegram message. Do not infer webhook health from a successful container healthcheck, because the local check cannot test DNS, TLS, routing, or Telegram delivery.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Persistence: what survives replacement

Files written only to a container’s writable layer disappear when that container is removed. Docker’s quickstart documentation demonstrates this behavior when containers and their data are taken down. A named volume, such as bot_state above, lives outside the container layer and is mounted again when Compose recreates the service.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Test persistence before launch:

  1. Have the bot write a known state value under /app/data.
  2. Run docker compose down; this removes containers but normally keeps named volumes.
  3. Start again with docker compose up -d and verify the value remains.
  4. Never use docker compose down -v in this test unless you intentionally want to delete the named volume.

Use an external database when state is larger, must be queried by multiple replicas, or needs independent backup and failover. A local named volume is not a substitute for a database backup strategy.

Secrets and host security checklist

  • Keep the Telegram token in a secret file or another dedicated secret manager; do not commit it, place it in an image layer, or echo it in a diagnostic command.
  • Grant the Compose secret only to the bot service. Other services do not need the token.
  • Use a non-root container user where the image and volume permissions allow it.
  • Protect the host, Docker socket, and secret directory with operating-system permissions.
  • For webhooks, use HTTPS, validate the Telegram secret header, and restrict the listener to the proxy network.
  • Bound local log files and configure an external log destination when the bot runs continuously.
  • Choose an explicit shutdown timeout and test that the bot handles SIGTERM without corrupting state.

Reboots, updates, and rollback

Keeping the bot running after a reboot

Set the restart policy in the Compose service and ensure the Docker daemon itself starts at host boot. unless-stopped restarts the bot after a process failure and after a daemon or host restart, while respecting an operator’s intentional stop. Choose a different policy only when you have a documented reason.

Deploying changed code

Build the changed service, then recreate only that service:

docker compose build bot
docker compose up --no-deps -d bot
docker compose ps
docker compose logs -f bot

The --no-deps flag avoids unnecessarily restarting healthy dependencies. If you changed the Compose definition, use the full production command instead so the new configuration is rendered and applied.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Rolling back

Tag known-good images in your registry or retain the previous build context. A rollback means selecting that known-good image or code revision, rebuilding if necessary, and running the same controlled recreation command. Verify the healthcheck, send a test message, and inspect state after the rollback. Do not delete volumes as part of a code rollback; application code and persistent data have different lifecycles.

Troubleshoot by symptom

Symptom Likely cause Action
Container exits immediately Missing token, import error, or an exception during application construction. Run docker compose logs bot; confirm the secret file is mounted and the pinned dependencies installed.
Container is running but unhealthy PID 1 is gone, the token file is empty, or the healthcheck command does not match the image. Run the healthcheck command interactively with docker compose exec bot and correct the local condition.
Polling receives nothing A webhook is still configured for the same bot, or outbound connectivity is blocked. Remove the old webhook deliberately, confirm outbound DNS and HTTPS access, then restart polling.
Webhook receives no updates DNS, TLS, proxy routing, path, or secret-header validation is wrong. Test the public HTTPS route through the proxy and compare its path and header with the PTB configuration.
State disappears after redeploy State was written inside the container layer or a volume was removed. Mount a named volume or external datastore and avoid destructive volume removal commands.
Bot starts before its database The dependency has no readiness healthcheck or uses short-form depends_on. Add a dependency healthcheck and condition: service_healthy, then retain application-level retries.
Token rotation has no effect The old secret is still mounted in the existing container. Replace the host secret file and recreate the bot container; do not print the new token while verifying.

Final launch checklist

  1. Run docker compose config and review the rendered service, secret, volume, and healthcheck.
  2. Build from pinned Python and PTB versions.
  3. Start with docker compose up --build -d, then inspect docker compose ps and docker compose logs -f bot.
  4. Send /start from Telegram and confirm the expected reply.
  5. For polling, verify that no webhook is configured. For webhooks, verify public HTTPS routing and the secret header.
  6. Recreate the container and confirm that required state remains in the named volume or external datastore.
  7. Test a deliberate stop, an automatic restart, and a host reboot procedure before calling the deployment production-ready.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

Leave a Reply

Your email address will not be published. Required fields are marked *

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

More from the Wire

  1. World desk4 min
    How to Spot an AI Voice Scam Before Sending MoneyDon’t rely on how a caller sounds. Pause, call back through a known number, and verify the emergency with another trusted person before sending money.
  2. Mountain View desk4 min
    Google’s SynthID Detector: How to Check AI-Generated Images, Video and AudioGoogle’s SynthID Detector looks for an embedded watermark in supported images, video and audio. Here is what its results do—and do not—show.
  3. Redmond desk20 min
    How to create a link to File or Folder in Windows 11Windows 11 gives you several ways to point to a file or folder without moving or duplicating it. You can create a desktop shortcut,…
Recommended PC Tool
Recommended PC Tool
Crashes, No Sound, or Screen Glitches?Free driver scan
Windows Errors? Fix Them Before They SpreadFree repair scan

Two free Windows tools

One Free Minute Could Fix That PC

Before you go - each of these free tools takes about a minute and tackles what quietly slows a Windows PC down.

Special offer. View Outbyte info, uninstall instructions, EULA, and Privacy Policy.