SSD Nodes Learn 8GB RAM — $66/taon
Mga Gabay Matt ConnorNi Matt Connor · Na-update 2026-08-02

Self-hosted URL shortener gamit ang Shlink at Docker

Magpatakbo ng sariling URL shortener sa VPS gamit ang Shlink 5.1 at Docker Compose, mula short domain DNS at Postgres hanggang API keys, QR codes at click stats.

Ang ginagawa mo

Ang self-hosted URL shortener ay isang maliit na server na nagko-convert ng mahabang link sa maikling link na pagmamay-ari mo, at binibilang ang bawat click dito. Shlink ang dapat piliin: open source ito, inilalabas bilang Docker image, at ginagawa nito ang buong trabaho sa isang container kasama ang isang database. Inilalagay ng gabay na ito ang Shlink sa isang VPS sa likod ng aktuwal na short domain, na may HTTPS, API key, QR codes at click stats.

Dalawang bahagi ang nagpapagana rito na parang commercial shortener. Sumasagot ang API server sa mga redirect at nag-iimbak ng data. Hiwalay na static app ang web client na kumokonekta sa API mula sa browser mo. Maaari mong patakbuhin ang dalawa, o API lang at kontrolin ito mula sa command line.

Ang mga version number dito ang kasalukuyan noong July 2026: Shlink 5.1 at shlink-web-client 4.8.

Ituro muna ang maikling domain sa server

Ang domain ang mismong produkto. s.example.com/abc123 ang link na nakikita ng mga tao, kaya pumili ng maikli at itakda ito bago mag-install ng anuman. Iniimbak ng Shlink ang domain kasama ng bawat short URL, at kapag binago mo ito sa kalaunan, hindi na gagana ang lahat ng link na naipamahagi mo na.

Gumawa ng isang DNS A record para sa maikling domain, at ituro ito sa public IPv4 address ng iyong VPS. Magdagdag din ng AAAA record kung may IPv6 ang server. Pagkatapos, kumpirmahing nareresolba ito bago magpatuloy.

dig +short s.example.com A

Ang output ay dapat na address ng iyong server. Kung walang output, hindi pa propagated ang record, at mabibigo ang lahat ng susunod na hakbang sa hindi malinaw na paraan, dahil hindi maaaring mag-isyu ng TLS (transport layer security) certificate para sa pangalan na hindi nareresolba.

Ang compose file

Nangangailangan ang Shlink ng database. Gumagana ang SQLite para sa pagsubok, pero Postgres ang tamang piliin para sa anumang balak mong panatilihin dahil naiipon ang mga row ng pagbisita, at mas mahusay pang pinangangasiwaan ng Postgres ang mga index at sabay-sabay na pagsusulat. Ilagay ito sa /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:

Naka-bind ang parehong published port sa 127.0.0.1, kaya walang maaabot mula sa internet hanggang hindi naikakabit ang reverse proxy sa susunod na seksyon. Inuuna ng Docker ang sarili nitong forwarding rules bago ang host firewall, kaya ilalantad ng simpleng 8080:8080 line ang app kahit mukhang sarado ang firewall ng server. Maiiwasan ito sa pag-bind sa loopback address. Nalalapat ang parehong pattern sa anumang app na patatakbuhin mo sa ganitong paraan, at mas detalyado itong ipinaliwanag sa gabay sa Docker Compose sa isang VPS.

Kinukuha ang database password mula sa .env file na nasa tabi ng compose file, kaya hindi ito napupunta sa 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

Simulan ito at i-monitor ang pag-angat ng API.

cd /opt/shlink
sudo docker compose up -d
sudo docker compose logs -f shlink

Isinasagawa ng unang start ang database migrations, kaya mas matagal ito kaysa sa mga susunod na start. Kapag naging stable na ito, tingnan kung tumutugon nang lokal ang service.

curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:8080/rest/health

Nangangahulugang buhay ang API at gumagana ang koneksyon sa database ang 200. Ang 500 dito ay halos palaging database ang sanhi: hindi tugma ang DB_PASSWORD sa .env sa ginawa ng Postgres, dahil binabasa ng Postgres image ang POSTGRES_PASSWORD kapag nag-i-initialize lamang ito ng walang laman na data directory. Walang epekto ang pag-edit ng password sa kalaunan hangga't hindi mo inaalis ang volume at muling sinisimulan ang service.

I-terminate ang HTTPS sa harap nito

Naghahatid ang Shlink ng plain HTTP sa port 8080. Dapat nasa reverse proxy ang TLS, at ang pinakamahalagang setting ay ang pagpapasa ng orihinal na host name. Tinutukoy ng Shlink kung saang domain kabilang ang isang short code sa pamamagitan ng pagbasa sa Host header, kaya ang proxy na nagre-rewrite nito ay nagdudulot ng 404 response sa mga link na umiiral, pati na rin ng mga visit stat na nakakabit sa maling domain.

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;
    }
}

Pagkatapos, i-issue ang certificate. Nasa guide ng Certbot para sa nginx sa Ubuntu 24.04 ang kumpletong walkthrough, kasama ang renewal timer.

sudo certbot --nginx -d s.example.com

Ang IS_HTTPS_ENABLED: "true" sa compose file ang nagtatakda kung ano ang ipi-print ng Shlink na https:// sa mga short URL na ibinabalik nito. Hindi nito awtomatikong ine-enable ang TLS. Iwan itong false sa likod ng HTTPS proxy, at bawat link na ibinabalik ng API ay magiging http:// link na magre-redirect pa, kaya nagkakaroon ng dagdag na round trip at mukhang mali sa web client.

Gumawa ng API key

Walang makakagamit sa API nang walang key. Bumuo ng isa gamit ang CLI sa loob ng container.

sudo docker compose exec shlink shlink api-key:generate --name "web client"

Isang beses lang ipinapakita ng command ang key. Kopyahin ito ngayon dahil naka-store ito bilang hash at hindi na muling maipapakita. Ipinapakita ng shlink api-key:list ang mga pangalan at kung naka-enable ang bawat key, pero hindi kailanman ang mismong key. I-revoke ang isang key gamit ang shlink api-key:disable at ang pangalan nito.

Dala ng bawat REST call ang key sa isang X-Api-Key header.

curl -H "X-Api-Key: YOUR_KEY" https://s.example.com/rest/v3/short-urls

Ang JSON object na may shortUrls key ay nangangahulugang gumagana ang key. Ang 401 na may INVALID_API_KEY ay nangangahulugang mali o naka-disable ang key, o lumampas na ito sa expiration date.

Ang CLI ang pinakamabilis na paraan para gumawa ng mga link, at mahusay itong gamitin sa mga script.

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

Ang --custom-slug ay nagbibigay ng nababasang link sa halip na awtomatikong nabuong code. Natatangi ang mga slug sa bawat domain, kaya mabibigo ang ikalawang pagtatangka sa slug na ginagamit na sa halip na tahimik na ma-overwrite ang unang link. Maaaring ulitin ang --tag, at ginagamit ang mga tag para pagsama-samahin ang mga link na kakailanganin mo ng pinagsama-samang statistics sa hinaharap.

Ilista muna ang mga umiiral, pagkatapos ay tingnan ang traffic ng isang link.

sudo docker compose exec shlink shlink short-url:list
sudo docker compose exec shlink shlink short-url:visits docs

Nagpi-print ang short-url:visits ng isang row para sa bawat click, kasama ang petsa, referrer at user agent. Mananatiling walang laman ang mga column para sa bansa at lungsod maliban kung magtakda ka ng GEOLITE_LICENSE_KEY environment variable. Isa itong libreng MaxMind key na ginagamit ng Shlink para i-download ang GeoLite2 database. Kung wala ito, itatala pa rin ang mga pagbisita, pero hindi matutukoy ang lokasyon ng mga ito.

Ang web client at mga QR code

Ang web client ay nasa 127.0.0.1:8081 na at nangangailangan ng sarili nitong proxy entry, o SSH tunnel kung ayaw mo itong i-publish. Hinihingi nito ang server URL at API key sa unang pag-load. Ilagay ang https://s.example.com at ang key na ginawa mo. Pinapanatili ng client ang dalawang ito sa browser storage at direktang tinatawagan ang iyong API, kaya walang data na dumadaan sa ibang server.

Walang kailangang configuration ang mga QR code. Idagdag ang /qr-code sa anumang short URL at ibabalik ng API ang image.

https://s.example.com/docs/qr-code?size=500&format=svg&margin=20

Ang size ay ang lapad sa pixels at tumatanggap ng 50 hanggang 1000, na may default na 300. Ang format ay png o svg. Ang margin ay ang bakanteng espasyo sa paligid ng code, sa pixels, at ang kabuuang image ay may sukat na size dagdag ang dalawang margin. Idagdag ang errorCorrection=Q para sa code na mababasa pa rin kapag maliit ang pagkaka-print o bahagyang natatakpan.

Panatilihing gumagana

Tahimik na pumapalya ang shortener. Hihinto ang pag-redirect ng mga link at walang magsasabi sa iyo, dahil ipagpapalagay ng taong nag-click na patay na ang link. Ituro ang uptime check sa aktuwal na short URL, hindi sa home page, at magpadala ng alert kapag hindi redirect ang resulta. Mahusay itong ginagawa ng isang self-hosted na Uptime Kuma instance, at maaari itong mag-monitor ng partikular na status code.

I-back up ang database, hindi ang container. Isang command lang ang kailangan para i-dump ito.

sudo docker compose exec -T database pg_dump -U shlink shlink | gzip > shlink-$(date +%F).sql.gz

Ang file na iyon at ang compose file mo ang makakabuo muli sa buong service sa isang bagong server. Ang mga upgrade ay sudo docker compose pull na sinusundan ng sudo docker compose up -d, at pinapatakbo ng Shlink ang anumang bagong migration sa pagsisimula. Kunin ang dump bago ka mag-pull, dahil hindi maaaring i-rollback ang migration.

FAQ

Itinutugma ng Shlink ang short code sa domain sa Host header. Kapag sariling pangalan o internal address ng proxy ang ipinadala nito, hahanapin ng Shlink ang code sa domain na walang mga link, kaya 404 ang ibinabalik nito. Itakda ang proxy_set_header Host $host; sa nginx location block at i-reload ang proxy. Agad na gagana ang mga link, nang hindi nire-restart ang container.

Kailangan ko ba ng Postgres, o sapat na ang SQLite?

Angkop ang SQLite para subukan ang Shlink at hindi nito kailangan ng pangalawang container. Lumipat sa Postgres bago mo i-publish ang mahahalagang link, dahil dumarami ang visit row sa bawat click at sini-serialize ng SQLite ang mga write. Ang paglipat sa ibang pagkakataon ay nangangailangan ng pag-export at pag-import muli ng iyong mga link, kaya makatitipid ka sa migration kung Postgres ang pipiliin mo sa simula.

Maaari ko bang mabawi ang API key na nakalimutan kong kopyahin?

Hindi. Hash ng key ang iniimbak ng Shlink, kaya ipinapakita lamang ng api-key:list ang mga pangalan at status, hindi ang value. Gumawa ng kapalit gamit ang shlink api-key:generate, i-paste ito sa web client, pagkatapos ay i-disable ang luma gamit ang shlink api-key:disable upang hindi na ito gumana.

Bakit walang laman ang mga country column sa aking visit stats?

Kailangan ng geolocation ang GeoLite2 database, na dina-download lamang ng Shlink kapag binigyan mo ito ng GEOLITE_LICENSE_KEY. Libre ang key mula sa MaxMind. Idagdag ito sa environment section, likhain muli ang container, at malalagyan ng lokasyon ang mga bagong visit. Mananatiling blangko ang mga visit na naitala bago nito hanggang patakbuhin mo ang shlink visit:locate.

Panatilihin ang domain at ilipat ang data. I-dump ang database gamit ang pg_dump, kopyahin ang dump at compose file sa bagong server, simulan ang stack, pagkatapos ay i-restore ang dump sa walang-lamang database bago dumating ang aktuwal na traffic. Huling baguhin ang DNS record. Mananatili ang mga short code at ang history ng kanilang visit dahil nasa database ang lahat.

#shlink#url-shortener#self-hosting#docker#postgres