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:
- Filas de tarefas no Kubernetes para resolução de CAPTCHA
- Workers de resolução de CAPTCHA com escalonamento automático
Containerize seu solucionador — comece com a CaptchaAI hoje mesmo.