用 Shlink + Docker 自建一个 URL 短链服务
Shlink 是 PHP 圈事实上的自托管 URL 短链服务 — MIT 协议,由 Alejandro Celaya
(acelaya)持续维护,官方打包了开箱即用的 Docker 镜像,把 99% 的运维工作
都消化掉了。本文一步步带你搭起一套 2026 年可用的 Docker Compose 栈,
十分钟左右就能跑起来,自带 Web UI、GeoLite2 地理位置、HTTPS。
TL;DR. 起一个
shlinkio/shlink:stable、旁边放一个一次性的
shlink-installer容器、配 MariaDB + Redis、把短域名解析到这台机器、
前面挂 Caddy 或 Traefik 做 HTTPS。总共七个 YAML 服务 + 一个.env。
Shlink 是什么 — 为什么需要自己托管
Shlink 是一个 PHP 服务(基于 Mezzio / Laminas,用 Doctrine 做 ORM,
跑在 RoadRunner 上),把长 URL 变成短 URL。你 POST 一个长 URL 到它的
REST API,得到一个短码,Shlink 会用 30x 把访客从
<你的短域名>/abc12 重定向回真实目的地,同时记录这次访问(IP、
UA、来源、国家)。
当前活跃版本线是 5.x。截至本文撰写时,最新镜像 tag 是 5.1.5
(stable 和 latest 都指向它;2026-07-03 由 acelaya 推送)。
amd64 镜像 107 MB,arm64 镜像 98 MB,自 v4.0.0 起以非 root 身份运行 —
已经不再需要 -non-root / -alpine 之类的后缀体操了。
对比常见托管方案:
- Bitly / TinyURL — 日常用还行,但你拿不到数据、扛不住大规模重定向
速度,链接历史数据可能被服务商擅自删除。 - YOURLS — 元老级 PHP 自托管方案,现在还能用,但数据模型仅支持 MySQL,
UI 也明显停留在 2008 年审美。 - Shlink — 现代化的 REST API、多数据库(MariaDB / MySQL / PostgreSQL /
MSSQL)、QR 码生成(4.5.0 起 deprecated,见 Pitfalls 一节)、完整的
OpenAPI 规范、可选的 React PWA Web 客户端。
Docker 栈 — 一份完整的 compose
官方推荐的栈一共七个服务。下面是最小可行版本,直接来自
shlinkio/shlink 自己仓库的 docker-compose.yml 和
shlinkio/shlink-docker 参考仓库:
services:
shlink:
image: shlinkio/shlink:stable
restart: unless-stopped
depends_on:
mariadb:
condition: service_healthy
redis:
condition: service_healthy
env_file: .env
networks: [shlink-net]
shlink-installer:
image: shlinkio/shlink:stable
depends_on:
mariadb:
condition: service_healthy
env_file: .env
entrypoint: shlink-installer
command: init --no-interaction
networks: [shlink-net]
shlink-web-client:
image: shlinkio/shlink-web-client
restart: unless-stopped
depends_on:
- shlink
environment:
SHLINK_SERVER_URL: ${SHLINK_SERVER_URL}
SHLINK_SERVER_API_KEY: ${INITIAL_API_KEY}
networks: [shlink-net]
mariadb:
image: mariadb:11
environment:
MARIADB_ROOT_PASSWORD: ${DB_ROOT_PASSWORD}
MARIADB_DATABASE: shlink
MARIADB_USER: shlink
MARIADB_PASSWORD: ${DB_PASSWORD}
volumes:
- mariadb_data:/var/lib/mysql
healthcheck:
test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
interval: 10s
timeout: 5s
retries: 5
networks: [shlink-net]
redis:
image: redis:7-alpine
networks: [shlink-net]
networks:
shlink-net:
driver: bridge
volumes:
mariadb_data:有两件事值得专门强调:
shlink和shlink-installer是同一个镜像,只是启动方式不同。
installer 容器跑一次shlink-installer init --no-interaction,等
MariaDB 起好、跑迁移、下载 GeoLite2,然后退出 0。这是故意的 ——
docker compose up不能被卡住。shlink-web-client可选但强烈建议开。 它是一个 React PWA
(nginx:alpine 基础镜像、25 MB),通过上面两个环境变量读 server
配置。
让它能跑起来的 .env
Shlink 的配置几乎全部由环境变量驱动。最小可用的 .env:
# 品牌
DEFAULT_DOMAIN=s.example.com
IS_HTTPS_ENABLED=true
SHORT_URL_TRAILING_SLASH=false
# 数据库(MariaDB 用 'maria',不是 'mariadb' — 常见拼错)
DB_DRIVER=maria
DB_HOST=mariadb
DB_PORT=3306
DB_DATABASE=shlink
DB_USER=shlink
DB_PASSWORD=please...n
# 访问记录 & 限流
REDIS_SERVERS=redis://redis:6379
# 首次启动的 API key(首次启动后忽略)
INITIAL_API_KEY=*** openssl rand -hex 32)
# GeoLite2(免费,2019-12-30 起必需,用于访问地理位置)
GEOLITE_LICENSE_KEY=your-maxmind-license-key
# Web 客户端配置(shlink-web-client 读,不是 shlink)
SHLINK_SERVER_URL=https://s.example.com三个看起来像拼错但其实没拼错的设置:
DB_DRIVER=maria— MariaDB 用这个。mariadb是悄悄错的(对不上任何
driver)。MySQL 用mysql,PostgreSQL 用postgres,MSSQL 用mssql,
SQLite 用sqlite。REDIS_SERVERS支持逗号分隔的多个实例 — 当你横向扩 Shlink 到多实例、
想用 Redis Cluster 时很有用。v5.0.0 还加了unix:/path/to/redis.sock
支持本机 Unix socket。- 任何环境变量
X都可以用X_FILE指向一个文件(Docker secrets 模式)。
这是生产环境传DB_PASSWORD和INITIAL_API_KEY的唯一安全姿势。
GeoLite2 — 唯一有点烦的配置
2019-12-30 起,MaxMind 要求用(免费)license key 才能下载 GeoLite2。
Shlink 把它读成 GEOLITE_LICENSE_KEY 环境变量,并在首次启动时下载数据库。
申请 key 的步骤:
- 到 https://www.maxmind.com/en/geolite2/signup 注册账号。
- 生成 license key — “Will you be using geoipupdate?” 一项选 No
(Shlink 自己负责下载)。 - 在
.env里写GEOLITE_LICENSE_KEY=<key>,然后等大约 30 分钟让 key 在
MaxMind 的边缘同步生效。(是的这很烦。事实就是这样。) - 重启
shlink服务(docker compose up -d shlink)。下一次访问就会
触发 GeoLite2 下载。
如果忘了设这个 key,Shlink 照样能跑 —— 访问记录照样记,但国家/城市
字段会一直是 Unknown。等你真的想要地理分析时就难受了。
创建 API key
调用 Shlink REST API 时认证靠 X-Api-Key: <uuid> 头。没有 admin 用户 —
每把 key 都是显式创建的。三种创建方式:
# 1. 首次启动前在 .env 里设 INITIAL_API_KEY。首次启动时会创建一把 key,
# 后续启动忽略。
# 2. 栈起来之后,在容器里用 CLI 创建:
docker compose exec shlink shlink api-key:create --name default
# -> 把一个 UUID 打到 stdout。
# 3. 也可以用 REST API 创建(需要已经有一把 author_apis=ALL 的 key):
curl -X POST https://s.example.com/rest/v3/api-keys
-H "X-Api-Key: $EXISTING_KEY"
-H "Content-Type: application/json"
-d '{"name":"secondary"}'Web UI 不能创建 key — 按 Shlink 的设计,它只消费 key。如果丢了 key,
唯一的恢复方式是 docker compose exec shlink shlink api-key:create --name replacement。
通过 API 创建第一个短链
拿到 INITIAL_API_KEY 之后,短化你的第一个 URL:
KEY=$(grep INITIAL_API_KEY .env | cut -d= -f2)
curl -X POST https://s.example.com/rest/v3/short-urls
-H "X-Api-Key: $KEY"
-H "Content-Type: application/json"
-d '{
"longUrl": "https://github.com/shlinkio/shlink",
"tags": ["github","docs"],
"title": "Shlink on GitHub"
}'返回:
{
"shortCode": "gh5h2",
"shortUrl": "https://s.example.com/gh5h2",
"longUrl": "https://github.com/shlinkio/shlink",
"tags": ["github","docs"],
"title": "Shlink on GitHub"
}访问 https://s.example.com/gh5h2,会跳到 GitHub。
访问 https://s.example.com/rest/health 返回 200(确认服务在线)。
HTTPS — 终结者在外面,不在 Shlink 里
Shlink 的 Docker 镜像只说 HTTP,监听 8080。它不做 TLS 终结。在它前面挂一个:
- Caddy —
s.example.com { reverse_proxy shlink:8080 }。通过 Let’s Encrypt
自动签证书。 - Traefik — 基于 label 的配置,同样自动签证书。YAML 略多一点。
- nginx + certbot — 能用,但要手动跑
certbot certonly+ 配续期 cron。 - Cloudflare Tunnel — 自己不用管证书,Cloudflare 帮你搞定。如果你已经在
Cloudflare 上,挺合适。
启用 HTTPS 后两个环境变量坑:
IS_HTTPS_ENABLED=true告诉 Shlink 生成https://短链(默认不设就是
http://)。- 如果前面挂了多层代理(Cloudflare → Caddy → Shlink),设
TRUSTED_PROXIES
(v4.5.0 起)—— 逗号分隔的 IP 段列表,让 Shlink 能解析到真实客户端 IP,
否则访问记录的 IP 字段全是错的。
坑(2026 版)
有几件事第一次部署一定会坑你:
- 生产别用 SQLite。 官方文档明确警告:”Using it in production is not
supported, even less if mounted via docker volumes.” 上公开服务之前换到
MariaDB / Postgres / MSSQL。 latestvsstable. 有 alpha/beta 时latest会指过去;stable永远
跟最新的稳定版。生产环境 pin 具体版本(例如5.1.5)或stable。shlink-installer exited with code 0不是错误。 首次初始化跑完
schema、迁移、ORM proxy 生成、GeoLite2 下载,然后退出 0。看
docker compose logs shlink-installer就能知道它到底干了啥。- Web 客户端的环境变量要挂在 web-client 容器上,不要挂在 installer 上。
SHLINK_SERVER_URL/SHLINK_SERVER_API_KEY配的是 nginx 服务的 React 应用
里预填的 server 列表。如果挂在shlink上,啥也不会发生。 - 升级需要手动
db:migrate. installer 只在冷启动时跑一次。改镜像 tag →
docker compose pull→docker compose up -d→docker compose exec shlink shlink db:migrate。 - 跑多实例必须用 Redis。 Shlink 用
symfony/lock做共享状态;没有 Redis
的话,锁是本地的,并发创建 domain/key 时会撞车。
你能控制什么 vs 不能控制什么
你能控制的:
- 全部 URL → 短码映射。
- 全部访问日志(IP 默认是 hash 化的;设
ANONYMIZE_REMOTE_ADDR=false会关掉
哈希 —— 会让你的实例违反 GDPR)。 - 完整的 Redis / MariaDB 状态。任何时候
docker compose exec mariadb mysqldump
就能把整库备份带走。
你无法控制的:
- 短域名本身 — 这是 DNS 的事。把 A/AAAA 解析到跑 Shlink 的机器就完事。
- 证书 — 这是反向代理的活(或者 Cloudflare 的活)。
一句话总结
单用户 / 小团队部署,shlinkio/shlink:stable + mariadb:11 +
redis:7-alpine + 一个 Caddy 反向代理,就是 2026 年最稳的配置。镜像维护
活跃、API 干净、接入 GeoLite2 之后访问分析也好用。镜像总数:四个。
compose.yaml 里每服务的 YAML 量:十行。
参考资料
- 项目主页:https://github.com/shlinkio/shlink(5,137 stars、MIT、默认分支
develop) - Docker 镜像:https://hub.docker.com/r/shlinkio/shlink
- 官方文档:https://shlink.io/documentation/
- 参考 Docker 栈:https://github.com/shlinkio/shlink-docker
- Web 客户端:https://github.com/shlinkio/shlink-web-client
评论