Por qué contenerizar un bot
- Compilaciones reproducibles. La misma versión de Python y las mismas dependencias fijadas en todos lados.
- Hosting portátil. La imagen corre en un VPS, en plataformas que compilan desde un Dockerfile (Railway, Fly.io, Render) o en un servidor en casa.
- Reinicios gratis. La política de reinicio de Docker levanta el bot tras un fallo o un reinicio del servidor.
Si aún no decides dónde correrlo, mira PaaS vs VPS para bots y nuestro resumen desplegar un bot en Python.
Estructura del proyecto
mibot/
bot/
__init__.py
main.py
requirements.txt
Dockerfile
.dockerignore
compose.yaml
.env # nunca va al repositorio
Fija las dependencias en requirements.txt (por ejemplo python-telegram-bot==22.* o discord.py==2.*) para que una recompilación el mes próximo instale la misma versión mayor.
El Dockerfile
FROM python:3.12-slim
ENV PYTHONUNBUFFERED=1 \
PYTHONDONTWRITEBYTECODE=1 \
PIP_NO_CACHE_DIR=1
WORKDIR /app
# Primero las dependencias, para que cambiar el código no las reinstale
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY bot/ bot/
# Corre como usuario sin privilegios
RUN useradd --create-home --uid 10001 app
USER app
# Forma exec: Python es el PID 1 y recibe SIGTERM directamente
CMD ["python", "-m", "bot.main"]
Detalles que importan:
PYTHONUNBUFFERED=1hace queprinty los logs aparezcan al instante endocker logs. Sin él, la salida puede quedarse en un búfer y no ves nada cuando el bot falla.- Copiar
requirements.txtantes del código mantiene en caché la capa de dependencias. - Forma exec (
["python", ...]) en lugar de forma shell (python -m bot.main). En forma shell,/bin/shes el PID 1 y puede no reenviar señales, así quedocker stopespera el periodo de gracia y luego mata el bot. Mira apagado limpio para bots. - La imagen
-slimes una buena opción por defecto. Las imágenes Alpine usan musl, lo que puede hacer más lenta la instalación de algunos paquetes de Python o requerir compiladores.
.dockerignore
.env
.git
__pycache__/
*.pyc
.venv/
tests/
Excluir .env importa: sin eso, COPY . . metería tu token en una capa de la imagen, y cualquiera con la imagen podría extraerlo.
compose.yaml
services:
bot:
build: .
restart: unless-stopped
env_file: .env
stop_grace_period: 20s
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
restart: unless-stoppedreinicia el bot tras fallos y cuando arranca el daemon de Docker al encender el servidor, salvo que lo hayas detenido a mano.env_fileinyectaTELEGRAM_TOKEN,DISCORD_TOKENy demás en tiempo de ejecución. Valida el archivo con nuestro validador de .env.- El driver
json-filepor defecto crece sin límite si no lo acotas;max-sizeymax-fileevitan que un bot muy verboso llene el disco.
Arráncalo y sigue los logs:
docker compose up -d --build
docker compose logs -f bot
Bots con webhook: expón un puerto
Un bot que recibe webhooks (WhatsApp Cloud API, Telegram en modo webhook) debe escuchar en un puerto y estar detrás de HTTPS:
ports:
- "127.0.0.1:8080:8080"
healthcheck:
test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8080/health', timeout=3)"]
interval: 30s
timeout: 5s
retries: 3
Enlazar a 127.0.0.1 mantiene el puerto privado; pon un proxy inverso como Caddy o nginx delante para TLS. En un PaaS, lee el puerto de la variable de entorno PORT. Los bots de polling o gateway (Discord) no necesitan ningún puerto abierto.
En producción
- VPS: instala Docker Engine, copia el proyecto (o descarga la imagen de un registro), crea
.envconchmod 600y ejecutadocker compose up -d. Nuestro tutorial de VPS en Vultr cubre el endurecimiento del servidor. - PaaS: plataformas como Railway detectan el Dockerfile y lo compilan; define las variables en el panel en lugar de un
.env. Mira desplegar un bot en Railway. - Actualizaciones:
git pull && docker compose up -d --buildrecompila y reemplaza el contenedor. Corre una sola réplica de un bot de polling o gateway, o recibirá cada evento dos veces (Telegram responde 409 Conflict al segundo que hace polling).
Preguntas frecuentes
¿Necesito Docker para desplegar un bot en Python?
No. Un virtualenv más una unidad de systemd funciona bien en un VPS. Docker ayuda cuando quieres la misma configuración en varios servidores o en una plataforma que compila imágenes.
¿Por qué mi contenedor se reinicia en bucle?
El bot termina al arrancar, casi siempre porque falta una variable de entorno o el token es inválido. Lee docker compose logs bot; con PYTHONUNBUFFERED=1 el traceback aparece ahí.
¿Cómo conservo datos entre recompilaciones?
Usa un volumen con nombre para archivos como una base SQLite (volumes: ["botdata:/app/data"]), o una base de datos administrada. Lo que se escribe dentro del contenedor se pierde al recrearlo.
Fuentes
- Docker docs, referencia de Dockerfile (CMD, forma exec): docs.docker.com/reference/dockerfile
- Docker docs, políticas de reinicio: docs.docker.com/engine/containers/start-containers-automatically
- Docker docs, driver de logs JSON: docs.docker.com/engine/logging/drivers/json-file
- Docker docs, referencia de Compose: docs.docker.com/reference/compose-file/services
- Documentación de Python, PYTHONUNBUFFERED: docs.python.org/3/using/cmdline.html