Własny skracacz linków z Shlink i Docker Compose
Instalacja Shlink na własnym VPS krok po kroku. Konfiguracja kontenerów Docker, bazy Postgres, certyfikatów SSL oraz obsługa API, statystyk i kodów QR dla krótkich domen.
Co budujesz
Samodzielnie hostowany skracacz adresów URL to niewielki serwer, który zamienia długi link w krótki, należący do Ciebie, a dodatkowo zlicza każde kliknięcie. Shlink jest najlepszym wyborem: jest oprogramowaniem open source, jest dostarczany jako obraz Docker i wykonuje całe zadanie w jednym kontenerze oraz bazie danych. Niniejszy przewodnik opisuje instalację na VPS za własną, krótką domeną, z obsługą HTTPS, kluczem API, kodami QR i statystykami kliknięć.
Dwa elementy sprawiają, że całość działa jak komercyjny skracacz. Serwer API odpowiada za przekierowania i przechowuje dane. Klient webowy to oddzielna aplikacja statyczna, która komunikuje się z tym API z poziomu przeglądarki. Można uruchomić oba komponenty lub tylko samo API i zarządzać nim z poziomu wiersza poleceń.
Podane tutaj numery wersji były aktualne na lipiec 2026: Shlink 5.1 oraz shlink-web-client 4.8.
Skieruj krótką domenę na serwer w pierwszej kolejności
Domena jest produktem. s.example.com/abc123 to link, który widzą użytkownicy, dlatego należy wybrać krótką nazwę przed rozpoczęciem instalacji. Shlink zapisuje domenę przy każdym krótkim adresie URL, a jej późniejsza zmiana spowoduje, że wszystkie udostępnione wcześniej linki przestaną działać.
Utwórz jeden rekord DNS typu A dla krótkiej domeny, wskazujący na publiczny adres IPv4 Twojego VPS. Dodaj również rekord AAAA, jeśli serwer posiada adres IPv6. Następnie potwierdź, że domena się rozwiązuje, zanim przejdziesz dalej.
dig +short s.example.com AWynikiem polecenia powinien być adres Twojego serwera. Jeśli wynik jest pusty, rekord nie został jeszcze rozpropagowany, a każdy kolejny krok zakończy się niepowodzeniem w niejasny sposób, ponieważ certyfikat TLS (transport layer security) nie może zostać wystawiony dla nazwy, która się nie rozwiązuje.
Plik compose
Shlink wymaga bazy danych. SQLite sprawdza się w testach, jednak dla trwałych rozwiązań właściwym wyborem jest Postgres. Liczba rekordów wizyt szybko rośnie, a Postgres lepiej zarządza indeksami i współbieżnymi zapisami. Umieść poniższą treść w /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:Oba opublikowane porty są powiązane z 127.0.0.1, więc żadna usługa nie jest dostępna z Internetu, dopóki nie zostanie skonfigurowany reverse proxy opisany w następnej sekcji. Docker tworzy własne reguły przekierowania z pominięciem firewalla hosta, co oznacza, że zwykła linia 8080:8080 wystawiłaby aplikację na świat nawet na serwerze z pozornie zamkniętym firewallem. Powiązanie z adresem loopback zapobiega temu zjawisku. Ten sam schemat dotyczy każdej aplikacji uruchamianej w ten sposób, co zostało szczegółowo omówione w przewodniku po Docker Compose na VPS.
Hasło do bazy danych jest pobierane z pliku .env znajdującego się obok pliku compose, dzięki czemu nie trafia ono do pliku 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/.envUruchom usługę i obserwuj inicjalizację API.
cd /opt/shlink
sudo docker compose up -d
sudo docker compose logs -f shlinkPierwsze uruchomienie obejmuje migracje bazy danych, dlatego trwa dłużej niż kolejne. Po ustabilizowaniu się procesu sprawdź, czy usługa odpowiada lokalnie.
curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:8080/rest/healthKod 200 oznacza, że API działa, a połączenie z bazą danych jest poprawne. Błąd 500 w tym miejscu niemal zawsze wynika z problemów z bazą danych: wartość DB_PASSWORD w .env nie zgadza się z danymi, z którymi zainicjalizowano Postgres. Obraz Postgres odczytuje POSTGRES_PASSWORD tylko podczas tworzenia pustego katalogu danych. Późniejsza edycja hasła nie przynosi efektu, dopóki wolumen nie zostanie usunięty, a proces uruchomiony ponownie.
Terminacja HTTPS przed usługą
Shlink obsługuje zwykły protokół HTTP na porcie 8080. Obsługa TLS powinna odbywać się w reverse proxy, a kluczowym ustawieniem jest przekazywanie oryginalnej nazwy hosta. Shlink określa domenę, do której należy krótki kod, odczytując nagłówek Host, dlatego proxy zmieniające ten nagłówek spowoduje błędy 404 dla istniejących linków oraz przypisanie statystyk odwiedzin do niewłaściwej domeny.
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;
}
}Następnie należy wygenerować certyfikat. Pełna procedura, w tym harmonogram odnawiania, znajduje się w przewodniku Certbot dla nginx na systemie Ubuntu 24.04.
sudo certbot --nginx -d s.example.comZmienna IS_HTTPS_ENABLED: "true" w pliku compose odpowiada za to, aby Shlink generował adresy https:// w zwracanych krótkich linkach. Sama w sobie nie włącza ona obsługi TLS. Należy pozostawić ją jako false za proxy HTTPS, w przeciwnym razie każdy link zwrócony przez API będzie linkiem http://, który wymaga dodatkowego przekierowania, co zwiększa opóźnienie i powoduje błędy wyświetlania w interfejsie webowym.
Tworzenie klucza API
Żadna usługa nie nawiąże połączenia z API bez klucza. Należy wygenerować go za pomocą CLI wewnątrz kontenera.
sudo docker compose exec shlink shlink api-key:generate --name "web client"Polecenie wyświetla klucz tylko raz. Należy go skopiować natychmiast, ponieważ w bazie danych przechowywany jest w formie skrótu i nie będzie można go ponownie odczytać. shlink api-key:list wyświetla nazwy kluczy oraz informacje o ich aktywności, lecz nigdy nie pokazuje samego klucza. Unieważnienie klucza odbywa się za pomocą shlink api-key:disable oraz nazwy klucza.
Każde wywołanie REST musi zawierać klucz w nagłówku X-Api-Key.
curl -H "X-Api-Key: YOUR_KEY" https://s.example.com/rest/v3/short-urlsObiekt JSON z kluczem shortUrls oznacza, że klucz jest poprawny. Kod 401 z komunikatem INVALID_API_KEY oznacza, że klucz jest nieprawidłowy, wyłączony lub wygasł.
Tworzenie krótkich linków z poziomu wiersza poleceń
Interfejs CLI jest najszybszym sposobem tworzenia linków i najlepiej sprawdza się w skryptach.
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 pozwala uzyskać czytelny link zamiast wygenerowanego kodu. Slugi są unikalne w obrębie domeny, więc kolejna próba użycia zajętego sluga zakończy się niepowodzeniem, zamiast cichego nadpisania istniejącego linku. --tag można powtarzać, a tagi służą do grupowania linków, dla których w przyszłości wymagane będą łączne statystyki.
Wyświetl listę istniejących linków, a następnie sprawdź ruch dla wybranego z nich.
sudo docker compose exec shlink shlink short-url:list
sudo docker compose exec shlink shlink short-url:visits docsshort-url:visits wyświetla jeden wiersz na każde kliknięcie, zawierający datę, adres odsyłający (referrer) oraz user agent. Kolumny kraju i miasta pozostają puste, dopóki nie zostanie ustawiona zmienna środowiskowa GEOLITE_LICENSE_KEY, która jest darmowym kluczem MaxMind używanym przez Shlink do pobrania bazy danych GeoLite2. Bez tego odwiedziny są nadal rejestrowane, ale nie są lokalizowane geograficznie.
Klient webowy i kody QR
Klient webowy znajduje się teraz pod adresem 127.0.0.1:8081 i wymaga własnego wpisu w proxy lub tunelu SSH, jeśli nie chcesz udostępniać go publicznie. Przy pierwszym uruchomieniu wymaga podania adresu URL serwera oraz klucza API. Wprowadź https://s.example.com oraz wygenerowany wcześniej klucz. Klient przechowuje oba te elementy w pamięci przeglądarki i wywołuje API bezpośrednio, dzięki czemu żadne dane nie przechodzą przez podmioty trzecie. Rozdzielenie interfejsu od API to wzorzec warty uwagi, ponieważ jest to ten sam mechanizm, który pozwala Halcyon na prezentację biblioteki Jellyfin w formie wypożyczalni kaset z lat 90. bez modyfikowania samego serwera multimediów.
Kody QR nie wymagają żadnej konfiguracji. Dodaj /qr-code do dowolnego krótkiego adresu URL, a API zwróci obraz.
https://s.example.com/docs/qr-code?size=500&format=svg&margin=20size określa szerokość w pikselach i przyjmuje wartości od 50 do 1000, przy czym wartością domyślną jest 300. format to png lub svg. margin to margines wokół kodu w pikselach; gotowy obraz ma wymiar równy szerokości plus dwukrotność marginesu. Dodaj errorCorrection=Q dla kodu, który pozostaje czytelny nawet po wydrukowaniu w małym rozmiarze lub częściowym zasłonięciu.
Utrzymanie ciągłości działania
Skracacz linków może ulec awarii w sposób niezauważalny. Linki przestają przekierowywać, a administrator nie otrzymuje powiadomienia, ponieważ użytkownik zakłada, że link jest po prostu nieaktywny. Należy skonfigurować monitorowanie dostępności (uptime check) dla rzeczywistego krótkiego adresu URL, zamiast dla strony głównej, oraz ustawić alerty dla każdego wyniku innego niż przekierowanie. Samodzielnie hostowana instancja Uptime Kuma sprawdza się w tym zadaniu dobrze i pozwala monitorować konkretny kod statusu HTTP.
Należy tworzyć kopie zapasowe bazy danych, a nie kontenera. Jedno polecenie wykonuje zrzut danych.
sudo docker compose exec -T database pg_dump -U shlink shlink | gzip > shlink-$(date +%F).sql.gzTen plik wraz z plikiem compose pozwala na odtworzenie całej usługi na nowym serwerze. Każda aplikacja na serwerze wymaga własnej wersji tej pary plików. Biblioteki zdjęć stanowią wyjątek, ponieważ PhotoPrism oraz Immich przechowują oryginały plików na dysku, a nie tylko wiersze w bazie danych, więc sam zrzut bazy nie wystarczy do przywrócenia systemu. Aktualizacje wykonuje się poprzez sudo docker compose pull, a następnie sudo docker compose up -d, przy czym Shlink automatycznie uruchamia wszelkie nowe migracje przy starcie. Zrzut bazy należy wykonać przed pobraniem nowej wersji obrazu, ponieważ migracji nie można cofnąć.
FAQ
Dlaczego moje krótkie linki zwracają błąd 404 po dodaniu reverse proxy?
Shlink dopasowuje krótki kod do domeny w nagłówku Host. Proxy, które przesyła własną nazwę lub adres wewnętrzny, powoduje, że Shlink szuka kodu w domenie, która nie posiada żadnych linków, więc zwraca błąd 404. Ustaw proxy_set_header Host $host; w bloku location serwera nginx i przeładuj konfigurację proxy. Linki zaczną działać natychmiast, bez konieczności restartu kontenera.
Czy potrzebuję Postgres, czy wystarczy SQLite?
SQLite jest wystarczający do testowania Shlink i nie wymaga drugiego kontenera. Przejdź na Postgres przed publikacją ważnych linków, ponieważ wiersze z wizytami rosną z każdym kliknięciem, a SQLite szereguje zapisy. Późniejsza zmiana oznacza konieczność eksportu i ponownego importu linków, więc wybór Postgres na początku pozwala uniknąć tej migracji.
Czy mogę odzyskać klucz API, którego zapomniałem skopiować?
Nie. Shlink przechowuje skrót klucza, więc api-key:list wyświetla nazwy i statusy, ale nigdy samej wartości. Wygeneruj nowy klucz za pomocą shlink api-key:generate, wklej go do klienta webowego, a następnie wyłącz stary za pomocą shlink api-key:disable, aby przestał działać.
Dlaczego kolumny z krajami są puste w moich statystykach wizyt?
Geolokalizacja wymaga bazy danych GeoLite2, którą Shlink pobiera tylko po podaniu klucza GEOLITE_LICENSE_KEY. Klucz jest darmowy i dostępny w serwisie MaxMind. Dodaj go do sekcji environment, utwórz ponownie kontener, a nowe wizyty będą lokalizowane. Wizyty zarejestrowane wcześniej pozostaną puste, dopóki nie uruchomisz shlink visit:locate.
Jak przenieść Shlink na inny serwer?
Zachowaj domenę i przenieś dane. Wykonaj zrzut bazy danych za pomocą pg_dump, skopiuj zrzut oraz plik compose na nowy serwer, uruchom stos, a następnie przywróć zrzut do pustej bazy danych, zanim dotrze do niej rzeczywisty ruch. Zmień rekord DNS na samym końcu. Krótkie kody oraz historia wizyt zostaną zachowane, ponieważ wszystkie dane znajdują się w bazie.