MAATRIX / Блог / PrivateBin в Docker Compose: готовый файл

PrivateBin в Docker Compose: готовый файл

MAATRIX

Если вам нужно передать коллеге пароль, приватный ключ или кусок конфига так, чтобы он не осел навсегда в переписке Telegram или почте, обычный pastebin не подходит — сервис видит текст в открытом виде и хранит его сколько захочет. PrivateBin решает это иначе: шифрование происходит в браузере до отправки на сервер, а сам сервер хранит только бессмысленный шифротекст и никогда не видит ни пароль от записи, ни её содержимое. Ниже — рабочий docker-compose.yml, который поднимает такой pastebin на вашем VPS за несколько минут, плюс нюансы, на которых обычно спотыкаются: права на volume, обратный прокси, ограничение размера вставки и автоудаление записей.

Обсудить статью, задать вопрос или начать новую тему

Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.

Перейти в сообщество →

Что такое PrivateBin и чем он отличается от обычного pastebin

PrivateBin — open-source проект (форк ныне заброшенного ZeroBin), написанный на PHP, без базы данных в классическом понимании — записи хранятся как файлы или в SQLite/Postgres/Redis, в зависимости от бэкенда хранилища. Ключевая идея — zero-knowledge: браузер генерирует ключ AES-256 (в текущих версиях — GCM-режим через WebCrypto API), шифрует текст локально, и на сервер уходит уже шифротекст плюс соль. Сам ключ никогда не покидает URL — он передаётся в fragment-части ссылки (#), которая по стандарту HTTP вообще не отправляется на сервер даже с TLS-соединением. Это значит, что владелец сервера, при всём желании — даже если его заставят по повестке отдать данные, — физически не может расшифровать содержимое конкретной записи без ссылки, которая осталась только у отправителя и получателя.

Отличие от обычных решений вроде обычного Pastebin.com или самохостед-аналогов типа Rustdesk-каталогов конфигов — в том, что PrivateBin по умолчанию:

  • удаляет запись после первого прочтения (burn after reading) или по таймеру;
  • не индексирует содержимое — искать по тексту записей нельзя в принципе, его сервер не видит;
  • поддерживает вложения файлов (тоже зашифрованные) и markdown/код с подсветкой синтаксиса;
  • не требует регистрации и не хранит IP отправителя в самой записи (хотя nginx/access-логи — отдельная история, о ней ниже).

Для команд разработки типичный сценарий — передать .env с секретами новому сотруднику, поделиться API-ключом с фрилансером, отправить приватный лог с ошибкой без риска, что он всплывёт в открытом чате через полгода.

Готовый docker-compose.yml

Официальный образ — privatebin/nginx-fpm-alpine, в котором уже связаны nginx, php-fpm и сам PrivateBin. Это самый простой вариант для продакшена — не нужно городить отдельный контейнер для fpm и отдельный для веб-сервера.

version: "3.9"

services:
  privatebin:
    image: privatebin/nginx-fpm-alpine:latest
    container_name: privatebin
    restart: unless-stopped
    ports:
      - "127.0.0.1:8099:8080"
    volumes:
      - privatebin_data:/srv/data
      - ./conf.php:/srv/cfg/conf.php:ro
    environment:
      - PRIVATEBIN_NAME=Notes
    tmpfs:
      - /var/lib/nginx/tmp
      - /var/tmp/nginx

volumes:
  privatebin_data:
    driver: local

Порт контейнера — 8080, и наружу мы его не публикуем напрямую, а слушаем только на loopback (127.0.0.1:8099), потому что TLS-терминацию и домен будет отдавать внешний реверс-прокси (Nginx или Caddy на хосте). Если вы разворачиваете PrivateBin на отдельном VPS без другого веб-сервера, порт можно пробросить как "80:8080" и поставить Caddy рядом отдельным сервисом — но для большинства случаев, когда на сервере уже крутится несколько сайтов, вариант с loopback-портом и общим реверс-прокси удобнее.

Ключевой момент — volume для данных должен быть постоянным, иначе при пересоздании контейнера (например, docker compose up -d после docker compose pull) все записи потеряются. В официальном образе данные по умолчанию пишутся в /srv/data внутри контейнера — именно этот путь и мапим на named volume.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать сервер

Конфигурация conf.php: лимиты, время жизни, storage-бэкенд

По умолчанию образ генерирует conf.php сам, но для продакшена лучше передать свой — так вы контролируете лимит размера вставки, доступные сроки хранения и язык интерфейса. Создайте файл conf.php рядом с docker-compose.yml:

[main]
name = "Notes"
basepath = ""
discussion = false
opendiscussion = false
password = true
fileupload = true
burnafterreadingselected = false
defaultformatter = "plaintext"
sizelimit = 10485760
template = "bootstrap-dark"
languageselection = false
languagedefault = "ru"
urlshortener = ""
qrcode = true
icon = "identicon"
htmlpurifier_cache_dir = "/tmp"

[expire]
default = "1week"

[expire_options]
5min = 300
10min = 600
1hour = 3600
1day = 86400
1week = 604800
1month = 2592000
never = 0

[formatter_options]
0 = "Plain Text"
1 = "Source Code"
2 = "Markdown"

[traffic]
limit = 10
header = ""
exempted = "127.0.0.1"

[purge]
limit = 300
batchsize = 10

[model]
class = Filesystem

[model_options]
dir = PATH . "data"

Пояснения по важным параметрам:

  • sizelimit = 10485760 — лимит на одну вставку в байтах, здесь 10 МБ. Если вы планируете передавать файлы (логи, дампы), увеличивайте — но помните, что весь файл шифруется и передаётся в браузере целиком, без стриминга, так что вставки на сотни мегабайт превращают страницу в тормоза на слабом клиентском устройстве.
  • password = true — разрешает дополнительную защиту паролем поверх шифрования ссылкой. Это не обязательный слой, но полезен, если ссылку придётся отправить не по защищённому каналу (например, в рабочий чат, где её может увидеть третий человек).
  • [traffic] limit — простая защита от спама: не более N вставок с одного IP за период (по умолчанию 10 минут). exempted = "127.0.0.1" нужен, если у вас перед PrivateBin стоит реверс-прокси и вы отдаёте реальный IP через X-Forwarded-For — иначе счётчик будет считать все запросы как один IP прокси.
  • class = Filesystem — самый простой бэкенд хранения, без БД. Для нагруженного инстанса с тысячами записей в день лучше class = Db с SQLite или PostgreSQL — Filesystem начинает деградировать на десятках тысяч мелких файлов из-за накладных расходов файловой системы.

Смонтировать конфиг нужно именно как файл, не директорию — иначе Docker создаст пустую папку вместо файла, если conf.php ещё не существует на хосте на момент первого up. Проверьте, что файл реально лежит рядом с compose-файлом до запуска.

Реверс-прокси и HTTPS

PrivateBin работает по HTTP внутри Docker-сети, а TLS терминирует внешний прокси. Если вы используете Caddy с автоматическим SSL — конфигурация тривиальна, полный разбор установки есть в статье про Caddy с авто-SSL на Ubuntu 24.04:

notes.example.com {
    reverse_proxy 127.0.0.1:8099

    header {
        Strict-Transport-Security "max-age=63072000"
        X-Content-Type-Options "nosniff"
        X-Frame-Options "DENY"
        Referrer-Policy "no-referrer"
    }
}

Для Nginx с сертификатом от Let's Encrypt конфиг чуть длиннее — важно правильно пробросить заголовки, иначе счётчик трафика в conf.php будет видеть только IP прокси:

server {
    listen 443 ssl http2;
    server_name notes.example.com;

    ssl_certificate     /etc/letsencrypt/live/notes.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/notes.example.com/privkey.pem;

    client_max_body_size 15m;

    location / {
        proxy_pass http://127.0.0.1:8099;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

Обратите внимание на client_max_body_size — если её не поднять выше значения по умолчанию (обычно 1 МБ), Nginx будет резать крупные вставки до того, как запрос дойдёт до PrivateBin, и пользователь получит невнятную ошибку 413 вместо понятного сообщения о превышении лимита. Значение должно быть чуть больше sizelimit из conf.php, с запасом на служебные заголовки шифрования. Если сомневаетесь, что выбрать между Nginx и Caddy для такой задачи — сравнение подходов есть в статье Caddy или Nginx: что выбрать для сервера.

Автоочистка старых записей

Записи с истёкшим сроком не удаляются сами по себе фоновым процессом — PrivateBin чистит их лениво, при следующем визите на главную страницу (согласно [purge] limit и batchsize из конфига). На малопосещаемом инстансе это может означать, что просроченные, но ещё не прочитанные burn-after-reading записи будут физически лежать на диске неделями. Если для вас важна гигиена хранения (например, из требований к обработке персональных данных), добавьте принудительную очистку по cron — самый надёжный способ дёрнуть garbage collection PrivateBin извне:

# /etc/cron.d/privatebin-purge
0 * * * * root curl -s -o /dev/null "https://notes.example.com/?" 

Это просто периодический GET на главную страницу — он триггерит внутреннюю логику purge так же, как обычный визит пользователя. Более подробно про настройку периодических задач — в статье про cron-задачи на VPS.

Если вы используете class = Db с PostgreSQL вместо Filesystem, та же логика purge применяется к строкам таблицы — отдельного механизма TTL на уровне БД в PrivateBin нет, так что cron-запрос выше обязателен независимо от выбранного бэкенда хранения.

Обновление и бэкап

Обновление образа — стандартная процедура для Docker Compose:

docker compose pull
docker compose up -d

Данные в named volume при этом не трогаются. Бэкапить стоит саму директорию с данными — если используете Filesystem-бэкенд, это privatebin_data volume; если Postgres — дамп базы. Учтите важный нюанс: бэкап шифрованных blob-файлов сам по себе бесполезен для восстановления доступа к содержимому без ссылок, которые были у пользователей (сервер физически не хранит ключи), — бэкапить нужно ради непрерывности сервиса (чтобы активные, ещё не прочитанные записи не потерялись при сбое диска), а не ради возможности что-то расшифровать постфактум.

docker run --rm -v privatebin_privatebin_data:/data -v $(pwd):/backup alpine \
  tar czf /backup/privatebin-data-$(date +%F).tar.gz -C /data .

Общие практики бэкапа Docker-стека, включая ротацию архивов и восстановление, разобраны в статье про Docker Compose для продакшена.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать сервер

Нужны сами нейросети для контента?

Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.

Частые вопросы

Можно ли развернуть PrivateBin без реверс-прокси, только на IP сервера без домена?

Да, технически можно — пробросьте порт 80 напрямую и заходите по http://IP-адрес. Но без HTTPS шифрование теряет смысл: ключ передаётся в URL fragment, и хотя браузер его не отправляет на сервер, сам факт работы по незащищённому HTTP делает вас уязвимыми к MITM-подмене JavaScript-кода шифрования на лету. Для реальной приватности домен и TLS обязательны.

Что произойдёт, если я забуду пароль сервера или потеряю доступ к VPS — можно ли восстановить конкретную запись?

Нет. Сервер не хранит ключ шифрования — он был только в URL-ссылке у отправителя и получателя. Даже полный root-доступ к серверу и его дискам не даёт возможности прочитать содержимое конкретной записи без этой ссылки. Это архитектурная особенность zero-knowledge, а не недоработка.

Чем PrivateBin отличается от Cryptpad или Standard Notes для похожих задач?

PrivateBin — про одноразовый обмен коротким секретом (пароль, ключ, код), а не про совместное редактирование или постоянное хранение заметок. Если нужен полноценный редактор документов с историей версий, посмотрите статью про Cryptpad в Docker Compose — это другой класс задач.

Нужен ли отдельный VPS под PrivateBin или можно на общий сервер с другими сайтами?

PrivateBin — крайне лёгкое приложение (PHP + nginx на Alpine), нагрузка минимальна даже при активном использовании командой из десятков человек. Разворачивать его отдельно от других сервисов не обязательно — достаточно того же VPS, где уже крутится реверс-прокси для остальных проектов, с отдельным поддоменом.

Как ограничить доступ к инстансу только для своей команды?

Проще всего — Basic Auth на уровне реверс-прокси (Nginx auth_basic или Caddy basicauth) поверх самого PrivateBin, либо VPN/firewall-правило, разрешающее подключение только с рабочих IP. Сам PrivateBin авторизации не имеет — он рассчитан на модель "у кого есть ссылка, тот и читает".

Обсудить статью, задать вопрос или начать новую тему

Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.

Перейти в сообщество →