DevOps & Scaling

Docker + CaptchaAI: solução de CAPTCHA em contêiner

A forma mais previsível de rodar o solucionador de CAPTCHA da CaptchaAI em produção é dentro de um contêiner Docker: build reprodutível, chave de API isolada por variável de ambiente e escalonamento horizontal com docker compose up --scale. Este guia vai do Dockerfile mínimo ao build multi-estágio e termina numa fila de workers com Redis — sem credencial fixada no código em nenhuma etapa.


Monte o Dockerfile do solucionador

Comece com uma imagem enxuta baseada na imagem oficial do Python. A chave de API nunca entra na imagem — ela chega como variável de ambiente no momento de execução:

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"]

requirements.txt:

requests>=2.31.0

Fixar a versão evita que um lançamento novo de requests quebre o build sem aviso — a mesma imagem sobe igual no seu notebook e no pipeline de CI.


Script Python que fala com a API da CaptchaAI

O script envia o desafio ao endpoint in.php, faz o polling em res.php a cada 5 s e devolve o token pronto para o formulário de destino. O timeout de 120 s cobre a maioria dos casos de reCAPTCHA v2 — aumente o número de tentativas se o seu volume de envios for maior:

# 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]}...")

Build da imagem e execução do contêiner

Gere a imagem local e suba um contêiner de teste passando a chave de API só na hora de executar — nunca durante o docker build:

# 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

Rode esse comando localmente antes de subir qualquer coisa para o registry. Se o contêiner imprimir o token no log, a integração com a API está funcionando — o próximo passo é só uma questão de escala.


Build multi-estágio para produção

Separe a instalação das dependências da execução: imagem menor, sem compilador nem cache do pip, rodando com usuário sem privilégios de root:

# 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"]

O usuário solver sem privilégios de root reduz o dano de qualquer vulnerabilidade no processo Python: se o contêiner for comprometido, ele não tem permissão para escrever fora do próprio diretório de trabalho.


Docker Compose com múltiplos workers

Defina replicas no serviço e deixe o Compose (ou o Swarm/Kubernetes) distribuir as tarefas entre os contêineres. O exemplo abaixo sobe quatro workers de resolução mais uma fila Redis compartilhada:

# 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

Times que atendem principalmente usuários no Brasil costumam hospedar esses workers numa região AWS como sa-east-1 (São Paulo), reduzindo a latência até o site de destino e deixando o tempo de cada tarefa mais previsível do que rodando em uma região nos EUA. Um healthcheck no serviço solver-worker ajuda o Compose (ou o orquestrador equivalente) a reiniciar automaticamente qualquer réplica que trave sem derrubar as demais.


Worker que consome a fila com Redis

Esse worker consome tarefas da fila captcha:tasks, resolve cada uma via API e grava o resultado num hash captcha:results — útil quando o serviço que recebe o formulário do usuário roda separado do serviço que resolve o CAPTCHA:

# 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()

Quem publica a tarefa na fila (r.rpush("captcha:tasks", ...)) e quem consome o resultado (r.hget("captcha:results", task_id)) podem ser serviços completamente separados — um front-end web, um job agendado, ou outro contêiner. O Redis vira a fronteira entre "quem precisa do token" e "quem sabe resolver CAPTCHA".


Variáveis de ambiente e segredos

Mantenha a chave de API fora do controle de versão: .env local (nunca commitado) resolve o caso simples; em Swarm ou Kubernetes, prefira segredos nativos:

# .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

Se o formulário atrás do CAPTCHA coleta dados de clientes, trate os logs dos workers como dado sensível sob a LGPD: evite gravar o payload completo e limite a retenção dos logs por task_id.


Erros comuns e como corrigir

Antes de investigar código, rode docker logs <contêiner> — a maioria dos problemas abaixo aparece direto na saída do worker:

Problema Causa Correção
O contêiner sai imediatamente CAPTCHAAI_KEY ausente Passe -e CAPTCHAAI_KEY=...
A resolução DNS falha Sem acesso à rede Verifique as configurações de rede do Docker
Uso de memória alto Muitas requisições simultâneas Limite a memória e a concorrência do contêiner
Chave de API exposta na imagem Chave gravada direto no Dockerfile Use variáveis de ambiente ou segredos
Worker fica ocioso sem processar Redis inacessível a partir do contêiner Confira REDIS_URL e a rede do Compose entre os serviços

Perguntas frequentes

Preciso rodar o solver-worker e o queue-worker ao mesmo tempo?

Não necessariamente. Use só o solver-worker com replicas quando cada chamada já sabe qual site e sitekey resolver. Adicione o queue-worker com Redis quando o serviço que recebe o formulário do usuário estiver separado do serviço que resolve o CAPTCHA, como no exemplo deste guia.

Qual imagem base é mais indicada para rodar em produção?

python:3.11-slim, no build multi-estágio deste guia: mantém só o binário do Python e as dependências, sem compilador, rodando com usuário não-root.

Quantos workers preciso para acompanhar o plano BASIC (5 threads)?

No BASIC (US$ 15/mês, 5 threads), 4 a 5 workers já usam boa parte da capacidade contratada. Em planos maiores, como ADVANCE (US$ 90/mês, 50 threads) ou ENTERPRISE (US$ 300/mês, 200 threads), ajuste replicas no Compose na mesma proporção.

Posso usar segredos do Docker em vez de variáveis de ambiente?

Sim. Docker Swarm e Kubernetes suportam segredos nativos: monte-os como arquivo e leia em /run/secrets/captchaai_key dentro do contêiner.

Vale a pena configurar restart automático para os workers?

Sim. restart: unless-stopped, como no exemplo do Compose, garante que um worker que trave por falha de rede ou timeout volte sozinho, sem depender de alguém reiniciar o contêiner manualmente às 3 da manhã.


Guias relacionados

Para evoluir essa arquitetura além de um único host Docker, veja também:


Containerize seu solucionador — comece com a CaptchaAI hoje mesmo.

Os comentários estão desativados para este artigo.