Self-hosted
Минимальный self-hosted вариант trip2g: ghcr.io/trip2g/trip2g:latest + MinIO + Caddy.
Этот вариант подходит, если вам нужен один сервер, один docker-compose.yml и запуск через docker compose up -d.
Что поднимется
trip2gподнимает сайт, админку, авторизацию и git-репозиторий заметок.minioхранит ассеты и простые резервные копии SQLite.caddyпринимает внешнийHTTP/HTTPSи проксирует запросы внутрь compose-сети.- Векторный поиск можно оставить выключенным или включить через OpenAI / OpenAI-compatible embeddings API.
Что важно не забыть
- Для публичного сервера нужен
HTTPS. Иначе secure-cookie для входа не будет работать. - Для входа по email нужен аккаунт в
resend.com, API key и подтвержденный домен или поддомен отправителя. - Для production обязательно задайте свои
JWT_SECRETиDATA_ENCRYPTION_KEY. Со значениями по умолчанию сервер падать или работать небезопасно. - Наружу обычно публикуются только
80и443уcaddy.
Подготовка
Нужны:
- Linux-сервер с Docker и Docker Compose plugin
- домен для сайта, например
docs.example.com - поддомен для email-отправителя, например
mg.example.com - доступ к DNS
Создайте директорию, например /opt/trip2g, и положите туда два файла: docker-compose.yml и .env.
Проверьте сервер перед установкой
Если сервер не чистый, убедитесь до запуска:
- Порты
80и443свободны — compose отдаёт их Caddy:ss -tlnp | grep -E ':80 |:443 ' - Нет другого Caddy, Nginx или Traefik, уже слушающего эти порты. Если есть — его нужно остановить или перенести.
- Нет конфликтующих Docker-сетей от других проектов (редко, но бывает при нестандартном overlay).
Если порты заняты существующим reverse proxy (Nginx, Caddy, Traefik), убирать его не нужно — достаточно прописать trip2g как upstream в нём. Тогда сервис caddy из docker-compose.yml можно убрать, а порт 8081 опубликовать напрямую. То же самое касается MinIO: если у вас уже есть своё объектное хранилище — MinIO поднимать не нужно, достаточно указать его реквизиты в .env.
docker-compose.yml
services:
caddy:
image: caddy:2
restart: unless-stopped
depends_on:
trip2g:
condition: service_started
minio:
condition: service_healthy
ports:
- "80:80"
- "443:443"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- caddy-data:/data
- caddy-config:/config
minio:
image: minio/minio:latest
restart: unless-stopped
command: server /data --console-address ":9001"
environment:
MINIO_ROOT_USER: ${MINIO_ROOT_USER}
MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD}
volumes:
- minio-data:/data
expose:
- "9000"
- "9001"
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
interval: 5s
timeout: 5s
retries: 20
trip2g:
image: ghcr.io/trip2g/trip2g:latest
restart: unless-stopped
depends_on:
minio:
condition: service_healthy
env_file:
- .env
volumes:
- trip2g-data:/data
expose:
- "8081"
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:8082/healthz"]
interval: 10s
timeout: 5s
retries: 12
start_period: 15s
volumes:
caddy-data:
caddy-config:
trip2g-data:
minio-data:
Что здесь важно:
caddyпубликует только80и443.trip2g-dataхранит данные trip2g и внутренний bare git-репозиторий.minio-dataхранит MinIO-объекты.trip2gиminioдоступны только внутри compose-сети.
.env
Минимальный .env для production:
PUBLIC_URL=https://docs.example.com
LISTEN_ADDR=0.0.0.0:8081
INTERNAL_LISTEN_ADDR=:8082
DB_FILE=/data/data.sqlite3
GIT_API_REPO_PATH=/data/git
LOG_LEVEL=info
DEV=false
OWNER_EMAIL=owner@example.com
MAIL_FROM=no-reply@mg.example.com
RESEND_API_KEY=re_xxxxxxxxx
JWT_SECRET=replace-with-long-random-secret
DATA_ENCRYPTION_KEY=0123456789abcdef0123456789abcdef
MINIO_ROOT_USER=trip2g
MINIO_ROOT_PASSWORD=replace-with-long-random-password
MINIO_ENDPOINT=minio:9000
MINIO_PUBLIC_URL=https://files.example.com
MINIO_ACCESS_KEY_ID=trip2g
MINIO_SECRET_KEY=replace-with-long-random-password
MINIO_BUCKET=trip2g
MINIO_REGION=us-east-1
MINIO_USE_SSL=false
MINIO_INIT_TIMEOUT=30s
MINIO_URL_EXPIRES_IN=10m
SIMPLE_BACKUP=true
FEATURES={}
# Для OpenAI embeddings:
# OPENAI_API_KEY=sk-...
# FEATURES={"vector_search":{"enabled":true,"model":"text-embedding-3-small"}}
# Для OpenAI-compatible embeddings API:
# OPENAI_API_KEY=provider-token-if-needed
# FEATURES={"vector_search":{"enabled":true,"model":"bge-m3","base_url":"https://embeddings.example.com/v1"}}
Что означает каждая настройка
PUBLIC_URL— внешний адрес вашего сайта. Используется в ссылках, email и интеграциях.LISTEN_ADDR— адрес, на котором слушает основной HTTP-сервер.INTERNAL_LISTEN_ADDR— внутренний адрес для healthcheck и служебных endpoint'ов.DB_FILE— путь к файлу данных внутри контейнера.GIT_API_REPO_PATH— путь к встроенному git-репозиторию trip2g.LOG_LEVEL— уровень логов сервера.DEV=false— production-режим. Не включайтеDEV=trueна публичном сервере.OWNER_EMAIL— email владельца инстанса.MAIL_FROM— адрес отправителя. Он должен принадлежать домену, который вы подтвердили в Resend.RESEND_API_KEY— API key для отправки кодов входа по email.JWT_SECRET— секрет для пользовательских сессий. После смены старые сессии станут недействительными.DATA_ENCRYPTION_KEY— 32-байтовый ключ для шифрования чувствительных данных. Сгенерировать:openssl rand -base64 32 | head -c 32MINIO_ROOT_USER/MINIO_ROOT_PASSWORD— root-учетка самого MinIO.MINIO_ENDPOINT— адрес MinIO из контейнераtrip2g.MINIO_PUBLIC_URL— публичный адрес MinIO, который попадет в presigned URL для файлов.MINIO_ACCESS_KEY_ID/MINIO_SECRET_KEY— ключи, которымиtrip2gходит в MinIO.MINIO_BUCKET— bucket для ассетов и backup-объектов.MINIO_REGION— регион S3-совместимого API. Для MinIO обычно оставляютus-east-1.MINIO_USE_SSL=false— между контейнерами обычно не нужен TLS.MINIO_INIT_TIMEOUT— сколько ждать MinIO на старте.MINIO_URL_EXPIRES_IN— срок жизни presigned URL для скачивания файлов.SIMPLE_BACKUP=true— включает простые backup'ы SQLite в MinIO.FEATURES— JSON-конфиг feature flags. Здесь включается векторный поиск.OPENAI_API_KEY— ключ для OpenAI или совместимого embeddings API.
Опционально для локального smoke-test без HTTPS:
PUBLIC_URL=http://SERVER_IP:8081
USER_TOKEN_INSECURE=true
Но это только для временной проверки. Для публичного сервера оставляйте secure cookies и ставьте TLS.
Caddyfile
Если хотите нормальный публичный setup, дайте сайту и файлам отдельные домены:
docs.example.com→ trip2gfiles.example.com→ MinIO
Положите рядом с docker-compose.yml файл Caddyfile:
docs.example.com {
encode zstd gzip
reverse_proxy trip2g:8081
}
files.example.com {
encode zstd gzip
reverse_proxy minio:9000
}
# Опционально, если нужна MinIO console снаружи:
# minio-admin.example.com {
# reverse_proxy minio:9001
# }
Для такой схемы в .env должны совпасть:
PUBLIC_URL=https://docs.example.com
MINIO_PUBLIC_URL=https://files.example.com
Зачем это нужно:
- trip2g работает на основном домене;
- ссылки на файлы отдаются с публичного MinIO-домена;
caddyходит к сервисам по именамtrip2gиminioвнутри docker-сети.
Внешнее объектное хранилище вместо MinIO
По умолчанию MinIO работает на том же сервере, что и trip2g. Это удобно для старта, но не защищает от потери сервера: если диск умрёт, пропадут и файлы, и резервные копии.
Для production рекомендуем вынести хранилище на отдельный S3-совместимый сервис: Backblaze B2, Hetzner Object Storage, Timeweb S3 и другие.
В этом случае сервис minio из docker-compose.yml можно убрать полностью, а в .env указать реквизиты внешнего сервиса:
MINIO_ENDPOINT=s3.us-east-005.backblazeb2.com
MINIO_PUBLIC_URL=https://files.example.com
MINIO_ACCESS_KEY_ID=your-key-id
MINIO_SECRET_KEY=your-secret
MINIO_BUCKET=trip2g
MINIO_REGION=us-east-005
MINIO_USE_SSL=true
Тогда SIMPLE_BACKUP=true будет складывать резервные копии SQLite уже на внешний сервис — автоматически, без дополнительных усилий и с защитой от потери сервера.
Репликация SQLite через Litestream
SIMPLE_BACKUP=true делает периодические snapshot'ы базы в MinIO. Если нужна непрерывная репликация SQLite с интервалом в 1 секунду, добавьте Litestream.
Litestream запускается на хосте как systemd-сервис и реплицирует файл базы напрямую в S3-совместимое хранилище. В infra/ уже есть готовая конфигурация:
infra/generate-litestream-config.sh— генерирует/etc/litestream.ymlиз переменных окруженияinfra/litestream.service— systemd unit
Конфигурация читает те же переменные, что и .env trip2g: MINIO_ACCESS_KEY_ID, MINIO_SECRET_KEY, MINIO_ENDPOINT, MINIO_BUCKET, DB_FILE. После установки litestream:
sudo cp infra/generate-litestream-config.sh /usr/local/bin/generate-litestream-config.sh
sudo chmod +x /usr/local/bin/generate-litestream-config.sh
sudo cp infra/litestream.service /etc/systemd/system/litestream.service
sudo systemctl enable --now litestream
Litestream и SIMPLE_BACKUP можно использовать одновременно — они не конфликтуют. Особенно полезна эта связка с внешним объектным хранилищем: тогда и файлы, и база данных хранятся вне сервера.
Как создать бесплатный аккаунт Resend
На resend.com:
- Зарегистрируйте бесплатный аккаунт.
- Добавьте домен или, лучше, поддомен отправителя, например
mg.example.com. - Подтвердите DNS-записи, которые покажет Resend.
- Создайте API key.
- Поставьте этот ключ в
RESEND_API_KEY. - Укажите
MAIL_FROMна адрес внутри подтвержденного домена, напримерno-reply@mg.example.com.
Почему лучше поддомен:
- проще изолировать почтовую репутацию;
- не нужно смешивать транзакционные письма trip2g с основной почтой домена.
Если домен в Resend не подтверждать, письма будут приходить только вам самому. Этого достаточно, если email-вход нужен только владельцу инстанса. Если по email должны входить другие пользователи, домен отправителя нужно подтвердить.
Векторный поиск через OpenAI или совместимый сервис
По умолчанию trip2g прекрасно работает и без этого. Полнотекстовый поиск останется доступен.
Если нужен семантический поиск:
OpenAI
OPENAI_API_KEY=sk-...
FEATURES={"vector_search":{"enabled":true,"model":"text-embedding-3-small"}}
Рекомендуемая модель для старта: text-embedding-3-small.
OpenAI-compatible embeddings API
OPENAI_API_KEY=provider-token-if-needed
FEATURES={"vector_search":{"enabled":true,"model":"bge-m3","base_url":"https://embeddings.example.com/v1"}}
Важно: trip2g валидирует имя embedding-модели. Сейчас поддерживаются:
text-embedding-3-smalltext-embedding-3-largetext-embedding-ada-002multilingual-e5-basebge-m3
То есть “любой OpenAI-compatible сервис” подойдет только если он умеет отдавать embeddings через совместимый /v1 API и вы используете одно из поддерживаемых имен моделей.
Запуск
В директории с docker-compose.yml:
docker compose up -d
Если у вас старый синтаксис Compose:
docker-compose up -d
Проверка:
docker compose ps
docker compose logs -f caddy trip2g
После старта:
- откройте
https://docs.example.com - войдите по email владельца из
OWNER_EMAIL - на пустом инстансе сервис сам предложит ссылку на скачивание ZIP с настроенным vault
- настройте Obsidian plugin на ваш
PUBLIC_URL
Дальше можно идти в Начало работы и продолжать уже с плагином Obsidian.
Что еще легко забыть
- DNS
A/AAAAзапись дляPUBLIC_URL - DNS
A/AAAAзапись дляMINIO_PUBLIC_URL - TLS-сертификат на домен сайта
- DNS-записи Resend для домена отправителя
- сохранность volume
trip2g-data - не публикуйте MinIO console (
9001) без необходимости - мониторинг логов после первого входа и первой отправки письма
Если нужен максимально простой старт, сначала поднимите инстанс без векторного поиска, проверьте вход по email, и только потом добавляйте FEATURES для embeddings.