BotNetwork

Desplegar un bot en Python con Docker y Docker Compose

Una imagen de Docker convierte "en mi máquina funciona" en "funciona en cualquier máquina con Docker". Para un bot bastan una docena de líneas de configuración.

Publicado 4 min de lectura

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=1 hace que print y los logs aparezcan al instante en docker logs. Sin él, la salida puede quedarse en un búfer y no ves nada cuando el bot falla.
  • Copiar requirements.txt antes 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/sh es el PID 1 y puede no reenviar señales, así que docker stop espera el periodo de gracia y luego mata el bot. Mira apagado limpio para bots.
  • La imagen -slim es 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-stopped reinicia el bot tras fallos y cuando arranca el daemon de Docker al encender el servidor, salvo que lo hayas detenido a mano.
  • env_file inyecta TELEGRAM_TOKEN, DISCORD_TOKEN y demás en tiempo de ejecución. Valida el archivo con nuestro validador de .env.
  • El driver json-file por defecto crece sin límite si no lo acotas; max-size y max-file evitan 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 .env con chmod 600 y ejecuta docker 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 --build recompila 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