DevOps & Scaling

Docker + CaptchaAI : résolution de CAPTCHA conteneurisée

Conteneuriser votre solveur CaptchaAI, c'est exécuter exactement le même code sur votre poste, en intégration continue et en production — sans le classique « ça marche chez moi ». Une fois l'image construite, vous en tirez concrètement trois choses :

  • Reproductibilité : la même image, les mêmes dépendances, du laptop au serveur.
  • Mise à l'échelle horizontale : quatre workers derrière une file d'attente, puis huit un jour de charge, puis retour à quatre la nuit.
  • Déploiements propres : aucun résidu sur l'hôte, un rollback aussi simple que redéployer l'image précédente.

Ce guide part d'un Dockerfile minimal et monte progressivement jusqu'à une configuration Docker Compose multi-workers avec Redis, prête à déployer sur OVHcloud, Scaleway ou une région AWS eu-west-3 (Paris).


Un Dockerfile minimal pour votre solveur

Commencez petit. L'image de base python:3.11-slim suffit : elle est légère, à jour et évite d'embarquer une distribution complète. Installez les dépendances en premier pour tirer parti du cache de couches Docker, puis copiez le script. Point critique : la clé API n'est jamais inscrite en dur dans l'image. Elle est déclarée vide et injectée au démarrage du conteneur, ce qui évite de la retrouver dans l'historique du registre ou dans une couche partagée.

FROM python:3.11-slim

WORKDIR /app

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

COPY solver.py .

# API key passed at runtime, not baked into image
ENV CAPTCHAAI_KEY=""

CMD ["python", "solver.py"]

Le fichier requirements.txt reste volontairement minimal — une seule dépendance pour parler à l'API :

requests>=2.31.0

Le script de résolution reCAPTCHA v2

Le cœur du conteneur est un script qui envoie le défi à l'API CaptchaAI, puis interroge le résultat jusqu'à obtenir le token. La logique est la même que pour n'importe quelle intégration reCAPTCHA v2 : un POST sur in.php avec la méthode userrecaptcha, puis une boucle de polling sur res.php toutes les 5 secondes tant que la réponse vaut CAPCHA_NOT_READY. La clé, le sitekey et l'URL de la page sont lus depuis l'environnement, jamais codés en dur.

# solver.py
import os
import sys
import requests
import time


def solve_recaptcha(api_key, site_key, page_url):
    """Solve reCAPTCHA v2 using CaptchaAI."""
    resp = requests.post("https://ocr.captchaai.com/in.php", data={
        "key": api_key,
        "method": "userrecaptcha",
        "googlekey": site_key,
        "pageurl": page_url,
        "json": 1,
    }, timeout=30)
    result = resp.json()

    if result.get("status") != 1:
        raise RuntimeError(f"Submit error: {result.get('request')}")

    task_id = result["request"]

    # Poll for result
    for _ in range(24):  # 120s max
        time.sleep(5)
        resp = requests.get("https://ocr.captchaai.com/res.php", params={
            "key": api_key,
            "action": "get",
            "id": task_id,
            "json": 1,
        }, timeout=15)
        data = resp.json()
        if data["request"] != "CAPCHA_NOT_READY":
            if data.get("status") == 1:
                return data["request"]
            raise RuntimeError(f"Solve error: {data['request']}")

    raise TimeoutError("Solve timeout")


if __name__ == "__main__":
    api_key = os.environ.get("CAPTCHAAI_KEY")
    if not api_key:
        print("Error: CAPTCHAAI_KEY environment variable required")
        sys.exit(1)

    site_key = os.environ.get("SITE_KEY", "6Le-wvkSAAAAAPBMRTvw0Q4Muexq9bi0DJwx_mJ-")
    page_url = os.environ.get("PAGE_URL", "https://example.com")

    token = solve_recaptcha(api_key, site_key, page_url)
    print(f"Token: {token[:50]}...")

Le même squelette s'adapte aux autres types pris en charge : il suffit de changer la méthode et les paramètres pour Cloudflare Turnstile (turnstile) ou GeeTest v3 (geetest).


Construire l'image et lancer le conteneur

Deux commandes suffisent : une pour construire, une pour exécuter. La clé API et les valeurs cibles arrivent via des drapeaux -e, si bien que la même image sert pour tous vos environnements. L'option --rm supprime le conteneur à la fin — parfait pour un solveur à usage unique lancé depuis un script.

# Build
docker build -t captchaai-solver .

# Run with API key from environment
docker run --rm \
  -e CAPTCHAAI_KEY="YOUR_API_KEY" \
  -e SITE_KEY="TARGET_SITE_KEY" \
  -e PAGE_URL="https://example.com" \
  captchaai-solver

Build multi-étapes pour la production

En production, réduisez la surface d'attaque et le poids de l'image. Deux réflexes structurent cette étape :

  • Build multi-étapes : les dépendances sont installées dans une étape intermédiaire, et seule la couche utile est copiée dans l'image finale.
  • Processus non-root : un utilisateur solver dédié limite l'impact d'une éventuelle compromission — attendu par la plupart des équipes sécurité, et cohérent avec une démarche de minimisation RGPD quand vos workers manipulent des données de session.
# Build stage
FROM python:3.11-slim AS builder

WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir --target=/app/deps -r requirements.txt

# Runtime stage
FROM python:3.11-slim

# Run as non-root
RUN useradd --create-home solver
USER solver

WORKDIR /home/solver/app

COPY --from=builder /app/deps /home/solver/app/deps
COPY solver.py .

ENV PYTHONPATH=/home/solver/app/deps
ENV PYTHONUNBUFFERED=1

CMD ["python", "solver.py"]

PYTHONUNBUFFERED=1 fait remonter les logs immédiatement vers docker logs au lieu de les laisser bloqués dans un tampon — indispensable pour diagnostiquer un worker en direct.


Docker Compose : plusieurs workers en parallèle

Pour traiter un volume soutenu, répartissez la charge sur plusieurs conteneurs. Docker Compose orchestre le tout : un service de workers répliqué, une instance Redis qui sert de file d'attente, et un service dédié à la consommation des tâches. Les limites de mémoire et de CPU par conteneur évitent qu'un pic ne sature l'hôte.

# docker-compose.yml
version: "3.8"

services:
  solver-worker:
    build: .
    environment:

      - CAPTCHAAI_KEY=${CAPTCHAAI_KEY}
    restart: unless-stopped
    deploy:
      replicas: 4
      resources:
        limits:
          memory: 256M
          cpus: "0.25"

  redis:
    image: redis:7-alpine
    ports:

      - "6379:6379"

  queue-worker:
    build:
      context: .
      dockerfile: Dockerfile.worker
    environment:

      - CAPTCHAAI_KEY=${CAPTCHAAI_KEY}
      - REDIS_URL=redis://redis:6379
    depends_on:

      - redis
    deploy:
      replicas: 4

Gardez en tête que multiplier les répliques n'accélère rien au-delà du nombre de threads de votre plan CaptchaAI : un thread correspond à un CAPTCHA en cours de résolution. Avec BASIC ($15/mois, 5 threads), cinq résolutions tournent en parallèle et le reste patiente dans Redis ; pour aller plus loin, montez en gamme vers STANDARD ($30/mois, 15 threads) ou ADVANCE ($90/mois, 50 threads).


Un worker piloté par une file d'attente Redis

Le worker de production ne résout pas un CAPTCHA en one-shot : il boucle sur Redis avec blpop, récupère chaque tâche, la traite via l'API, puis écrit le résultat dans un hash. Ce découplage entre la mise en file et la résolution rend le système résilient — un worker qui plante n'emporte pas la file avec lui, et vous ajoutez de la capacité en lançant simplement plus de conteneurs.

# queue_worker.py
import os
import json
import time
import redis
import requests


def process_task(api_key, task_data):
    """Process a single CAPTCHA task from the queue."""
    resp = requests.post("https://ocr.captchaai.com/in.php", data={
        "key": api_key,
        "method": task_data["method"],
        "json": 1,
        **task_data["params"],
    }, timeout=30)
    result = resp.json()

    if result.get("status") != 1:
        return {"error": result.get("request")}

    task_id = result["request"]

    for _ in range(24):
        time.sleep(5)
        resp = requests.get("https://ocr.captchaai.com/res.php", params={
            "key": api_key, "action": "get",
            "id": task_id, "json": 1,
        }, timeout=15)
        data = resp.json()
        if data["request"] != "CAPCHA_NOT_READY":
            if data.get("status") == 1:
                return {"token": data["request"]}
            return {"error": data["request"]}

    return {"error": "timeout"}


def main():
    api_key = os.environ["CAPTCHAAI_KEY"]
    redis_url = os.environ.get("REDIS_URL", "redis://localhost:6379")
    r = redis.from_url(redis_url)

    print("Worker started, waiting for tasks...")
    while True:
        _, raw = r.blpop("captcha:tasks")
        task = json.loads(raw)
        task_id = task.get("id", "unknown")

        print(f"Processing task {task_id}...")
        result = process_task(api_key, task)

        r.hset("captcha:results", task_id, json.dumps(result))
        print(f"Task {task_id} done: {'ok' if 'token' in result else 'error'}")


if __name__ == "__main__":
    main()

Gérer la clé API et les variables d'environnement

Ne validez jamais votre clé API dans Git. Placez-la dans un fichier .env local, ajoutez-le au .gitignore, et laissez Docker Compose l'injecter au démarrage. Le drapeau --scale permet d'ajuster le nombre de workers à la volée, sans reconstruire quoi que ce soit.

# .env file (never commit to Git)
CAPTCHAAI_KEY=your_api_key_here

# .gitignore
echo ".env" >> .gitignore

# Run with .env file
docker compose --env-file .env up -d

# Scale workers
docker compose up -d --scale queue-worker=8

Pour un cluster, préférez les secrets orchestrés (Docker Swarm ou Kubernetes) à un fichier .env : la clé est alors montée comme un fichier accessible uniquement au processus, jamais visible dans docker inspect ni dans les variables d'environnement du conteneur.


Dépannage des conteneurs

Les incidents les plus courants tiennent à la configuration réseau ou à la gestion de la clé. Ce tableau reprend les symptômes typiques et leur correctif.

Problème Cause probable Correctif
Le conteneur s'arrête aussitôt lancé CAPTCHAAI_KEY absente Passer -e CAPTCHAAI_KEY=... au démarrage
La résolution DNS échoue Pas d'accès réseau depuis le conteneur Vérifier la configuration réseau de Docker
Consommation mémoire élevée Trop de résolutions simultanées Limiter la mémoire et la concurrence par conteneur
Clé API visible dans l'image Clé écrite en dur dans le Dockerfile Utiliser une variable d'environnement ou un secret

FAQ

Comment passer la clé API sans l'exposer dans l'image ?

Injectez-la au démarrage via une variable d'environnement (-e CAPTCHAAI_KEY=...) ou, en cluster, via un secret Docker monté sous /run/secrets/captchaai_key. Une clé écrite dans le Dockerfile se retrouve dans une couche de l'image et reste lisible par quiconque récupère cette image.

Combien de workers lancer pour mon plan CaptchaAI ?

Alignez le nombre de résolutions simultanées sur les threads de votre plan. Chaque worker applicatif gère 5 à 10 résolutions en parallèle, mais l'API ne traitera jamais plus de CAPTCHA en même temps que votre quota de threads : 5 avec BASIC ($15/mois, 5 threads), 15 avec STANDARD ($30/mois, 15 threads). Au-delà, les tâches attendent dans Redis.

Comment limiter la consommation mémoire des conteneurs ?

Fixez des limits de mémoire et de CPU dans le bloc deploy.resources de Docker Compose, comme le montre l'exemple (256 Mo, 0,25 CPU par worker). Ajustez ensuite le nombre de répliques plutôt que d'agrandir un seul conteneur : plusieurs petits workers sont plus faciles à superviser et à redémarrer.

Puis-je déployer ces workers sur OVHcloud ou Scaleway ?

Oui. L'image est standard et ne dépend d'aucune fonctionnalité propriétaire ; elle tourne aussi bien sur une instance OVHcloud, un conteneur Scaleway ou une région AWS eu-west-3 (Paris). Choisir un hébergeur proche de vos sites cibles réduit la latence réseau vers l'API.


Guides connexes


Conteneurisez votre solveur — créez votre compte CaptchaAI dès aujourd'hui.

Les commentaires sont désactivés pour cet article.