Shlink і Docker Compose: власний скорочувач URL на VPS
Розгорніть Shlink 5.1 на VPS через Docker Compose: короткий домен із HTTPS, Postgres, API key, web client, QR-коди та статистика переходів.
Що ви створюєте
Self-hosted сервіс скорочення URL — це невеликий сервер, який перетворює довге посилання на коротке посилання під вашим контролем і підраховує кожен перехід за ним. Варто вибрати Shlink: це open source продукт, він постачається як Docker image і виконує всі основні завдання в одному контейнері разом із базою даних. У цьому посібнику його розгорнуто на VPS за справжнім коротким доменом із HTTPS, API key, QR-кодами та статистикою переходів.
Два компоненти забезпечують функціональність комерційного сервісу скорочення URL. API server обробляє редиректи та зберігає дані. Web client — це окремий статичний застосунок, який звертається до цього API з браузера. Можна запустити обидва компоненти або використовувати лише API і керувати ним із командного рядка.
Наведені тут номери версій були актуальними станом на July 2026: Shlink 5.1 і shlink-web-client 4.8.
Спочатку прив’яжіть короткий домен до сервера
Домен — основа сервісу. s.example.com/abc123 — це посилання, яке бачать користувачі, тому виберіть коротке ім’я до встановлення будь-яких компонентів. Shlink зберігає домен у кожній короткій URL-адресі, і після його зміни всі вже поширені посилання перестануть працювати.
Створіть один DNS-запис типу A для короткого домену та вкажіть у ньому публічну IPv4-адресу вашого VPS. Якщо сервер має IPv6, додайте також запис AAAA. Перш ніж продовжити, перевірте, що домен розв’язується.
dig +short s.example.com AУ виведенні має бути адреса вашого сервера. Якщо виведення порожнє, запис ще не поширився в DNS. Усі наступні кроки завершаться незрозумілими помилками, оскільки сертифікат TLS (transport layer security) неможливо видати для імені, яке не розв’язується.
Файл compose
Shlink потребує базу даних. SQLite підходить для тестування, але для даних, які ви плануєте зберігати, правильним вибором буде Postgres, оскільки кількість рядків про відвідування зростає, а Postgres краще працює з індексами та паралельними записами. Додайте це в /opt/shlink/compose.yaml.
services:
shlink:
image: shlinkio/shlink:stable
restart: unless-stopped
ports:
- "127.0.0.1:8080:8080"
environment:
DEFAULT_DOMAIN: s.example.com
IS_HTTPS_ENABLED: "true"
DB_DRIVER: postgres
DB_HOST: database
DB_NAME: shlink
DB_USER: shlink
DB_PASSWORD: ${DB_PASSWORD}
depends_on:
- database
database:
image: postgres:17-alpine
restart: unless-stopped
environment:
POSTGRES_DB: shlink
POSTGRES_USER: shlink
POSTGRES_PASSWORD: ${DB_PASSWORD}
volumes:
- shlink_db:/var/lib/postgresql/data
web-client:
image: shlinkio/shlink-web-client:stable
restart: unless-stopped
ports:
- "127.0.0.1:8081:8080"
volumes:
shlink_db:Обидва опубліковані порти прив’язані до 127.0.0.1, тому з інтернету нічого не буде доступно, доки в наступному розділі не буде налаштовано reverse proxy. Docker додає власні правила перенаправлення перед правилами firewall хоста. Тому звичайний рядок 8080:8080 відкрив би доступ до застосунку навіть на сервері, де firewall виглядає закритим. Прив’язка до loopback-адреси запобігає цьому. Такий самий підхід застосовується до будь-якого застосунку, який ви запускаєте цим способом. Докладніше це описано в посібнику з Docker Compose на VPS.
Пароль бази даних береться з файлу .env поруч із compose-файлом, тому він не потрапляє до YAML.
sudo mkdir -p /opt/shlink
printf 'DB_PASSWORD=%s\n' "$(openssl rand -base64 24)" | sudo tee /opt/shlink/.env
sudo chmod 600 /opt/shlink/.envЗапустіть його та стежте за запуском API.
cd /opt/shlink
sudo docker compose up -d
sudo docker compose logs -f shlinkПід час першого запуску виконуються міграції бази даних, тому він триває довше, ніж наступні запуски. Після завершення перевірте, що сервіс відповідає локально.
curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:8080/rest/health200 означає, що API працює, а підключення до бази даних успішне. 500 тут майже завжди вказує на проблему з базою даних: DB_PASSWORD у .env не відповідає значенню, з яким було створено Postgres, оскільки образ Postgres читає POSTGRES_PASSWORD лише під час ініціалізації порожнього каталогу даних. Редагування пароля пізніше не матиме ефекту, доки ви не видалите volume і не запустите все знову.
Завершення HTTPS перед сервісом
Shlink працює зі звичайним HTTP на порту 8080. TLS має завершуватися на reverse proxy. Важливо лише передавати початкове ім’я хоста. Shlink визначає, якому домену належить короткий код, читаючи заголовок Host. Тому проксі, який змінює цей заголовок, повертає відповіді 404 для наявних посилань і прив’язує статистику переходів до неправильного домену.
server {
server_name s.example.com;
listen 80;
location / {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}Після цього отримайте сертифікат. Повний посібник, зокрема налаштування таймера поновлення, наведено в посібнику з Certbot для nginx в Ubuntu 24.04.
sudo certbot --nginx -d s.example.comПараметр IS_HTTPS_ENABLED: "true" у compose-файлі визначає, чи виводитиме Shlink https:// у коротких URL, які він повертає. Сам по собі цей параметр не вмикає TLS. Залиште false за HTTPS-проксі, і кожне посилання, яке повертає API, буде посиланням http://, що потім перенаправляє на потрібну адресу. Це створює зайвий мережевий обмін і має неправильний вигляд у вебклієнті.
Створення API key
Без key API не може приймати запити. Створіть його через CLI усередині контейнера.
sudo docker compose exec shlink shlink api-key:generate --name "web client"Команда виводить key лише один раз. Скопіюйте його зараз, оскільки він зберігається у вигляді хешу й більше не відображатиметься. shlink api-key:list показує назви та статус кожного key, але ніколи не показує сам key. Відкличте key за допомогою shlink api-key:disable і його назви.
Кожен REST-виклик передає key у заголовку X-Api-Key.
curl -H "X-Api-Key: YOUR_KEY" https://s.example.com/rest/v3/short-urlsJSON-об’єкт із key shortUrls означає, що key працює. Відповідь 401 зі значенням INVALID_API_KEY означає, що key неправильний, вимкнений або строк його дії минув.
Створюйте короткі посилання з командного рядка
CLI — найшвидший спосіб створювати посилання. Він також добре підходить для використання у скриптах.
sudo docker compose exec shlink shlink short-url:create https://example.com/a/very/long/path
sudo docker compose exec shlink shlink short-url:create https://example.com/docs --custom-slug docs --tag reference--custom-slug створює зрозуміле посилання замість згенерованого коду. Slug є унікальним у межах домену, тому повторна спроба використати зайнятий slug завершується помилкою, а не непомітним перезаписуванням першого посилання. --tag можна повторювати, а теги дають змогу групувати посилання, для яких згодом потрібно отримувати об’єднану статистику.
Спочатку перегляньте наявні записи, а потім перегляньте трафік одного посилання.
sudo docker compose exec shlink shlink short-url:list
sudo docker compose exec shlink shlink short-url:visits docsshort-url:visits виводить окремий рядок для кожного кліку із зазначенням дати, referrer і user agent. Стовпці country і city залишаються порожніми, якщо не задано змінну середовища GEOLITE_LICENSE_KEY. Це безкоштовний ключ MaxMind, який Shlink використовує для завантаження бази даних GeoLite2. Без нього відвідування все одно записуються, але їхнє географічне розташування не визначається.
Вебклієнт і QR-коди
Вебклієнт тепер доступний за адресою 127.0.0.1:8081 і потребує окремого запису проксі або SSH-тунелю, якщо ви не хочете публікувати його в мережі. Під час першого завантаження він запитує URL-адресу сервера та API key. Введіть https://s.example.com і згенерований вами key. Клієнт зберігає обидва значення в сховищі браузера та безпосередньо звертається до вашого API, тому дані не проходять через сторонні сервіси. Відокремлення інтерфейсу від API — вартий уваги підхід, оскільки саме він дає змогу Halcyon оформити бібліотеку Jellyfin як відеопрокат 1990-х без змін на медіасервері.
QR-коди взагалі не потребують налаштування. Додайте /qr-code до будь-якої короткої URL-адреси, і API поверне зображення.
https://s.example.com/docs/qr-code?size=500&format=svg&margin=20size — це ширина в пікселях. Допустимий діапазон — від 50 до 1000, значення за замовчуванням — 300. format — це png або svg. margin — це вільний простір навколо коду в пікселях. Розмір готового зображення дорівнює розміру коду плюс подвоєний розмір поля. Додайте errorCorrection=Q, щоб код можна було сканувати навіть у разі друку в малому розмірі або часткового перекриття.
Підтримуйте сервіс у робочому стані
Сервіс скорочення URL може непомітно відмовити. Посилання перестають перенаправляти, але ніхто не повідомляє про проблему, бо користувач, який натиснув посилання, вирішив, що воно більше не працює. Налаштуйте перевірку доступності для реального короткого URL, а не для головної сторінки, і створіть сповіщення про будь-яку відповідь, яка не є перенаправленням. Self-hosted екземпляр Uptime Kuma добре підходить для цього та може перевіряти конкретний код стану.
Створюйте резервну копію бази даних, а не контейнера. Для цього достатньо однієї команди.
sudo docker compose exec -T database pg_dump -U shlink shlink | gzip > shlink-$(date +%F).sql.gzЦей файл разом із compose-файлом дає змогу відновити весь сервіс на новому сервері. Для кожного застосунку на сервері потрібна власна версія цієї пари файлів. Фототека є складнішим випадком, оскільки PhotoPrism і Immich зберігають оригінали на диску, а також записи в базі даних. Тому одного дампа недостатньо для відновлення. Оновлення виконуються через sudo docker compose pull, а потім через sudo docker compose up -d, і Shlink запускає всі нові міграції під час старту. Створіть дамп перед виконанням pull, оскільки міграцію неможливо скасувати.
FAQ
Чому мої короткі посилання повертають 404 після додавання reverse proxy?
Shlink зіставляє короткий код із доменом у заголовку Host. Якщо проксі передає власне ім’я або внутрішню адресу, Shlink шукає цей код у домені, для якого немає посилань, і повертає 404. Установіть proxy_set_header Host $host; у блоці location nginx і перезавантажте проксі. Посилання одразу почнуть працювати, без перезапуску контейнера.
Чи потрібен Postgres, чи достатньо SQLite?
SQLite підходить для тестування Shlink і не потребує другого контейнера. Перейдіть на Postgres до публікації важливих посилань, оскільки кількість записів про відвідування зростає з кожним переходом, а SQLite серіалізує операції запису. Подальше переключення потребує експорту та повторного імпорту посилань, тому вибір Postgres на початку позбавляє цієї міграції.
Чи можна відновити API key, який я забув скопіювати?
Ні. Shlink зберігає hash ключа, тому api-key:list показує імена та статус, але ніколи не показує саме значення. Створіть заміну за допомогою shlink api-key:generate, вставте її у web client, а потім вимкніть старий ключ за допомогою shlink api-key:disable, щоб він більше не працював.
Чому стовпці з країнами порожні у статистиці відвідувань?
Для геолокації потрібна база даних GeoLite2. Shlink завантажує її лише після передавання GEOLITE_LICENSE_KEY. Ключ можна безкоштовно отримати в MaxMind. Додайте його до секції середовища, пересоздайте контейнер, і нові відвідування отримуватимуть дані геолокації. Відвідування, записані раніше, залишаться без цих даних, доки ви не виконаєте shlink visit:locate.
Як перенести Shlink на інший сервер?
Збережіть домен і перенесіть дані. Створіть дамп бази даних за допомогою pg_dump, скопіюйте дамп і compose file на новий сервер, запустіть stack, а потім відновіть дамп у порожню базу даних до надходження реального трафіку. Змініть DNS-запис в останню чергу. Короткі коди та історія їхніх відвідувань збережуться, оскільки всі дані містяться в базі даних.