Контейнерные стеки dix.su¶
Контейнерный стек — готовый docker-compose.yml, который поднимает пользовательский
сервис вместе с туннельным клиентом в одном контейнере. Пользователь запускает
стек у себя на машине или VPS — туннель поднимается автоматически.
Гайд по запуску — guides/container-stacks.md.
Доступные стеки¶
| Стек | Репозиторий | Назначение | Мин. RAM |
|---|---|---|---|
dixsu-shop / django_shop |
gitlab.com/dixsu/market/django_shop |
Django интернет-магазин (Oscar Commerce) | 512 МБ |
dixsu-git |
gitlab.com/dixsu/market/dixsu-git |
Git-хостинг на базе Forgejo + встроенный Docker/OCI-реестр и реестры пакетов (PyPI, Maven, npm, ...) — см. guides/forgejo-registry.md |
512 МБ |
dixsu-plausible |
gitlab.com/dixsu/market/dixsu-plausible |
Аналитика посещений (Plausible CE v3.2.1) | 2 ГБ ¹ |
dixsu-test |
gitlab.com/dixsu/market/dixsu-test |
Тестовый стек (веб-сервер-фикстура для QA) | 128 МБ |
¹ Требование ClickHouse: CPU с поддержкой SSE 4.2 (x86) или NEON (ARM). Подходит: RPi 4 4GB+, RPi 5, мини-ПК. Не подходит: Redmi 4, Redmi Note 8.
Все стеки используют одну и ту же структуру: сервис(ы) приложения + сервис tunnel
(контейнер ramanzes/dixsu-tunnel:latest).
Архитектура¶
docker-compose.yml
├── app ← пользовательский сервис (Django, Forgejo, ...)
│ слушает на внутреннем порту (обычно 8000 или 3000)
└── tunnel ← ramanzes/dixsu-tunnel:latest
DIXSU_SLUG = ваш slug из личного кабинета
DIXSU_TOKEN = ваш токен из личного кабинета
LOCAL_PORT = порт, на котором слушает app
TUNNEL_SOURCE = "docker"
│
└── подключается к dix.su через WSS
передаёт трафик от https://slug.dix.su
на app:LOCAL_PORT внутри docker-сети
Контейнеры общаются через docker-сеть — tunnel обращается к app
по имени сервиса (не через localhost).
Docker-образ туннельного клиента¶
ramanzes/dixsu-tunnel:latest — минимальный образ на базе elixir:1.19-slim.
Dockerfile¶
FROM elixir:1.19-slim
RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY device_client.exs .
ENTRYPOINT ["elixir", "device_client.exs"]
CA-сертификаты необходимы — без них Mix.install не может скачать зависимости
с hex.pm по HTTPS.
Конфигурация через env¶
Образ поддерживает два режима запуска:
1. Env-переменные (Docker-режим)
environment:
DIXSU_SLUG: your-slug
DIXSU_TOKEN: your-token
LOCAL_PORT: "8000"
TUNNEL_SOURCE: "docker"
2. CLI-аргументы (прямой режим)
| Переменная | CLI-эквивалент | Default | Описание |
|---|---|---|---|
DIXSU_SLUG |
$1 |
обязательно | Slug туннеля |
DIXSU_TOKEN |
$2 |
обязательно | Токен туннеля |
LOCAL_PORT |
$3 |
80 |
Порт, на котором слушает приложение |
TUNNEL_SOURCE |
— | "direct" |
"docker" для контейнерных стеков |
SERVER_HOST |
— | 127.0.0.1 |
Хост прокси-сервера |
SERVER_PORT |
— | 4001 |
Порт прокси-сервера |
CA_CERT_PATH |
— | — | Путь к CA-сертификату (для self-signed) |
Репозиторий образа: gitlab.com/dixsu/elixir/websocket-client
Идентификация источника туннеля (TUNNEL_SOURCE)¶
При подключении к серверу клиент передаёт ?source=direct|docker в WebSocket-URL:
Phoenix-сервер сохраняет значение в Registry-метаданных:
# dixu_proxy: channel join
{:ok, socket} = Socket.assign(socket, :source, source_param)
Registry.register(DeviceRegistry, slug, %{source: source})
Внутренний API /internal/tunnel/:slug возвращает поле source вместе со
статусом активности. Личный кабинет читает его через /api/profile/tunnel/status.
Отображение в UI (dix.su/auth/protected#tunnel)¶
| Источник | Иконка | Метка |
|---|---|---|
direct |
⚡ | активен · прямой |
docker |
📦 | активен · docker-стек |
| туннель не активен | — | не подключён |
Ограничения для docker-стеков¶
Ряд функций отключён для контейнерных туннелей (источник "docker"):
Заблокированные TCP-порты¶
| Протокол | Порт | Причина |
|---|---|---|
| SSH | 22 | недоступен изнутри контейнера |
| RDP | 3389 | не имеет смысла для web-сервисов |
| VNC | 5900 | не имеет смысла для web-сервисов |
Тумблеры этих протоколов в UI не активны, при наведении — подсказка с объяснением.
Блокировка настроек при активном туннеле¶
Когда туннель активен (любого типа — прямой или docker), в разделе «Туннель» личного кабинета блокируются:
- Кнопки выбора платформы (Linux/macOS/Windows/ARM)
- Кнопка скачивания бинарника
- Поле порта
- Кнопка копирования команды установки
- Форма ввода email
- Кнопка копирования E2E-ссылки
Отображается жёлтый баннер с предупреждением. Это предотвращает попытку создать новый туннель, пока один уже активен.
Пример: структура docker-compose.yml стека¶
services:
app:
image: ...
expose:
- "8000"
env_file: .env
networks:
- dixsu
tunnel:
image: ramanzes/dixsu-tunnel:latest
restart: unless-stopped
depends_on:
- app
environment:
DIXSU_SLUG: ${DIXSU_SLUG}
DIXSU_TOKEN: ${DIXSU_TOKEN}
LOCAL_PORT: "8000"
SERVER_HOST: dix.su
SERVER_PORT: "443"
TUNNEL_SOURCE: "docker"
networks:
- dixsu
networks:
dixsu:
driver: bridge
Заметки:
- expose, а не ports для app — порт не публикуется наружу, только в docker-сети.
- restart: unless-stopped на tunnel — обеспечивает автоматическое переподключение.
- depends_on: app — tunnel стартует после app, но TCP-relay всё равно работает
если app временно упал (клиент переподключается).
Деплой стеков через git¶
Каждый стек хранится в отдельном репозитории на GitLab:
# Первый запуск
git clone git@gitlab.com:dixsu/market/django_shop.git
cd django_shop
cp .env.template .env
# Заполнить .env → DIXSU_SLUG, DIXSU_TOKEN, ...
docker compose up -d
# Обновление
git pull
docker compose up -d --build app # только приложение, без пересборки tunnel
Образ ramanzes/dixsu-tunnel:latest обновляется отдельно — rebuild нужен только
при смене логики клиента (новый TUNNEL_SOURCE, env-fallback и т.д.).
Архитектура стека dixsu-plausible (3 сервиса + tunnel)¶
Plausible CE состоит из трёх компонентов, каждый в отдельном контейнере:
docker-compose.yml
├── plausible_db ← PostgreSQL 16 (пользователи, сайты, настройки)
├── plausible_events_db ← ClickHouse 24.12 (события посещений)
├── plausible ← ghcr.io/plausible/community-edition:v3.2.1
│ порт 8000, зависит от обеих БД (condition: healthy)
└── tunnel ← ramanzes/dixsu-tunnel:latest → LOCAL_PORT=8000
plausible стартует только после того, как обе БД перешли в healthy.
При первом старте выполняет миграции автоматически.
Конфигурация .env¶
# ── dix.su туннель ────────────────────────────────────────────────
DIXSU_SLUG=myanalytics # ← ваш slug
DIXSU_TOKEN=abc123xyz # ← ваш токен
# ── Plausible ─────────────────────────────────────────────────────
BASE_URL=https://myanalytics.dix.su # публичный URL инстанса
SECRET_KEY_BASE=<openssl rand -base64 64> # мин. 64 байта
# ── PostgreSQL ────────────────────────────────────────────────────
POSTGRES_PASSWORD=сильный-пароль
# DATABASE_URL по умолчанию строится из POSTGRES_PASSWORD автоматически
# ── ClickHouse ────────────────────────────────────────────────────
# CLICKHOUSE_DATABASE_URL по умолчанию http://plausible_events_db:8123/plausible_events
Полный список env-переменных (SMTP, Google OAuth, TOTP, геолокация) — в GitHub Wiki проекта.
Интеграция сайта¶
Plausible работает на стороне браузера. Добавить в <head> отслеживаемого сайта:
Бэкенд (Django/Node/любой) этот обмен не видит — он просто один раз отдаёт HTML со скриптом.
Дополнительно:
- Events API (POST /api/event) — события с сервера без JS (конверсии, регистрации)
- Stats API — чтение агрегированной статистики из своего приложения
Известные ограничения¶
- Один туннель на slug: нельзя запустить два стека с одним slug одновременно — второй соединится и вытолкнет первый.
- TCP-порты недоступны: docker-стек не форвардит SSH/RDP/VNC — только HTTP/HTTPS.
- Email: по умолчанию
EMAIL_BACKEND=django.core.mail.backends.dummy.EmailBackend— письма игнорируются. Для отправки сменить наsmtp.EmailBackendи задать SMTP-параметры. - dixsu-plausible требует мощного железа: ClickHouse не запустится без SSE 4.2 / NEON и менее 2 ГБ RAM. На Redmi 4 / Redmi Note 8 стек не работоспособен.