Частина посилань на цій сторінці партнерські: якщо ви зареєструєтесь через них, сайт отримає комісію, для вас ціна не змінюється. На ціни й рекомендації це не впливає.
Одинадцять днів тому ми поставили privacy-first сервер аналітики поруч із сайтом, який уже працював. Жодної нової машини, жодного керованого сервісу, жодного банера про cookie. Це зайняло близько двадцяти хвилин, зараз він споживає 340 MB RAM, а його база даних виросла до 9.5 MB.
За ці одинадцять днів він зафіксував 96 pageviews, тоді як вебсервер перед ним залогував 2016 успішних відповідей зі сторінками.
Обидва числа справжні, обидва з однієї машини, і розрив між ними - найкорисніше, що є в цій статті. Почнімо з інсталяції, бо інсталяція - це легка частина.
Навіщо взагалі самохостити аналітику
Три причини, у порядку їхньої реальної ваги.
За сервер ви вже платите. Хостингова аналітика коштує щомісяця й дорожчає разом із кількістю pageviews, тож рахунок зростає саме тоді, коли сайт починає працювати. Контейнер поруч з наявним сайтом коштує стільки, скільки коштують 340 MB RAM, а на сервері за €5.49 це похибка округлення. Решту парку ми розрахували в нашому огляді Hetzner та в порівнянні VPS.
Жодного банера про cookie. Umami не ставить cookie й не будує міжсайтових профілів. Це різниця між сайтом, що відкривається контентом, і сайтом, що відкривається діалогом про згоду, а вона вартує більше за будь-яку метрику, яку цей діалог захищає.
Дані лишаються у вашому Postgres. Їх можна запитувати через SQL, бекапити разом з усім іншим і видаляти, коли захочете. Жодних квот на експорт, жодних тарифів за зберігання.
Чесний контраргумент: тепер ви обслуговуєте базу даних і застосунок, який за вас ніхто не патчить. Якщо ви не збираєтеся раз на місяць виконувати docker compose pull, беріть хостингову версію й не читайте далі.
Compose-файл для машини, на якій уже щось працює
Саме тут більшість гайдів помиляється, припускаючи порожню машину. Реалістичний випадок - це сервер, на якому вже є reverse proxy і сайт, а мета - додати два контейнери, не чіпаючи того, що працює.
services:
umami-db:
image: postgres:17-alpine
container_name: umami-db
restart: unless-stopped
env_file: [.env.umami]
volumes:
- umami_db:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U umami"]
interval: 10s
timeout: 5s
retries: 10
umami:
image: ghcr.io/umami-software/umami:postgresql-latest
container_name: umami
restart: unless-stopped
env_file: [.env.umami]
depends_on:
umami-db:
condition: service_healthy
volumes:
umami_db:
Тут по-справжньому працюють дві деталі.
Жоден контейнер не публікує порт. Вони спілкуються з reverse proxy через мережу compose і недоступні з інтернету інакше, як через нього. Якщо за звичкою додати ports: ["3000:3000"], ви щойно виставили адмінський логін з типовим паролем на публічний порт, і між вами та поганим вечором стоїть лише файрвол.
Healthcheck - не декорація. Umami виконує міграції під час першого запуску. Якщо стартувати його проти Postgres, який ще ініціалізується, перший запуск впаде з помилкою з’єднання, і ви будете налагоджувати проблему, що лікується перезапуском. condition: service_healthy прибирає цю гонитву.
Поруч лежить файл .env.umami:
POSTGRES_DB=umami
POSTGRES_USER=umami
POSTGRES_PASSWORD=<long random string>
DATABASE_URL=postgresql://umami:<same string>@umami-db:5432/umami
APP_SECRET=<another long random string>
DATABASE_URL - єдина змінна, яку застосунок справді вимагає. APP_SECRET підписує токени автентифікації, і документація прямо каже, що кожна інсталяція має мати власний. Згенеруйте обидва однаково й ніколи не використовуйте повторно:
openssl rand -hex 32
Тримайте цей файл із chmod 600. Пароль до бази лежить у ньому у двох місцях.
Підключення до Caddy
Якщо ви вже запускаєте Caddy так, як ми описували в налаштуванні Caddy і Docker, уся інтеграція - це п’ять рядків:
stats.yourdomain.dev {
encode zstd gzip
header {
X-Content-Type-Options nosniff
Referrer-Policy no-referrer
-Server
}
reverse_proxy umami:3000
}
Caddy резолвить umami через мережу compose й випускає сертифікат на перший запит. TLS-конфігурацію писати не треба, і це єдина причина використовувати його на малій машині.
Спрямуйте DNS-запис на сервер до перезавантаження, інакше запит на сертифікат провалиться, а Caddy на деякий час відступить.
Крок, який не можна відкладати
Під час першого запуску Umami створює типовий акаунт: користувач admin, пароль umami. Це задокументовано, а отже, його знає кожен сканер в інтернеті.
Увійдіть, змініть пароль, а тоді переконайтеся, що старого більше немає:
curl -s -o /dev/null -w '%{http_code}\n' \
-X POST https://stats.yourdomain.dev/api/auth/login \
-H 'Content-Type: application/json' \
-d '{"username":"admin","password":"umami"}'
Відповідь, яку ви хочете побачити, - 401. Будь-що інше означає, що зміна не спрацювала, і у вас публічна панель з опублікованим паролем.
Потім додайте свій сайт в інтерфейсі й вставте сніпет, який він видасть, у head вашого HTML:
<script defer src="https://stats.yourdomain.dev/script.js"
data-website-id="your-website-id"></script>
Website id не є секретом. Він лише вказує, якому сайту належить pageview, і більше нічого.
Як виглядають реальні цифри за одинадцять днів
Ось повна вартість цього рішення, виміряна на живій машині, а не оцінена:
| Метрика | Значення |
|---|---|
| Пам’ять контейнера Umami | 302 MB |
| Пам’ять контейнера Postgres | 37 MB |
| Розмір бази, 96 pageviews | 9.5 MB |
| CPU, обидва контейнери, сталий стан | 0.00% |
| Аптайм від інсталяції, перезапуски | 11 днів, 0 |
Ці 9.5 MB - це майже суцільно порожня схема. Postgres не росте лінійно від жменьки подій, тому не виводьте з цього числа вартість одного pageview. Для планування ресурсів важлива цифра пам’яті: закладайте приблизно 350 MB, і неприємних сюрпризів не буде.
Тепер неприємна частина
Та сама машина, ті самі одинадцять днів, два джерела правди, які різко розходяться.
Access log зафіксував 2016 успішних HTML-відповідей. Umami зафіксував 96 pageviews у 26 сесіях. Якщо сприймати це буквально, аналітика бачила 4.8% вашого трафіку, і треба панікувати.
Не панікуйте. Розберіть краще лог.
Фільтрація user agent зі словами bot, spider, crawl, curl, python, wget чи headless відсікає 1143 із цих 2016 запитів. Понад половина трафіку на новий сайт - це відверто заявлена автоматизація, і це ще до врахування тих, хто брешуть.
З 873 запитів, що лишилися, 196 - це GoogleOther, краулер Google, чий user agent взагалі не містить рядка bot. Якщо ваш аналіз логів шукає “bot”, ви мовчки рахували краулер як читача. Варто запам’ятати це наступного разу, коли дашборд на основі access log скаже вам щось лестиве.
У залишку все ще є незмарковані скрейпери. Наприклад, велика концентрація однієї-єдиної стародавньої збірки Safari - це відбиток, а не аудиторія.
Тож реальне порівняння - це 96 зафіксованих pageviews проти кількох сотень правдоподібно людських запитів, а розрив має три джерела, за спаданням розміру:
- Автоматизація, що не виконує JavaScript. Вона з’являється в логу й ніколи в аналітиці. Це більша частина розриву, і тут аналітика має рацію.
- Заблоковані трекери. Деякі списки фільтрів блокують за шаблоном імені хоста, а хост із назвою
stats.- це шаблон. Самохостинг не дає імунітету, він лише прибирає відомий сторонній домен. Проєкт описує, як проксіювати скрипт через власний домен і змінити endpoint збору черезCOLLECT_API_ENDPOINT, якщо потрібно закрити цей розрив. - Prefetch і читачі без JS. Мала, але реальна частка, яку не виправити.
Висновок не в тому, що “логи чесні, а аналітика бреше”. Вони вимірюють різні популяції: лог рахує запити, аналітика рахує браузери, що виконують скрипт. Лог нового сайту - переважно роботи, тому читання сирих логів змушує молодий сайт здаватися популярним і є найпростішим способом збрехати самому собі про тягу до нього.
Якщо вам потрібне одне число, за яким орієнтуватися перші місяці, беріть сесії з аналітики, а лог вважайте інструментом налагодження.
Згода, коротко й обережно
Аналітика без cookie уникає вимоги згоди, що стосується зберігання чи читання інформації на пристрої відвідувача, а саме через неї банер обов’язковий для звичайного трекінгу. Це не робить обробку невидимою: IP-адреса все одно потрапляє на ваш сервер, як і під час кожного запиту до кожного сайту.
Практичні наслідки, не юридична порада: напишіть на сторінці приватності, що ви збираєте, тримайте дані агрегованими й задайте IGNORE_IP для власних адрес, щоб ваш дашборд не складався переважно з вас. Якщо ваша ситуація регульована, питайте кваліфікованого фахівця, а не блог.
Обслуговування - це і є вся угода, яку ви підписали
docker compose pull umami umami-db
docker compose up -d umami umami-db
Це щомісячна робота. Міграції схеми запускаються автоматично під час старту контейнера, що зручно, і саме тому важливий том: umami_db - єдине, що не можна відтворити з цього файлу. Бекапте його разом з рештою даних і раз перевірте відновлення, інакше ви зберігаєте неперевірену копію на 9.5 MB і хибне відчуття безпеки.
Після першого тижня зробіть ще одне: відкрийте дашборд і порівняйте його з тим, що вам відомо напевно. Якщо ви самі заходили на сайт двадцять разів, а дашборд показує два, щось між тегом script і колектором блокується, і краще дізнатися це зараз, ніж за три місяці, коли ви ухвалюєте рішення за цим числом.