Free tools Windows power users keep installed
One-click scans. No signup required.
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.
Need a server to try this on? Antihost offers Linux VPS plans with NVMe storage, full root access and a 14-day free trial.
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.
Quick wins for a faster PC:
Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →Clear out junk files and repair common Windows errorsFree Scan →Scan for outdated or missing drivers - takes under a minuteDriver Scan →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.
#1 Best Overall
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.
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 →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:
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.
Rank #2
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.
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.
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:
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:
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:
Recommended Free Tools
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:
- Expose a stable public HTTPS URL and terminate TLS at the proxy or at PTB.
- Route the webhook path to the bot container’s plain-HTTP listener on the private Compose network.
- 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.
Best Value
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.
Outdated 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 matchWindows 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 reinstallTest persistence before launch:
- Have the bot write a known state value under
/app/data. - Run
docker compose down; this removes containers but normally keeps named volumes. - Start again with
docker compose up -dand verify the value remains. - Never use
docker compose down -vin 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.
Do these 3 things before closing this tab:
1Clear out junk files and repair common Windows errors2Scan for outdated or missing drivers - takes under a minute3Repair Windows errors before they cause bigger problemsRolling 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.
Quick Recap
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
- Run
docker compose configand review the rendered service, secret, volume, and healthcheck. - Build from pinned Python and PTB versions.
- Start with
docker compose up --build -d, then inspectdocker compose psanddocker compose logs -f bot. - Send
/startfrom Telegram and confirm the expected reply. - For polling, verify that no webhook is configured. For webhooks, verify public HTTPS routing and the secret header.
- Recreate the container and confirm that required state remains in the named volume or external datastore.
- 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.




