← Back to DevBytes

Designing a URL Shortener Containerized on Kubernetes

Understanding the URL Shortener Architecture

A URL shortener is a web service that converts long, unwieldy URLs into compact, easy-to-share links. When a user clicks the shortened link, the service performs a lookup and redirects them to the original destination. Behind this seemingly simple operation lies a fascinating set of engineering challenges around data storage, collision handling, and high-throughput request processing.

Core Components

A well-designed URL shortener typically consists of:

Why Containerize on Kubernetes?

🚀 Deploy your AI agent in 10 minutes

Managed Hermes hosting. Zero DevOps. 100M tokens/mo included.

Try it free →

Running a URL shortener on Kubernetes transforms a simple web app into a resilient, horizontally scalable system. Containerization ensures consistent environments across development, staging, and production. Kubernetes provides declarative deployment, self-healing via restart policies, service discovery, load balancing, and seamless rolling updates — all critical for a service that may handle millions of redirects per day.

Key Benefits

Designing the Application

Let's build a production-ready URL shortener in Python using FastAPI. We'll use Base62 encoding for short code generation and PostgreSQL for persistence. The entire application will be containerized and deployed on Kubernetes.

Project Structure

url-shortener/
├── app/
│   ├── __init__.py
│   ├── main.py
│   ├── models.py
│   ├── schemas.py
│   ├── crud.py
│   ├── database.py
│   ├── utils.py
│   └── config.py
├── migrations/
├── Dockerfile
├── requirements.txt
├── k8s/
│   ├── namespace.yaml
│   ├── configmap.yaml
│   ├── secret.yaml
│   ├── deployment.yaml
│   ├── service.yaml
│   ├── ingress.yaml
│   └── hpa.yaml
└── docker-compose.yaml

Database Models

We define a SQLAlchemy model for the URL mapping table. Each shortened URL gets a unique short code and stores metadata like creation time, expiration, and click count.

# app/models.py
from sqlalchemy import Column, String, Text, DateTime, Integer, Boolean
from sqlalchemy.ext.declarative import declarative_base
from datetime import datetime, timezone

Base = declarative_base()

class URLMapping(Base):
    __tablename__ = "url_mappings"

    id = Column(Integer, primary_key=True, index=True, autoincrement=True)
    short_code = Column(String(10), unique=True, nullable=False, index=True)
    original_url = Column(Text, nullable=False)
    created_at = Column(DateTime(timezone=True), default=lambda: datetime.now(timezone.utc))
    expires_at = Column(DateTime(timezone=True), nullable=True)
    click_count = Column(Integer, default=0)
    is_active = Column(Boolean, default=True)

Short Code Generation Utility

The short code generator uses a Base62 alphabet (characters safe for URLs). We generate a random 7-character string and check for collisions against the database. For high-throughput scenarios, consider using a distributed ID generator like Snowflake or a pre-allocated range service.

# app/utils.py
import secrets
from typing import Optional

BASE62_ALPHABET = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
BASE = len(BASE62_ALPHABET)

def generate_short_code(length: int = 7) -> str:
    """Generate a cryptographically random Base62 string."""
    code = []
    for _ in range(length):
        index = secrets.randbelow(BASE)
        code.append(BASE62_ALPHABET[index])
    return ''.join(code)

def is_valid_url(url: str) -> bool:
    """Basic URL validation."""
    return url.startswith(("http://", "https://"))

Main Application Entry Point

The FastAPI application exposes two primary endpoints: POST /shorten to create a short link, and GET /{short_code} to resolve and redirect. We include health checks and metrics for Kubernetes probes.

# app/main.py
from fastapi import FastAPI, HTTPException, status, Request, Query
from fastapi.responses import RedirectResponse, JSONResponse
from sqlalchemy.orm import Session
from contextlib import asynccontextmanager
from datetime import datetime, timezone, timedelta
import validators

from app.database import engine, get_session, Base
from app.models import URLMapping
from app.utils import generate_short_code, is_valid_url
from app.config import settings

@asynccontextmanager
async def lifespan(app: FastAPI):
    # Create tables on startup
    Base.metadata.create_all(bind=engine)
    yield
    # Cleanup on shutdown
    pass

app = FastAPI(
    title="URL Shortener",
    version="1.0.0",
    lifespan=lifespan
)

@app.get("/health", tags=["Health"])
def health_check():
    return {"status": "healthy", "timestamp": datetime.now(timezone.utc).isoformat()}

@app.get("/ready", tags=["Health"])
def readiness_check(db: Session = Depends(get_session)):
    try:
        db.execute("SELECT 1")
        return {"status": "ready"}
    except Exception:
        raise HTTPException(status_code=503, detail="Database not reachable")

@app.post("/shorten", status_code=status.HTTP_201_CREATED)
def create_short_url(
    url: str = Query(..., description="The long URL to shorten"),
    custom_code: Optional[str] = Query(None, max_length=20),
    ttl_days: Optional[int] = Query(None, ge=1, le=365),
    db: Session = Depends(get_session)
):
    if not is_valid_url(url):
        raise HTTPException(status_code=400, detail="Invalid URL format")

    if custom_code:
        existing = db.query(URLMapping).filter(
            URLMapping.short_code == custom_code
        ).first()
        if existing:
            raise HTTPException(status_code=409, detail="Custom code already in use")
        short_code = custom_code
    else:
        for attempt in range(settings.MAX_RETRIES):
            short_code = generate_short_code()
            existing = db.query(URLMapping).filter(
                URLMapping.short_code == short_code
            ).first()
            if not existing:
                break
        else:
            raise HTTPException(status_code=500, detail="Could not generate unique code")

    expires_at = None
    if ttl_days:
        expires_at = datetime.now(timezone.utc) + timedelta(days=ttl_days)

    mapping = URLMapping(
        short_code=short_code,
        original_url=url,
        expires_at=expires_at
    )
    db.add(mapping)
    db.commit()
    db.refresh(mapping)

    short_url = f"{settings.BASE_URL}/{short_code}"
    return JSONResponse({
        "short_url": short_url,
        "short_code": short_code,
        "original_url": url,
        "expires_at": expires_at.isoformat() if expires_at else None
    })

@app.get("/{short_code}")
def redirect_to_original(
    short_code: str,
    request: Request,
    db: Session = Depends(get_session)
):
    mapping = db.query(URLMapping).filter(
        URLMapping.short_code == short_code,
        URLMapping.is_active == True
    ).first()

    if not mapping:
        raise HTTPException(status_code=404, detail="Short URL not found")

    if mapping.expires_at and mapping.expires_at < datetime.now(timezone.utc):
        raise HTTPException(status_code=410, detail="This short URL has expired")

    mapping.click_count += 1
    db.commit()

    return RedirectResponse(
        url=mapping.original_url,
        status_code=301
    )

Configuration Management

Environment variables drive all configuration, making the app cloud-native and compatible with Kubernetes ConfigMaps and Secrets.

# app/config.py
from pydantic_settings import BaseSettings
from typing import Optional

class Settings(BaseSettings):
    DATABASE_URL: str = "postgresql://postgres:postgres@localhost:5432/urlshortener"
    BASE_URL: str = "http://localhost:8000"
    MAX_RETRIES: int = 5
    SHORT_CODE_LENGTH: int = 7

    class Config:
        env_file = ".env"

settings = Settings()

Database Session Management

# app/database.py
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker, Session
from typing import Generator
from app.config import settings

engine = create_engine(
    settings.DATABASE_URL,
    pool_size=20,
    max_overflow=40,
    pool_pre_ping=True
)

SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)

def get_session() -> Generator[Session, None, None]:
    db = SessionLocal()
    try:
        yield db
    finally:
        db.close()

Containerizing with Docker

We create a multi-stage Dockerfile that builds dependencies in a cacheable layer and produces a slim production image. The image runs with a non-root user for security.

# Dockerfile
FROM python:3.11-slim AS builder

WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir --upgrade pip && \
    pip install --no-cache-dir -r requirements.txt

FROM python:3.11-slim AS runtime

RUN groupadd -r appuser && useradd -r -g appuser -m appuser

WORKDIR /app
COPY --from=builder /usr/local/lib/python3.11/site-packages /usr/local/lib/python3.11/site-packages
COPY --from=builder /usr/local/bin /usr/local/bin

COPY app/ ./app/

RUN chown -R appuser:appuser /app
USER appuser

EXPOSE 8000

HEALTHCHECK --interval=15s --timeout=5s --retries=3 \
  CMD curl -f http://localhost:8000/health || exit 1

ENTRYPOINT ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]

Python Dependencies

# requirements.txt
fastapi==0.104.1
uvicorn[standard]==0.24.0
sqlalchemy==2.0.23
psycopg2-binary==2.9.9
pydantic-settings==2.1.0
validators==0.22.0
alembic==1.13.0

Kubernetes Deployment

Now we define the Kubernetes manifests. We'll deploy a PostgreSQL StatefulSet alongside our application Deployment, configure networking with Services and Ingress, and set up autoscaling.

Namespace Isolation

# k8s/namespace.yaml
apiVersion: v1
kind: Namespace
metadata:
  name: url-shortener
  labels:
    app: url-shortener
    environment: production

ConfigMap for Application Settings

# k8s/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: url-shortener-config
  namespace: url-shortener
data:
  BASE_URL: "https://short.example.com"
  SHORT_CODE_LENGTH: "7"
  MAX_RETRIES: "5"
  DATABASE_HOST: "postgres-service"
  DATABASE_PORT: "5432"
  DATABASE_NAME: "urlshortener"

Secret for Sensitive Credentials

# k8s/secret.yaml
apiVersion: v1
kind: Secret
metadata:
  name: url-shortener-secret
  namespace: url-shortener
type: Opaque
stringData:
  DATABASE_USER: "postgres"
  DATABASE_PASSWORD: "s3cur3P@ssw0rd!"
  # In production, use sealed secrets or external secrets operator

Application Deployment

The Deployment defines replica count, resource limits, pod anti-affinity for spreading across nodes, and mounts configuration via environment variables from ConfigMap and Secret.

# k8s/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: url-shortener
  namespace: url-shortener
  labels:
    app: url-shortener
    component: api
spec:
  replicas: 3
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0
  selector:
    matchLabels:
      app: url-shortener
  template:
    metadata:
      labels:
        app: url-shortener
        component: api
        version: v1
    spec:
      affinity:
        podAntiAffinity:
          preferredDuringSchedulingIgnoredDuringExecution:
            - weight: 100
              podAffinityTerm:
                labelSelector:
                  matchExpressions:
                    - key: app
                      operator: In
                      values:
                        - url-shortener
                topologyKey: kubernetes.io/hostname
      containers:
        - name: url-shortener
          image: registry.example.com/url-shortener:1.0.0
          imagePullPolicy: IfNotPresent
          ports:
            - containerPort: 8000
              name: http
              protocol: TCP
          env:
            - name: DATABASE_URL
              value: "postgresql://$(DATABASE_USER):$(DATABASE_PASSWORD)@$(DATABASE_HOST):$(DATABASE_PORT)/$(DATABASE_NAME)"
            - name: BASE_URL
              valueFrom:
                configMapKeyRef:
                  name: url-shortener-config
                  key: BASE_URL
            - name: SHORT_CODE_LENGTH
              valueFrom:
                configMapKeyRef:
                  name: url-shortener-config
                  key: SHORT_CODE_LENGTH
            - name: MAX_RETRIES
              valueFrom:
                configMapKeyRef:
                  name: url-shortener-config
                  key: MAX_RETRIES
            - name: DATABASE_HOST
              valueFrom:
                configMapKeyRef:
                  name: url-shortener-config
                  key: DATABASE_HOST
            - name: DATABASE_PORT
              valueFrom:
                configMapKeyRef:
                  name: url-shortener-config
                  key: DATABASE_PORT
            - name: DATABASE_NAME
              valueFrom:
                configMapKeyRef:
                  name: url-shortener-config
                  key: DATABASE_NAME
            - name: DATABASE_USER
              valueFrom:
                secretKeyRef:
                  name: url-shortener-secret
                  key: DATABASE_USER
            - name: DATABASE_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: url-shortener-secret
                  key: DATABASE_PASSWORD
          resources:
            requests:
              cpu: "250m"
              memory: "256Mi"
            limits:
              cpu: "1000m"
              memory: "512Mi"
          livenessProbe:
            httpGet:
              path: /health
              port: 8000
            initialDelaySeconds: 10
            periodSeconds: 30
            timeoutSeconds: 5
            failureThreshold: 3
          readinessProbe:
            httpGet:
              path: /ready
              port: 8000
            initialDelaySeconds: 5
            periodSeconds: 10
            timeoutSeconds: 3
            failureThreshold: 2
          startupProbe:
            httpGet:
              path: /health
              port: 8000
            initialDelaySeconds: 0
            periodSeconds: 5
            failureThreshold: 12

Service for Internal Load Balancing

# k8s/service.yaml
apiVersion: v1
kind: Service
metadata:
  name: url-shortener-service
  namespace: url-shortener
  labels:
    app: url-shortener
spec:
  type: ClusterIP
  selector:
    app: url-shortener
    component: api
  ports:
    - name: http
      port: 80
      targetPort: 8000
      protocol: TCP
    - name: http-alt
      port: 8000
      targetPort: 8000
      protocol: TCP
  sessionAffinity: None

Ingress for External Traffic

# k8s/ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: url-shortener-ingress
  namespace: url-shortener
  annotations:
    cert-manager.io/cluster-issuer: "letsencrypt-prod"
    nginx.ingress.kubernetes.io/rewrite-target: /$2
    nginx.ingress.kubernetes.io/proxy-body-size: "10m"
    nginx.ingress.kubernetes.io/proxy-read-timeout: "30"
spec:
  ingressClassName: nginx
  tls:
    - hosts:
        - short.example.com
      secretName: url-shortener-tls
  rules:
    - host: short.example.com
      http:
        paths:
          - path: /(.*)
            pathType: ImplementationSpecific
            backend:
              service:
                name: url-shortener-service
                port:
                  number: 80

Horizontal Pod Autoscaler

HPA scales the deployment based on CPU utilization. For more advanced scenarios, you can use custom metrics like requests-per-second via Prometheus adapters.

# k8s/hpa.yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: url-shortener-hpa
  namespace: url-shortener
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: url-shortener
  minReplicas: 3
  maxReplicas: 20
  metrics:
    - type: Resource
      resource:
        name: cpu
        target:
          type: Utilization
          averageUtilization: 70
    - type: Resource
      resource:
        name: memory
        target:
          type: Utilization
          averageUtilization: 80
  behavior:
    scaleUp:
      stabilizationWindowSeconds: 60
      policies:
        - type: Pods
          value: 4
          periodSeconds: 60
        - type: Percent
          value: 100
          periodSeconds: 60
      selectPolicy: Max
    scaleDown:
      stabilizationWindowSeconds: 300
      policies:
        - type: Pods
          value: 2
          periodSeconds: 120
      selectPolicy: Min

PostgreSQL StatefulSet

For production, the database runs as a StatefulSet with a PersistentVolumeClaim. This ensures data survives pod restarts and node migrations.

# k8s/postgres-statefulset.yaml
apiVersion: v1
kind: Service
metadata:
  name: postgres-service
  namespace: url-shortener
  labels:
    app: postgres
spec:
  type: ClusterIP
  clusterIP: None  # Headless service for StatefulSet
  selector:
    app: postgres
  ports:
    - port: 5432
      targetPort: 5432
      name: postgres
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: postgres
  namespace: url-shortener
spec:
  serviceName: postgres-service
  replicas: 1
  selector:
    matchLabels:
      app: postgres
  template:
    metadata:
      labels:
        app: postgres
    spec:
      containers:
        - name: postgres
          image: postgres:15-alpine
          ports:
            - containerPort: 5432
              name: postgres
          env:
            - name: POSTGRES_USER
              valueFrom:
                secretKeyRef:
                  name: url-shortener-secret
                  key: DATABASE_USER
            - name: POSTGRES_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: url-shortener-secret
                  key: DATABASE_PASSWORD
            - name: POSTGRES_DB
              valueFrom:
                configMapKeyRef:
                  name: url-shortener-config
                  key: DATABASE_NAME
            - name: PGDATA
              value: /var/lib/postgresql/data/pgdata
          volumeMounts:
            - name: postgres-data
              mountPath: /var/lib/postgresql/data
          resources:
            requests:
              cpu: "500m"
              memory: "512Mi"
            limits:
              cpu: "2000m"
              memory: "2Gi"
          livenessProbe:
            exec:
              command: ["pg_isready", "-U", "postgres"]
            initialDelaySeconds: 30
            periodSeconds: 20
            timeoutSeconds: 5
          readinessProbe:
            exec:
              command: ["pg_isready", "-U", "postgres", "-d", "urlshortener"]
            initialDelaySeconds: 10
            periodSeconds: 10
  volumeClaimTemplates:
    - metadata:
        name: postgres-data
      spec:
        accessModes: ["ReadWriteOnce"]
        storageClassName: "standard"
        resources:
          requests:
            storage: 50Gi

Deployment Workflow

Follow these steps to deploy the complete stack to a Kubernetes cluster:

# 1. Create the namespace
kubectl apply -f k8s/namespace.yaml

# 2. Apply ConfigMap and Secret first
kubectl apply -f k8s/configmap.yaml
kubectl apply -f k8s/secret.yaml

# 3. Deploy PostgreSQL StatefulSet
kubectl apply -f k8s/postgres-statefulset.yaml

# 4. Wait for PostgreSQL to be ready
kubectl -n url-shortener wait --for=condition=ready pod -l app=postgres --timeout=120s

# 5. Build and push the Docker image
docker build -t registry.example.com/url-shortener:1.0.0 .
docker push registry.example.com/url-shortener:1.0.0

# 6. Deploy the application
kubectl apply -f k8s/deployment.yaml
kubectl apply -f k8s/service.yaml
kubectl apply -f k8s/hpa.yaml

# 7. Deploy Ingress (requires cert-manager and ingress controller)
kubectl apply -f k8s/ingress.yaml

# 8. Verify everything is running
kubectl -n url-shortener get pods,svc,ing,hpa

# 9. Test the endpoint
curl -X POST "https://short.example.com/shorten?url=https://example.com/very/long/url"

Best Practices for Production

1. Idempotent Short Code Generation

Implement idempotency keys in the API. If a client sends the same request twice (e.g., due to network retry), the service should return the existing short code rather than creating a duplicate. Store a hash of the original URL alongside the mapping to enable deduplication.

# Add to models.py
from sqlalchemy import Column, String, Index

class URLMapping(Base):
    # ... existing fields ...
    url_hash = Column(String(64), nullable=True, index=True)

# Create composite index
Index('idx_url_hash_active', URLMapping.url_hash, URLMapping.is_active)

2. Caching Layer with Redis

For redirects (the hot path), implement a Redis cache that stores short_code → original_url mappings. This reduces database load dramatically. Use a write-through cache pattern: update Redis when creating new mappings, and fall back to the database on cache miss.

# Add Redis cache to redirect endpoint
import redis.asyncio as redis

cache = redis.Redis(
    host=settings.REDIS_HOST,
    port=settings.REDIS_PORT,
    decode_responses=True
)

@app.get("/{short_code}")
async def redirect_to_original(short_code: str, ...):
    # Try cache first
    cached_url = await cache.get(f"url:{short_code}")
    if cached_url:
        # Increment click count asynchronously
        await cache.incr(f"clicks:{short_code}")
        return RedirectResponse(url=cached_url, status_code=301)
    
    # Fall back to database
    mapping = db.query(URLMapping).filter(...).first()
    if mapping:
        await cache.set(f"url:{short_code}", mapping.original_url, ex=3600)
        return RedirectResponse(url=mapping.original_url, status_code=301)
    raise HTTPException(status_code=404)

3. Database Connection Pooling

Configure SQLAlchemy connection pooling carefully. Each pod replica should use a moderate pool size (10-20 connections) to avoid overwhelming PostgreSQL. Use PgBouncer as a sidecar container in the pod for connection multiplexing when scaling beyond 20 pods.

# k8s/deployment.yaml — add PgBouncer sidecar
- name: pgbouncer
  image: pgbouncer/pgbouncer:latest
  ports:
    - containerPort: 6432
  env:
    - name: DB_USER
      valueFrom:
        secretKeyRef:
          name: url-shortener-secret
          key: DATABASE_USER
    - name: DB_PASSWORD
      valueFrom:
        secretKeyRef:
          name: url-shortener-secret
          key: DATABASE_PASSWORD
    - name: DB_HOST
      value: "postgres-service"
  volumeMounts:
    - name: pgbouncer-config
      mountPath: /etc/pgbouncer

4. Observability Stack

Integrate Prometheus metrics, structured logging, and distributed tracing. Export FastAPI metrics via prometheus_fastapi_instrumentator and ship logs to Loki or Elasticsearch.

# Add to main.py
from prometheus_fastapi_instrumentator import Instrumentator

instrumentator = Instrumentator()
instrumentator.instrument(app).expose(app, endpoint="/metrics")

5. Rate Limiting & Abuse Prevention

Protect the /shorten endpoint from abuse by implementing token bucket rate limiting. Use a Redis-backed rate limiter or deploy an Envoy rate-limit filter as a sidecar.

# k8s/deployment.yaml — add rate limit annotations
metadata:
  annotations:
    nginx.ingress.kubernetes.io/limit-rps: "50"
    nginx.ingress.kubernetes.io/limit-rate-after: "10"
    nginx.ingress.kubernetes.io/limit-whitelist: ""

6. Graceful Shutdown & Zero-Downtime Migrations

Configure proper SIGTERM handling in the application. FastAPI with uvicorn handles this by default, but ensure your deployment's terminationGracePeriodSeconds is sufficient (at least 30 seconds) to drain in-flight requests.

# k8s/deployment.yaml — pod template spec
spec:
  terminationGracePeriodSeconds: 30
  containers:
    - name: url-shortener
      lifecycle:
        preStop:
          exec:
            command: ["/bin/sh", "-c", "sleep 5"]

7. Disaster Recovery & Backups

Schedule regular PostgreSQL backups using pg_dump or continuous archiving with WAL-G. Store backups in object storage (S3, GCS) with retention policies.

# CronJob for daily backups
apiVersion: batch/v1
kind: CronJob
metadata:
  name: postgres-backup
  namespace: url-shortener
spec:
  schedule: "0 2 * * *"
  jobTemplate:
    spec:
      template:
        spec:
          containers:
            - name: backup
              image: postgres:15-alpine
              command:
                - /bin/sh
                - -c
                - |
                  pg_dump -h postgres-service -U postgres urlshortener | \
                  gzip > /backup/urlshortener-$(date +%Y%m%d).sql.gz
              env:
                - name: PGPASSWORD
                  valueFrom:
                    secretKeyRef:
                      name: url-shortener-secret
                      key: DATABASE_PASSWORD
              volumeMounts:
                - name: backup-volume
                  mountPath: /backup
          restartPolicy: OnFailure
          volumes:
            - name: backup-volume
              persistentVolumeClaim:
                claimName: backup-pvc

8. Multi-Environment Strategy

Use Kustomize or Helm to manage environment-specific configurations. Maintain separate overlays for development, staging, and production with different replica counts, resource allocations, and ingress hosts.

# Example Kustomize overlay structure
k8s/
├── base/
│   ├── deployment.yaml
│   ├── service.yaml
│   └── kustomization.yaml
├── overlays/
│   ├── dev/
│   │   ├── kustomization.yaml
│   │   └── patches/
│   ├── staging/
│   │   ├── kustomization.yaml
│   │   └── patches/
│   └── production/
│       ├── kustomization.yaml
│       └── patches/

Conclusion

Designing a URL shortener containerized on Kubernetes is a rewarding exercise that touches nearly every aspect of modern cloud-native development. You've built a REST API with collision-resistant short code generation, wrapped it in a secure Docker container, and deployed it on Kubernetes with high availability, autoscaling, TLS termination, and persistent state. By following the best practices outlined here — caching hot paths, implementing idempotency, configuring proper health probes, setting up observability, and planning for disaster recovery — you transform a simple URL shortening service into a production-grade system capable of handling millions of redirects with minimal latency. The patterns demonstrated here apply broadly: whether you're building a bookmarking service, a link-in-bio tool, or an enterprise redirect manager, the Kubernetes-native architecture provides the foundation for reliability, scalability, and operational excellence.

🚀 Need a reliable AI agent for your project?

Deploy Hermes Agent in 10 minutes. Managed hosting, zero DevOps.

Get Started — $23.99/mo
← Back to all articles