API-ключ CaptchaAI не должен встречаться в коде ни одной строкой — ни в scraper.py, ни в Dockerfile, ни в тестовом скрипте, который «всё равно никто не увидит». Правильное место для него — переменная окружения: её читает процесс, но она не попадает ни в Git, ни в слои образа, ни в скриншот со стек-трейсом.
Ниже — рабочая схема по средам: .env для локальной разработки, переменные ОС для сервера, Docker secrets для контейнеров, секреты GitHub Actions и GitLab CI для пайплайна и проверка ключа при старте, чтобы сервис падал сразу, а не на первой задаче CAPTCHA.
Почему на это стоит потратить полчаса: ключ привязан к тарифу с оплатой по потокам — например, BASIC ($15/мес, 5 потоков) или ADVANCE ($90/мес, 50 потоков). Утёкший ключ — это не абстрактный риск, а чужие задачи в ваших потоках и очередь у ваших собственных воркеров.
Типичные ошибки
| Ошибка | Риск | Как исправить |
|---|---|---|
Коммит .env в Git |
Ключ навсегда остаётся в истории репозитория | Добавьте .env в .gitignore до первого коммита |
| Печать ключа в логах | Ключ виден в агрегаторе логов и в выводе CI | Не логируйте ключ целиком — маскируйте или опускайте |
Ключ в Dockerfile |
Ключ запечён в слои образа | Передавайте через ENV во время выполнения, не на сборке |
| Пересылка ключа в чате или почте | Ключ утекает вместе с историей переписки | Используйте менеджер секретов или защищённый канал |
Каждой из четырёх ошибок соответствует свой способ хранения. Короткая карта, куда класть ключ в зависимости от того, где выполняется код:
| Где выполняется код | Где хранить ключ | Чем плох соседний вариант |
|---|---|---|
| Ноутбук разработчика | .env + .gitignore |
Переменная в ~/.bashrc видна всем процессам пользователя |
| Одиночный сервер | Переменная окружения юнита systemd | Файл .env придётся деплоить и синхронизировать вручную |
| Контейнеры, Swarm | Docker secret в /run/secrets/ |
-e KEY=... виден в docker inspect и в списке процессов |
| Пайплайн CI | Маскированный секрет репозитория | Значение в .gitlab-ci.yml попадает в историю Git |
Разные ключи под разные среды
Практика, которая экономит больше всего времени в командах: заведите отдельный ключ на каждую среду — local, staging, production.
Имя переменной остаётся одним и тем же, меняется только значение в конкретном окружении.
Что это даёт:
- Утёкший ключ разработчика ротируется без остановки продакшена.
- По расходу потоков видно, какая среда их съедает: QA-прогон на staging не смешивается с боевым трафиком.
- Подрядчику или временному участнику команды выдаётся ключ, который отзывается отдельно от остальных.
Для распределённых команд — типичный расклад, когда разработчики в Москве, Минске, Алматы и Тбилиси работают в одном репозитории — это ещё и способ обойтись без пересылки общего ключа в мессенджере. Тариф остаётся один, ключи разные.
И отдельно про данные: если пайплайн параллельно собирает или логирует персональные данные, требования 152-ФЗ и аналогичных норм касаются именно вашей стороны — храните только то, что вы вправе обрабатывать, и никогда не пишите в лог сам ключ.
Шаг 1: вынесите ключ в файл .env
Создайте .env в корне проекта:
CAPTCHAAI_API_KEY=your_actual_api_key_here
И сразу — до первого коммита — закройте его в .gitignore:
# .gitignore
.env
.env.local
.env.production
Порядок здесь важнее содержимого.
Файл, попавший в индекс Git хотя бы один раз, остаётся в истории репозитория, и удаление его следующим коммитом ничего не меняет.
Python (python-dotenv)
pip install python-dotenv
import os
from dotenv import load_dotenv
load_dotenv()
API_KEY = os.environ["CAPTCHAAI_API_KEY"]
# Use in API calls
import requests
resp = requests.post("https://ocr.captchaai.com/in.php", data={
"key": API_KEY,
"method": "userrecaptcha",
"googlekey": "6Le-SITEKEY",
"pageurl": "https://example.com",
"json": "1",
})
print(resp.json())
Обратите внимание на os.environ["CAPTCHAAI_API_KEY"] вместо os.environ.get(...).
При отсутствующей переменной процесс упадёт с KeyError на старте, а не отправит в in.php пустой ключ и не получит невнятную ошибку через полминуты.
JavaScript (dotenv)
npm install dotenv
require('dotenv').config();
const API_KEY = process.env.CAPTCHAAI_API_KEY;
if (!API_KEY) {
console.error('CAPTCHAAI_API_KEY not set');
process.exit(1);
}
// Use in API calls
const axios = require('axios');
const resp = await axios.post('https://ocr.captchaai.com/in.php', null, {
params: {
key: API_KEY,
method: 'userrecaptcha',
googlekey: '6Le-SITEKEY',
pageurl: 'https://example.com',
json: 1,
},
});
console.log(resp.data);
Шаг 2: переменные окружения на уровне ОС
На сервере файл .env часто лишний.
Переменную задают средствами системы, и тогда её не нужно ни деплоить, ни синхронизировать.
Linux / macOS
export CAPTCHAAI_API_KEY="your_actual_api_key_here"
# Persist across sessions — add to ~/.bashrc or ~/.zshrc
echo 'export CAPTCHAAI_API_KEY="your_actual_api_key_here"' >> ~/.bashrc
Windows (PowerShell)
На Windows переменную задают в текущей сессии, а затем закрепляют на уровне пользователя:
$env:CAPTCHAAI_API_KEY = "your_actual_api_key_here"
# Persist permanently
[System.Environment]::SetEnvironmentVariable("CAPTCHAAI_API_KEY", "your_actual_api_key_here", "User")
Отдельный нюанс для локальной машины: значение, дописанное в ~/.bashrc, видно любому процессу пользователя, включая сторонние CLI-утилиты и расширения редактора.
Для рабочих машин разработчиков это приемлемо. Для общих или staging-серверов лучше сразу переходить к секретам оркестратора.
Шаг 3: Docker без ключа внутри образа
Переменная при docker run
docker run -e CAPTCHAAI_API_KEY="your_key" my-scraper
Docker Compose
В compose-файле переменная берётся из окружения хоста:
# docker-compose.yml
services:
scraper:
image: my-scraper
environment:
- CAPTCHAAI_API_KEY=${CAPTCHAAI_API_KEY}
Запись ${CAPTCHAAI_API_KEY} подставляет переменную хоста.
Сам ключ в compose-файл не попадает, поэтому файл можно спокойно держать в репозитории.
Docker secrets (Swarm)
echo "your_actual_api_key_here" | docker secret create captchaai_key -
# docker-compose.yml (Swarm mode)
services:
scraper:
image: my-scraper
secrets:
- captchaai_key
secrets:
captchaai_key:
external: true
Читаем в коде:
with open("/run/secrets/captchaai_key") as f:
API_KEY = f.read().strip()
Разница принципиальная.
Переменная окружения видна в docker inspect и в списке процессов, а секрет монтируется в /run/secrets/ и не светится в метаданных контейнера.
Шаг 4: секреты в CI/CD
GitHub Actions
# .github/workflows/scrape.yml
jobs:
scrape:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: python scraper.py
env:
CAPTCHAAI_API_KEY: ${{ secrets.CAPTCHAAI_API_KEY }}
Секрет добавляется в Settings → Secrets and variables → Actions → New repository secret.
GitLab CI
# .gitlab-ci.yml
scrape:
script:
- python scraper.py
variables:
CAPTCHAAI_API_KEY: $CAPTCHAAI_API_KEY
Переменная добавляется в Settings → CI/CD → Variables с включённой опцией «Masked».
Тогда значение маскируется в логах джобы.
Отдельно проверьте форки и merge request от внешних участников.
Если секрет отдаётся пайплайну форка, достаточно одного PR со строкой echo $CAPTCHAAI_API_KEY.
Шаг 5: проверка ключа при старте
Пусть сервис проверяет ключ до того, как примет первую задачу:
import os
import sys
import requests
API_KEY = os.environ.get("CAPTCHAAI_API_KEY")
if not API_KEY:
print("ERROR: CAPTCHAAI_API_KEY environment variable not set")
sys.exit(1)
# Verify key works
resp = requests.get("https://ocr.captchaai.com/res.php", params={
"key": API_KEY, "action": "getbalance", "json": "1"
}).json()
if resp["status"] != 1:
print(f"ERROR: Invalid API key — {resp['request']}")
sys.exit(1)
print(f"API key valid — balance: ${float(resp['request']):.2f}")
Запрос getbalance к res.php отвечает сразу на два вопроса: ключ валиден и на балансе есть средства.
Ставьте эту проверку в readiness-пробу контейнера или в первые строки воркера — тогда сломанный деплой отсекается на старте, а не превращается в поток ошибок в мониторинге.
Чек-лист перед деплоем
Минимальный набор действий, который закрывает основную часть риска:
.envв.gitignoreдо первого коммита, а не после.- Разные значения ключа для
local,stagingиproduction. - Проверка
getbalanceпри старте сервиса. - Маскирование секрета в логах CI и запрет на его выдачу пайплайнам форков.
Частые вопросы
Что выбрать для продакшена: .env или менеджер секретов?
Для продакшена — менеджер секретов.
Шифровать сам .env смысла мало: ключ расшифровки всё равно надо где-то хранить. Подойдут AWS Secrets Manager, Google Secret Manager, Azure Key Vault или HashiCorp Vault, а .gitignore остаётся достаточным только для локальной разработки.
Ключ уже попал в публичный репозиторий — что делать в первую очередь?
Сначала ротируйте ключ в панели управления CaptchaAI, потом чистите историю.
Порядок именно такой: пока старый ключ действителен, он остаётся рабочим у всех, кто успел его скопировать, независимо от того, что вы сделали с репозиторием.
Можно ли держать несколько ключей в одном .env?
Да, через список значений или пронумерованные переменные:
CAPTCHAAI_KEYS=key1,key2,key3
keys = os.environ["CAPTCHAAI_KEYS"].split(",")
Учтите, что несколько ключей не суммируют потоки одного тарифа — это способ разделить среды или проекты, а не увеличить пропускную способность.
Где ключ виден на сервере, даже если он лежит в переменной окружения?
В /proc/<pid>/environ, в выводе docker inspect, в дампах падений и в отладочных эндпоинтах, которые печатают окружение целиком.
Поэтому продакшен-нагрузки лучше переводить на Docker secrets или менеджер секретов, а отладочные ручки с выводом окружения закрывать.
Отличается ли схема хранения для разных типов CAPTCHA?
Нет. Один и тот же ключ работает для reCAPTCHA v2 и v3, Cloudflare Turnstile, Cloudflare Challenge, GeeTest v3, image/OCR и grid-задач, а также для CaptchaFox (beta), Friendly Captcha (beta) и Lemin (beta). Меняется только параметр method в запросе к in.php — хранение ключа одинаковое.
Подключите ключ правильно с первого дня
Получите API-ключ на captchaai.com и заведите отдельное значение под каждую среду.