Quick wins for a faster PC:
Repair Windows errors before they cause bigger problemsFix Now →Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →iTechGuides is reader-supported. When you buy through links on our site, we may earn an affiliate commission. As an Amazon Associate I earn from qualifying purchases. Learn more
Need a server to try this on? Antihost offers Linux VPS plans with NVMe storage, full root access and a 14-day free trial.
Use Docker Compose to package the bot, its secret, and any supporting services into one repeatable application boundary. For most small bots, python-telegram-bot (PTB) 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 reason to expose an HTTPS endpoint. The deployment below covers both modes, keeps the token out of images and logs, survives host reboots, preserves state, and gives you commands for updates, health checks, and rollback.
What you will deploy
The example is intentionally small: one Python process in a bot container, a Compose-managed secret containing the Telegram token, and an optional named volume for state. Compose defines and runs the application as a unit, so the same commands handle building, starting, stopping, inspecting, and replacing the service. Add a database or queue as another service only when the bot actually needs one.
- Image: a pinned Python base image with a pinned PTB dependency.
- Process: one entry point that constructs an
Application, registers handlers, and callsrun_polling()or the webhook equivalent. - Secret: a Compose secret mounted at
/run/secrets/telegram_token, readable only by the bot service. - Recovery: a deliberate restart policy and a health check that describes what it can and cannot prove.
- Durability: a named volume or external datastore for data that must survive container replacement.
Docker’s documentation describes Compose as a tool for defining and running multi-container applications. Treat the Compose project, rather than an individual container, as your deployment boundary: configuration, secrets, dependencies, logs, and lifecycle commands belong there.
Prerequisites and project layout
Install Docker Engine with the Compose v2 plugin on the host where the bot will run. Verify both components before creating files:
#1 Best Overall
docker version
docker compose version
Create a directory such as telegram-bot/. Keep the token file outside version control and use this layout:
telegram-bot/
├── bot.py
├── requirements.txt
├── Dockerfile
├── compose.yaml
├── compose.production.yaml
├── .dockerignore
└── secrets/
└── telegram_token
Put the token issued by BotFather in secrets/telegram_token with no surrounding quotes and a trailing newline. Restrict the file on a Linux host with chmod 600 secrets/telegram_token. Add both secrets/ and any local .env file to .gitignore; never commit either one.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
Build a single-process PTB application
Read the token from the mounted secret
Do not make the token a Dockerfile ARG, bake it into an image layer, or pass it as a normal environment variable. The application reads the file path supplied by Compose:
import os
from pathlib import Path
def read_token() -> str:
secret_path = Path(os.environ.get('BOT_TOKEN_FILE', '/run/secrets/telegram_token'))
try:
token = secret_path.read_text(encoding='utf-8').strip()
except FileNotFoundError as exc:
raise RuntimeError(f'Missing Telegram token at {secret_path}') from exc
if not token:
raise RuntimeError('Telegram token is empty')
return token
Compose secrets are mounted under /run/secrets/<secret_name> and are granted per service. Docker warns that ordinary environment variables can be exposed through process inspection or logs, so a mounted secret is the safer default.
Register handlers and choose polling
This complete bot.py responds to /start, /help, and ordinary text. PTB’s ApplicationBuilder creates the application; run_polling() initializes it, starts the getUpdates loop, and performs an orderly shutdown when the process receives a termination signal.
import logging
from telegram import Update
from telegram.ext import (
ApplicationBuilder,
CommandHandler,
ContextTypes,
MessageHandler,
filters,
)
from token_reader import read_token
logging.basicConfig(
format='%(asctime)s %(levelname)s %(name)s %(message)s',
level=logging.INFO,
)
logger = logging.getLogger(__name__)
async def start(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
if update.effective_message:
await update.effective_message.reply_text('Bot is running. Send a message to echo it.')
async def help_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
if update.effective_message:
await update.effective_message.reply_text('Use /start or send plain text.')
async def echo(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
if update.effective_message and update.effective_message.text:
await update.effective_message.reply_text(update.effective_message.text)
def main() -> None:
application = (
ApplicationBuilder()
.token(read_token())
.build()
)
application.add_handler(CommandHandler('start', start))
application.add_handler(CommandHandler('help', help_command))
application.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, echo))
application.run_polling()
if __name__ == '__main__':
main()
Save the token reader in token_reader.py beside bot.py, or put the function directly in the same module. Keep one foreground process in the container. A process manager inside the container is unnecessary here; Compose supplies the lifecycle and restart policy.
Pin dependencies deliberately
PTB APIs can change between major versions. Resolve and test one version, then commit the generated, exact dependency file. The following illustrates the required shape without claiming that a particular PTB release is current:
Rank #2
python-telegram-bot[webhooks]==<tested-version>
If you will never use webhooks, omit the optional [webhooks] extra. For a polling-only image, python-telegram-bot==<tested-version> is sufficient. Replace the version marker with the exact release you tested, and pin the Python patch version in the image or its digest as well.
Containerize the bot
Dockerfile
This image runs as a non-root user, avoids writing Python bytecode, and starts the bot in the foreground:
FROM python:3.12-slim
ENV PYTHONDONTWRITEBYTECODE=1
PYTHONUNBUFFERED=1
BOT_TOKEN_FILE=/run/secrets/telegram_token
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir --require-hashes -r requirements.txt
COPY token_reader.py bot.py ./
RUN addgroup --system appgroup &&
adduser --system --ingroup appgroup appuser
USER appuser
CMD ['python', 'bot.py']
--require-hashes is appropriate only when your requirements file contains hashes for every resolved package. If you have not generated a hash-locked file, remove that option while you establish and test the lock file; do not pretend that an un-hashed list is fully reproducible. In production, replace the floating python:3.12-slim tag with the exact patch tag or digest approved by your update process.
.dockerignore
Prevent local secrets, caches, and development files from entering the build context:
.git
.gitignore
.env
secrets/
__pycache__/
*.pyc
.venv/
compose.production.yaml
The secret is mounted at runtime by Compose, so excluding it from the build context is an additional safeguard rather than the only one.
Define the Compose application
Base compose.yaml
The following file declares the secret, restart behavior, a process-level health check, and an optional state volume. The check verifies that PID 1 is alive; it does not prove that Telegram is reachable or that handlers are functioning.
services:
bot:
build:
context: .
secrets:
- telegram_token
environment:
BOT_TOKEN_FILE: /run/secrets/telegram_token
STATE_DIR: /var/lib/telegram-bot
restart: unless-stopped
init: true
read_only: true
tmpfs:
- /tmp
volumes:
- bot_state:/var/lib/telegram-bot
healthcheck:
test: ['CMD-SHELL', 'kill -0 1']
interval: 30s
timeout: 5s
retries: 3
start_period: 20s
secrets:
telegram_token:
file: ./secrets/telegram_token
volumes:
bot_state:
read_only: true protects the image filesystem. Give the application a writable named volume only for files it intentionally persists; in this example that location is /var/lib/telegram-bot. If the bot is entirely stateless, remove the volume and the STATE_DIR variable.
Add a database or queue only when needed
When the bot depends on another service, define a health check for that dependency and gate startup with depends_on. A process that merely starts first is not necessarily ready to accept connections:
services:
bot:
depends_on:
database:
condition: service_healthy
database:
image: postgres:16
environment:
POSTGRES_USER: bot
POSTGRES_PASSWORD_FILE: /run/secrets/database_password
POSTGRES_DB: bot
secrets:
- database_password
volumes:
- database_data:/var/lib/postgresql/data
healthcheck:
test: ['CMD-SHELL', 'pg_isready -U bot -d bot']
interval: 10s
timeout: 5s
retries: 5
secrets:
database_password:
file: ./secrets/database_password
volumes:
database_data:
The bot should still handle a temporary database outage with retries and clear errors. service_healthy prevents the initial race; it is not a substitute for runtime reconnection logic.
Start and verify the deployment
Build and launch locally
- From the project directory, build the image and start the service in the foreground:
docker compose up --build. - In another terminal, watch only the bot logs with
docker compose logs -f bot. - Inspect state with
docker compose ps. Wait for the health status to becomehealthyif the image declares the check above. - Send
/startto the bot in Telegram and confirm a response. - Stop with
Ctrl-Cand repeat in detached mode when the behavior is understood:docker compose up -d.
Use docker compose config to render and validate the merged configuration before deployment. It is useful for catching indentation errors, missing secret files, and an unexpected override. Do not paste rendered output into a public issue if it contains environment values or paths you consider sensitive.
Understand polling conflicts
Polling calls Telegram’s getUpdates API over an outbound connection. It needs no public inbound port, certificate, or domain, which is why it is the default choice for a small bot or a private host. Telegram will not deliver updates by polling while a webhook remains configured for the same bot. Before switching to polling, remove the old webhook and decide whether pending updates should be retained or discarded. Run only one active consumer for a bot token unless you have intentionally designed a different update-delivery arrangement.
The Tool Desk
Outbyte Driver Updater FREEFix the driver behind crashes, sound loss and screen glitchesFind Drivers →Outbyte PC Repair FREERepair Windows errors before they cause bigger problemsFix Now →Polling or webhook?
| Consideration | Polling | Webhook |
|---|---|---|
| Network shape | The bot opens outbound connections to Telegram with getUpdates. |
Telegram sends updates to a public URL that you operate. |
| Setup effort | Lowest; no public inbound endpoint is required. | Requires URL registration, routing, TLS termination, and a reachable host. |
| PTB entry point | Application.run_polling() |
Application.run_webhook() or Updater.start_webhook() |
| Typical fit | Small deployments, development, and private servers. | A public production endpoint when avoiding polling has a clear operational reason. |
| Main security control | Protect the token and host; no inbound bot listener is needed. | Use a webhook secret token and validate the X-Telegram-Bot-Api-Secret-Token header. |
PTB’s webhook guidance describes polling as repeatedly using getUpdates and a webhook as Telegram calling a URL when an update arrives. It advises switching only when there is a good reason. A webhook does not automatically improve reliability; it changes your network and certificate responsibilities.
Deploying with a webhook
Put TLS at a reverse proxy
The bot can listen on plain HTTP inside the Compose network while a reverse proxy terminates HTTPS. This keeps certificate handling at the edge and limits the bot process to the internal network. PTB documents webhook operation without supplying a certificate and key to the bot when another application performs TLS termination.
- Obtain a DNS name that resolves to the host and an HTTPS certificate.
- Route one private path from the proxy to the bot’s internal HTTP listener.
- Allow inbound traffic only to the proxy’s HTTPS port; do not expose the internal listener directly unless necessary.
- Set a random PTB webhook
secret_token, 1–256 characters, and configure the proxy or application to validate the matchingX-Telegram-Bot-Api-Secret-Tokenheader. - Register the complete HTTPS webhook URL with Telegram and confirm that the proxy forwards the request path and body unchanged.
PTB’s optional webhook support is supplied by the webhooks extra. Add that extra to the pinned requirements file, then replace run_polling() with the webhook startup path appropriate to your PTB version. The Bot API documents max_connections as 1–100, with a default of 40; this is a connection-setting constraint, not a promise of a particular throughput.
Compose shape for the webhook listener
services:
bot:
command: ['python', 'bot_webhook.py']
expose:
- '8080'
environment:
WEBHOOK_LISTEN: 0.0.0.0
WEBHOOK_PORT: 8080
WEBHOOK_PATH: /telegram/update
WEBHOOK_SECRET_FILE: /run/secrets/webhook_secret
secrets:
- telegram_token
- webhook_secret
reverse-proxy:
image: your-approved-proxy-image
depends_on:
- bot
ports:
- '443:443'
volumes:
- ./proxy-config:/etc/proxy:ro
- ./certificates:/etc/certificates:ro
secrets:
webhook_secret:
file: ./secrets/webhook_secret
The proxy image, configuration syntax, certificate renewal, and firewall rules depend on the proxy you select, so keep those details in its own documented configuration. The important Compose properties are that the bot is reachable on the private network, only the proxy publishes HTTPS, and both token types are mounted as secrets.
Use a production override
Keep developer conveniences separate from production settings. Docker recommends a production-specific Compose file that removes source-code bind mounts, applies production ports and logging, and sets restart behavior. The base file above has no source mount; if you add one for development, remove it in the override.
services:
bot:
restart: unless-stopped
build:
context: .
logging:
driver: json-file
options:
max-size: 10m
max-file: '5'
deploy:
resources:
limits:
memory: 512M
# Publish only the ports needed by your chosen edge service.
# Do not publish a polling bot because it has no inbound listener.
The deploy.resources values are examples of explicit limits, not universal sizing recommendations. Choose CPU and memory from your bot’s workload, message volume, library behavior, and observed headroom. The Docker Engine may treat some Swarm-oriented deploy keys differently outside Swarm, so verify the effective configuration on your target host.
Start with both files so the production settings are merged:
Free tools Windows power users keep installed
One-click scans. No signup required.
docker compose -f compose.yaml -f compose.production.yaml up -d --build
Use docker compose -f compose.yaml -f compose.production.yaml config to inspect the merged result. Keep source code, Compose files, and the exact dependency lock file in version control; keep secrets on the host or in a separate secret-management system.
Updates, rollback, and reboot recovery
Update changed code
- Pull or check out the reviewed code and dependency changes.
- Build only the changed service:
docker compose build bot. - Recreate it without needlessly restarting dependencies:
docker compose up --no-deps -d bot. - Watch startup and handler errors with
docker compose logs -f bot, then exercise a real command in Telegram.
Compose replaces the container from the new image while preserving named volumes. If a dependency schema changed, apply migrations as an explicit, reviewed one-off command rather than hiding migration logic in an endless restart loop.
Roll back safely
Tag known-good images in your registry or retain the previous Git revision and lock file. To roll back, check out that revision, rebuild the bot, and run the same up --no-deps -d bot command. If the new container fails before it becomes healthy, inspect docker compose ps and docker compose logs bot before removing anything. Keep the named volume intact unless you have confirmed that a data migration must be reversed; deleting it can destroy state.
Best Value
Survive a host reboot
restart: unless-stopped tells Docker to restart the bot after a process failure and after the Docker daemon or host returns, unless an operator intentionally stopped the service. Other policies such as always or on-failure may be appropriate for a different lifecycle, but choose one deliberately and test it. Enable Docker at host boot, then verify the actual behavior by restarting the Docker service during a maintenance window and checking docker compose ps afterward.
Persist state outside the container layer
Files written only to a container’s writable layer disappear when that container is removed. Docker’s quickstart documentation specifically warns that docker compose down removes containers and their writable-layer data. A named volume survives ordinary container replacement:
docker volume ls
docker compose down
docker compose up -d
After recreation, verify that expected files remain under /var/lib/telegram-bot. Do not use docker compose down -v on a project whose volumes contain data you need; the -v option removes named volumes. For larger, shared, or highly available workloads, put state in an external database rather than relying on a local volume tied to one host.
Operational checks that catch real failures
- Configuration: run
docker compose configand confirm the intended files, service names, secret mounts, and restart policy. - Secret presence: verify the token file exists on the host and is readable by Docker, but never print its contents. A missing or empty file should make the process fail clearly.
- Process health: use
docker compose psand the health status. The sample check proves only that PID 1 exists; add an application-local heartbeat if you need to detect a wedged event loop. - Logs: follow
docker compose logs -f bot, configure bounded retention, and forward logs to an external aggregator when continuous operation requires centralized search or alerting. - Shutdown: run
docker compose stopand confirm PTB exits cleanly on SIGTERM. A graceful stop prevents partial writes and makes restart behavior predictable. - Persistence: recreate the container deliberately and verify the volume or external database still contains a known test record.
- Network mode: for polling, outbound HTTPS is sufficient; for webhooks, test DNS, certificate validity, proxy routing, firewall rules, and Telegram’s ability to reach the public URL.
Troubleshooting by symptom
The container exits immediately
Read the first traceback with docker compose logs bot. A missing secret, an empty token, an import error, or a malformed requirements lock file is more likely than a Docker networking problem. Confirm the mounted path inside a temporary diagnostic container without printing the token, and verify that the command points to the module copied into the image.
The bot starts but receives no messages in polling mode
Check that another process is not consuming the same token and that a webhook is not still registered. Confirm outbound DNS and HTTPS from the host or container. Do not expose a port merely to fix polling; polling does not require inbound traffic. If you changed from webhook to polling, remove the webhook according to your Bot API procedure and decide what to do with queued updates.
Recommended Free Tools
Webhook requests return errors
Work from the outside in: resolve the hostname, inspect the certificate chain, check the proxy access log, verify the forwarded path and HTTP method, then inspect the bot log. A wrong secret header should be rejected; a missing header indicates a proxy or registration configuration problem. Ensure the internal bot port is reachable on the Compose network but not accidentally published to the internet.
The bot loses data after an update
Determine whether the data lived in the container layer, a named volume, or an external database. If it was in the layer, it was never durable. If it was in a named volume, check that the new Compose project name and volume declaration refer to the same volume and that no one ran down -v. For database migrations, restore from a tested backup rather than deleting the volume.
The container is healthy but the bot is stuck
The sample kill -0 1 check cannot detect a blocked event loop or failed Telegram requests. Add a local heartbeat file or a private health endpoint updated by the application, and make the check test that signal’s freshness. Keep that check local unless it genuinely measures the dependency you claim it measures; do not label a process check as proof of Telegram reachability.
Security and maintenance checklist
- Never commit the Telegram token,
.envfiles, private keys, or certificate material. - Prefer Compose secrets and grant each secret only to the service that needs it.
- Run as a non-root user and keep the application filesystem read-only where the framework permits.
- Pin the Python image and PTB version you tested; review updates in a controlled rebuild.
- Keep logs bounded and avoid logging authorization headers, tokens, or full update payloads that contain personal data.
- Use
depends_on: condition: service_healthyfor startup ordering, while retaining runtime retry logic. - Use a reverse proxy for webhook TLS when the bot should listen on plain HTTP internally.
- Test SIGTERM shutdown, container replacement, reboot recovery, volume persistence, and rollback before launch.
This design gives a small bot a reproducible Compose lifecycle without pretending that one health check, restart policy, or volume solves every operational problem. Polling is the low-friction default; a webhook is a deliberate network architecture with TLS, routing, and header-validation work. Whichever mode you choose, keep the token out of images, make state durable, and verify the exact behavior you rely on.
Windows Errors? Fix Them Before They Spread
Repair common Windows errors and clear accumulated junk for a smoother, more stable PC - no reinstall needed.Free scan · no reinstallOutdated Drivers Are Slowing You Down
One free scan finds every outdated or missing driver and matches the right update for your exact hardware.Free scan · exact hardware matchQuick Recap
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.

