The Messy Reality of Long URLs
We’ve all dealt with the “URL wall of text.” You try to share a specific documentation page or a deep-linked product, and the link is suddenly 300 characters long. It’s usually stuffed with UTM parameters, session IDs, and tracking gibberish that makes it look like a phishing attempt. In a professional setting, sending these to clients or team members looks unpolished and cluttered.
Most people naturally reach for Bitly or TinyURL. They work, but the landscape has changed. Bitly’s free tier is now incredibly restrictive, recently slashing its limit to just 10 links per month. If you want basic click analytics or a custom domain, you’re looking at $8 to $29 per month. More importantly, using a third-party service means handing over your traffic data to a middleman. For HomeLab enthusiasts or privacy-conscious DevOps teams, that’s a dealbreaker.
Why Third-Party Shorteners Fail DevOps Teams
The shift toward self-hosting isn’t just about saving $100 a year; it’s about data sovereignty and professional branding. When you use bit.ly/xyz, you are marketing Bitly. When you use links.yourdomain.com/xyz, you are building your own brand authority.
Security is another factor. Third-party shorteners are frequently blacklisted by corporate firewalls and aggressive email filters because phishers love them. Hosting your own instance on a clean domain significantly improves your email deliverability. You also gain total visibility. You can see exactly who clicked a link, their originating IP, and their browser version without paying for a “Premium” analytics tier. This is gold for debugging webhooks or auditing security.
Evaluating the Landscape: YOURLS vs. Shlink
If you’ve looked into self-hosting before, you likely found YOURLS. It’s a classic, but it shows its age. Built on a traditional PHP stack, it can be clunky to modernize, and its interface feels like the 2010s.
Shlink is the modern successor. It is a PHP-based (Laminas) microservice designed from day one to be container-native. It uses a “headless” architecture, meaning the engine and the web interface are separate. This is a massive win for DevOps workflows. You can manage everything via a REST API, a CLI tool, or the official Shlink Web Client. It’s flexible, fast, and stays out of your way.
Why Shlink Wins for Modern HomeLabs
Shlink’s killer feature is multi-domain support on a single instance. You can have dev.link for internal tools and brand.link for public marketing both pointing to one Docker container. It handles them separately without missing a beat. It also generates QR codes automatically and provides detailed Geolocation tracking via MaxMind integration.
Mastering this setup is a great exercise. It teaches you how to decouple backend services from frontends while managing persistent database storage in a containerized environment.
The Architecture: Server vs. Client
Before pulling images, understand that Shlink is split into two distinct parts:
- Shlink Server: The core engine. It handles the redirects and stores data in MariaDB, PostgreSQL, or SQLite.
- Shlink Web Client: A React-based dashboard. It communicates with the Server via API. It doesn’t even need to be on the same network, though we’ll run them together for simplicity.
Deploying Shlink with Docker Compose
I recommend MariaDB for the backend. It’s lightweight, reliable, and handles the 100MB to 500MB database size of a typical shortener with ease.
Create a project directory and drop this into a docker-compose.yml file:
version: '3.8'
services:
shlink-db:
image: mariadb:10.11
container_name: shlink-db
restart: always
environment:
- MARIADB_ROOT_PASSWORD=your_root_password
- MARIADB_DATABASE=shlink
- MARIADB_USER=shlink
- MARIADB_PASSWORD=shlink_pass
volumes:
- shlink_db_data:/var/lib/mysql
shlink-server:
image: shlinkio/shlink:stable
container_name: shlink-server
restart: always
depends_on:
- shlink-db
environment:
- DB_DRIVER=mariadb
- DB_USER=shlink
- DB_PASSWORD=shlink_pass
- DB_NAME=shlink
- DB_HOST=shlink-db
- DEFAULT_DOMAIN=ln.yourdomain.com
- IS_HTTPS_ENABLED=true
ports:
- "8080:8080"
shlink-web-client:
image: shlinkio/shlink-web-client:stable
container_name: shlink-web-client
restart: always
ports:
- "8181:80"
volumes:
shlink_db_data:
Launch the stack with docker-compose up -d. Your engine is running, but it’s locked down. Shlink doesn’t use a default admin password; it uses API keys.
Generating Your API Key
To get inside, you need to generate a key through the container’s CLI. This is a secure, modern pattern that avoids hardcoded credentials. Run this command:
docker exec -it shlink-server shlink api-key:generate
Save that string. You’ll need it in the next step.
Configuring the Shlink Web Client
Open http://your-server-ip:8181 in your browser. Click “Add a server.” A common point of confusion here is the “Server URL.” Since the Web Client runs in your local browser, it needs a URL your computer can actually reach.
- Name: My Shortener
- URL: http://your-server-ip:8080
- API Key: (The string you just generated)
Hit connect. You’re ready to create links. Try shortening a long URL and watch the analytics dashboard update in real-time.
Handling Reverse Proxies and HTTPS
Production environments shouldn’t use raw IPs and ports. Use a reverse proxy like Nginx Proxy Manager or Traefik. Map ln.yourdomain.com to the shlink-server container on port 8080.
If you use HTTPS—and you should—ensure the IS_HTTPS_ENABLED variable is set to true. This forces Shlink to generate https:// links instead of insecure http:// ones.
Advanced Tracking and Custom Slugs
The analytics depth is where Shlink really shines. By adding a free MaxMind license key, Shlink downloads the GeoLite2 database automatically. This gives you city-level tracking for every click.
Readability matters too. Instead of a random string like /a7G2k, you can create custom slugs like /portfolio or /meeting. These are perfect for physical business cards or Instagram bios where you want a clean, memorable aesthetic.
Final Thoughts on Infrastructure Control
This setup provides more than just short links. It creates a centralized hub for your outgoing traffic. If you ever switch domains, just update the DEFAULT_DOMAIN variable. Your history moves with you. You are no longer held hostage by a third-party’s pricing whims.
Docker’s real strength here is portability. Your entire link history is tucked away in that MariaDB volume. As long as you back up that data, your shortener can migrate across servers or cloud providers for years. This is the core of a great HomeLab: taking everyday services and making them private, permanent, and entirely yours.

