Self-hosting
L1nkZip is distributed as a Docker image, although the full code can be found on the Github repository in case you want to use it directly or contribute. It can be run as a Docker container or deployed to a Kubernetes cluster. Litestream is not required to run L1nkZip, but it is strongly encouraged if you are going to use sqlite.
Requirements
L1nkZip would not be possible without the amazings FastApi and PonyORM projects. The only requirement is Python 3.12+ if you use sqlite. Otherwise, you must install the database driver for your database of choice.
The official Docker image comes with sqlite and postgresql support, but you can extend it to support other databases.
- mysql: MySQLdb
- oracle: cx_oracle
Example from a Dockerfile extending the Docker image to support MySQL:
For more information check the PonyORM documentation.
Configuration
Required environment variables
API_DOMAIN: Domain of the API. This is the domain of the shortened URLs.DB_TYPE: Database type. Supported values areinmemory,sqliteandpostgresql. Other databases likemysql,oracle, andcockroachdbare also supported thanks to PonyORM, but require additional drivers.DB_NAME: Database name. Used for sqlite and postgresql.TOKEN: Token used to authenticate some administrative actions to the API. This is a secret value and should not be shared.GENERATOR_STRING: String used to generate the shortened URLs. This is a secret value and should not be shared. You can shuffle uppercase, lowercase letters and/or numbers without repeating them.
Optional environment variables
API_NAME: Name of the API. Used in the OpenAPI documentation. Default: L1nkZipDB_HOST: Database host. Used for postgresql.DB_USER: Database user. Used for postgresql.DB_PASSWORD: Database password. Used for postgresql.DB_DSN: Database DSN. Used for Oracle.SITE_URL: URL of the website. Used when the API is visited from a browser.PHISHTANK: Enable protection against phishing. Default: false. The values can also be "anonymous" or your actual PhishTank key. More details below.REDIS_SERVER: Redis server URL for caching (e.g., redis://localhost:6379/0). Optional.REDIS_TTL: Time-to-live for cached URLs in seconds. Default: 86400 (24 hours).RATE_LIMIT_CREATE: Rate limit for URL creation. Default: "10/minute".RATE_LIMIT_REDIRECT: Rate limit for URL redirection. Default: "120/minute".METRICS_ENABLED: Enable Prometheus metrics endpoint. Default: false.LOG_LEVEL: Logging level. Default: "INFO". Options: "DEBUG", "INFO", "WARN", "ERROR".LOG_FORMAT: Log format. Default: "text". Set to "json" for structured logging.
Phishtank support
L1nkZip can be configured to check if the URL to be shortened is in the PhishTank database. This is an optional feature and can be enabled by setting the PHISHTANK environment variable to anonymous or your actual PhishTank key.
To avoid overloading the service, the Phishtank database is downloaded into the local L1nkZip database. This action will be launched each time the update endpoint is contacted successfully. The update endpoint is protected by the TOKEN environment variable and should be called periodically by a cronjob or similar. Please, be respectful with the Phishtank policies. Check the API documentation for more details about the update endpoint.
Each update to the Phishtank database can add new entries and remove old ones. This will keep the size of the database under control, it is not growing all the time. When an entry is removed from the database, the shortened URL will be allowed/reactivated again. Keep in mind that L1nkZip is not a censorship tool and it is not intended to be used as such.
Docker image
The docker image is available on Docker Hub.
Kubernetes manifest
This is an example of a StatefulSet to deploy L1nkZip to a Kubernetes cluster. The required secrets are not included and it uses a sqlite database with litestream. This example is for a S3 compatible service (idrive e2), Amazon S3 configuration for AWS is slightly different. Please, have a look at the Litestream documentation for more details.
---
apiVersion: v1
kind: ConfigMap
metadata:
name: litestream
data:
litestream.yml: |
dbs:
- path: /data/l1nkzip.db
replicas:
- type: s3
bucket: <your-bucket>
path: l1nkzip.db
endpoint: <your-endpoint>
region: <your-region>
force-path-style: true
---
apiVersion: v1
kind: Service
metadata:
name: l1nkzip
spec:
selector:
app: l1nkzip
type: ClusterIP
ports:
- name: http
protocol: TCP
port: 80
targetPort: 80
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: l1nkzip
spec:
selector:
matchLabels:
app: l1nkzip
serviceName: l1nkzip
replicas: 1
template:
metadata:
labels:
app: l1nkzip
spec:
initContainers:
- name: init-litestream
image: litestream/litestream:0.3.9
args:
[
"restore",
"-if-db-not-exists",
"-if-replica-exists",
"-v",
"$(DB_NAME)",
]
volumeMounts:
- name: l1nkzip
mountPath: /data
- name: litestream
mountPath: /etc/litestream.yml
subPath: litestream.yml
env:
- name: LITESTREAM_ACCESS_KEY_ID
valueFrom:
secretKeyRef:
name: l1nkzip-secret
key: LITESTREAM_ACCESS_KEY_ID
- name: LITESTREAM_SECRET_ACCESS_KEY
valueFrom:
secretKeyRef:
name: l1nkzip-secret
key: LITESTREAM_SECRET_ACCESS_KEY
- name: DB_NAME
valueFrom:
configMapKeyRef:
name: l1nkzip-config
key: DB_NAME
containers:
- name: litestream
image: litestream/litestream:0.3.9
args: ["replicate"]
volumeMounts:
- name: l1nkzip
mountPath: /data
- name: litestream
mountPath: /etc/litestream.yml
subPath: litestream.yml
env:
- name: LITESTREAM_ACCESS_KEY_ID
valueFrom:
secretKeyRef:
name: l1nkzip-secret
key: LITESTREAM_ACCESS_KEY_ID
- name: LITESTREAM_SECRET_ACCESS_KEY
valueFrom:
secretKeyRef:
name: l1nkzip-secret
key: LITESTREAM_SECRET_ACCESS_KEY
ports:
- name: metrics
containerPort: 9090
- name: l1nkzip
image: dorogoy/l1nkzip:latest
imagePullPolicy: Always
ports:
- name: http
containerPort: 80
volumeMounts:
- name: l1nkzip
mountPath: /data
- name: litestream
mountPath: /etc/litestream.yml
subPath: litestream.yml
env:
- name: TOKEN
valueFrom:
secretKeyRef:
name: l1nkzip-secret
key: TOKEN
- name: GENERATOR_STRING
valueFrom:
secretKeyRef:
name: l1nkzip-secret
key: GENERATOR_STRING
- name: PHISHTANK
value: "anonymous"
- name: DB_TYPE
value: sqlite
- name: DB_NAME
valueFrom:
configMapKeyRef:
name: l1nkzip-config
key: DB_NAME
- name: API_DOMAIN
valueFrom:
configMapKeyRef:
name: l1nkzip-config
key: API_DOMAIN
- name: SITE_URL
valueFrom:
configMapKeyRef:
name: l1nkzip-config
key: SITE_URL
volumes:
- name: litestream
configMap:
name: litestream
- name: l1nkzip
persistentVolumeClaim:
claimName: l1nkzip
volumeClaimTemplates:
- metadata:
name: l1nkzip
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
Docker Compose Example
This example shows how to run L1nkZip with PostgreSQL and Redis using Docker Compose:
version: '3.8'
services:
postgres:
image: postgres:17-alpine
environment:
POSTGRES_DB: l1nkzip
POSTGRES_USER: l1nkzip
POSTGRES_PASSWORD: your-postgres-password
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U l1nkzip -d l1nkzip"]
interval: 10s
timeout: 5s
retries: 5
redis:
image: redis:7-alpine
command: redis-server --appendonly yes
volumes:
- redis_data:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 5
l1nkzip:
image: dorogoy/l1nkzip:latest
ports:
- "8000:80"
environment:
# Required environment variables
API_DOMAIN: "http://localhost:8000"
DB_TYPE: "postgres"
DB_NAME: "l1nkzip"
DB_HOST: "postgres"
DB_PORT: "5432"
DB_USER: "l1nkzip"
DB_PASSWORD: "your-postgres-password"
TOKEN: "your-secret-admin-token"
GENERATOR_STRING: "your-custom-alphabet-here"
# Optional environment variables
PHISHTANK: "anonymous"
REDIS_SERVER: "redis://redis:6379/0"
REDIS_TTL: "86400"
RATE_LIMIT_CREATE: "10/minute"
RATE_LIMIT_REDIRECT: "120/minute"
METRICS_ENABLED: "true"
LOG_LEVEL: "INFO"
LOG_FORMAT: "json"
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
restart: unless-stopped
volumes:
postgres_data:
redis_data:
To use this configuration:
- Create a
docker-compose.ymlfile with the above content - Replace the placeholder values:
your-postgres-password: A strong password for PostgreSQLyour-secret-admin-token: A secure token for admin operationsyour-custom-alphabet: A shuffled string of characters for URL generation (e.g., "aBcDeFgHiJkLmNoPqRsTuVwXyZ0123456789")- Run:
docker-compose up -d
The API will be available at http://localhost:8000 with:
- PostgreSQL database for persistent storage
- Redis caching for improved performance
- JSON structured logging
- Prometheus metrics at /metrics
- Health checks at /health
Monitoring and Observability
L1nkZip supports comprehensive monitoring through Prometheus metrics and structured logging.
Metrics Endpoint
When METRICS_ENABLED=true is set, L1nkZip exposes a Prometheus metrics endpoint at /metrics. This includes:
- HTTP request metrics (count, latency, error rates)
- Cache performance metrics (hit/miss rates)
- Business metrics (URLs created, redirects, phishing blocks)
- Database connection metrics
Structured Logging
Set LOG_FORMAT=json to enable JSON-formatted structured logging with fields including:
- Request tracing IDs
- Response times
- Error details
- Cache operations
- Phishing detection events
Example Monitoring Configuration
# Add to your Kubernetes deployment env vars
- name: METRICS_ENABLED
value: "true"
- name: LOG_FORMAT
value: "json"
- name: LOG_LEVEL
value: "INFO"
Grafana Dashboards
Pre-built Grafana dashboards are available for: - API performance monitoring - Cache efficiency analysis - Business metrics tracking - Error rate and latency monitoring
Alerting
Configure Prometheus alert rules for: - High error rates (>5% for 5 minutes) - Service availability (container down) - High latency (95th percentile >1 second) - Low cache hit rate (<70%) - High phishing block rates
Health Checks
The health endpoint at /health provides:
- Database connectivity status
- Service availability
- Basic dependency checks