Deployment Guide¶
This guide covers best practices for deploying litestar-admin to production environments.
Pre-Deployment Checklist¶
Before deploying, ensure you have:
Configured authentication with a secure secret key
Enabled HTTPS for all traffic
Set secure cookie options
Configured rate limiting
Set up audit logging
Tested all admin functionality
Configured proper database connections
Security Configuration¶
Secret Keys¶
Never use hardcoded or weak secret keys in production:
import os
import secrets
# Generate a secure key for development
# Run once: print(secrets.token_urlsafe(32))
# Load from environment in production
secret_key = os.environ.get("ADMIN_JWT_SECRET")
if not secret_key:
raise ValueError("ADMIN_JWT_SECRET environment variable must be set")
HTTPS Configuration¶
Always use HTTPS in production:
from litestar_admin import AdminConfig
from litestar_admin.auth import JWTConfig
jwt_config = JWTConfig(
secret_key=os.environ["ADMIN_JWT_SECRET"],
cookie_secure=True, # Only send cookies over HTTPS
cookie_samesite="strict", # Prevent CSRF
)
admin_config = AdminConfig(
auth_backend=auth_backend,
session_cookie_secure=True,
session_cookie_httponly=True,
session_cookie_samesite="lax",
)
Rate Limiting¶
Configure appropriate rate limits for your traffic:
admin_config = AdminConfig(
rate_limit_enabled=True,
rate_limit_requests=100, # Requests per window
rate_limit_window_seconds=60, # 1 minute window
)
For high-traffic deployments, use a distributed rate limit store like Redis.
Database Configuration¶
Production Database Setup¶
Use a production-ready database with connection pooling:
from litestar.plugins.sqlalchemy import SQLAlchemyConfig
db_config = SQLAlchemyConfig(
# PostgreSQL with asyncpg driver
connection_string=os.environ["DATABASE_URL"],
# Connection pool settings
pool_size=10,
max_overflow=20,
pool_timeout=30,
pool_recycle=1800,
# Echo SQL for debugging (disable in production)
echo=False,
)
Environment-Specific Configuration¶
import os
ENVIRONMENT = os.getenv("ENVIRONMENT", "development")
if ENVIRONMENT == "production":
db_url = os.environ["DATABASE_URL"]
debug = False
cookie_secure = True
else:
db_url = "sqlite+aiosqlite:///dev.db"
debug = True
cookie_secure = False
Deployment Options¶
Docker Deployment¶
Create a production Dockerfile:
# Dockerfile
FROM python:3.12-slim
WORKDIR /app
# Install dependencies
COPY pyproject.toml uv.lock ./
RUN pip install uv && uv sync --frozen --no-dev
# Copy application
COPY src/ src/
# Set environment
ENV PYTHONPATH=/app/src
ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1
# Run with uvicorn
CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8000"]
Docker Compose for local testing:
# docker-compose.yml
version: "3.8"
services:
app:
build: .
ports:
- "8000:8000"
environment:
- DATABASE_URL=postgresql+asyncpg://user:pass@db:5432/app
- ADMIN_JWT_SECRET=${ADMIN_JWT_SECRET}
- ENVIRONMENT=production
depends_on:
- db
db:
image: postgres:16
environment:
- POSTGRES_USER=user
- POSTGRES_PASSWORD=pass
- POSTGRES_DB=app
volumes:
- postgres_data:/var/lib/postgresql/data
volumes:
postgres_data:
Kubernetes Deployment¶
# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: litestar-admin
spec:
replicas: 3
selector:
matchLabels:
app: litestar-admin
template:
metadata:
labels:
app: litestar-admin
spec:
containers:
- name: app
image: your-registry/litestar-admin:latest
ports:
- containerPort: 8000
env:
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: app-secrets
key: database-url
- name: ADMIN_JWT_SECRET
valueFrom:
secretKeyRef:
name: app-secrets
key: jwt-secret
resources:
requests:
memory: "256Mi"
cpu: "250m"
limits:
memory: "512Mi"
cpu: "500m"
livenessProbe:
httpGet:
path: /admin/api/health
port: 8000
initialDelaySeconds: 10
periodSeconds: 10
readinessProbe:
httpGet:
path: /admin/api/health
port: 8000
initialDelaySeconds: 5
periodSeconds: 5
Systemd Service¶
For traditional server deployments:
# /etc/systemd/system/litestar-admin.service
[Unit]
Description=Litestar Admin Application
After=network.target
[Service]
Type=exec
User=www-data
Group=www-data
WorkingDirectory=/opt/litestar-admin
Environment="PATH=/opt/litestar-admin/.venv/bin"
Environment="DATABASE_URL=postgresql+asyncpg://user:pass@localhost/app"
Environment="ADMIN_JWT_SECRET=your-secret-key"
ExecStart=/opt/litestar-admin/.venv/bin/uvicorn app:app --host 127.0.0.1 --port 8000 --workers 4
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
Enable and start:
sudo systemctl enable litestar-admin
sudo systemctl start litestar-admin
Reverse Proxy Configuration¶
Nginx¶
upstream litestar_admin {
server 127.0.0.1:8000;
keepalive 64;
}
server {
listen 443 ssl http2;
server_name admin.example.com;
ssl_certificate /etc/ssl/certs/admin.example.com.crt;
ssl_certificate_key /etc/ssl/private/admin.example.com.key;
# Security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
# Admin panel
location /admin {
proxy_pass http://litestar_admin;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Connection "";
# WebSocket support (if needed)
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
# Timeouts
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}
# Static files with caching
location /admin/static {
proxy_pass http://litestar_admin;
proxy_cache_valid 200 1y;
add_header Cache-Control "public, immutable";
}
}
Caddy¶
admin.example.com {
reverse_proxy localhost:8000
header {
X-Frame-Options "SAMEORIGIN"
X-Content-Type-Options "nosniff"
X-XSS-Protection "1; mode=block"
Referrer-Policy "strict-origin-when-cross-origin"
}
}
Traefik¶
# traefik dynamic configuration
http:
routers:
admin:
rule: "Host(`admin.example.com`)"
service: litestar-admin
entryPoints:
- websecure
tls:
certResolver: letsencrypt
middlewares:
- security-headers
services:
litestar-admin:
loadBalancer:
servers:
- url: "http://app:8000"
middlewares:
security-headers:
headers:
frameDeny: true
contentTypeNosniff: true
browserXssFilter: true
referrerPolicy: "strict-origin-when-cross-origin"
Monitoring¶
Health Check Endpoint¶
The admin API includes a health check endpoint:
curl https://admin.example.com/admin/api/health
Logging Configuration¶
Configure structured logging for production:
import logging
import structlog
structlog.configure(
processors=[
structlog.stdlib.filter_by_level,
structlog.stdlib.add_logger_name,
structlog.stdlib.add_log_level,
structlog.processors.TimeStamper(fmt="iso"),
structlog.processors.JSONRenderer(),
],
wrapper_class=structlog.stdlib.BoundLogger,
context_class=dict,
logger_factory=structlog.stdlib.LoggerFactory(),
cache_logger_on_first_use=True,
)
# Set log level
logging.basicConfig(level=logging.INFO)
Metrics with Prometheus¶
Export metrics for monitoring:
from prometheus_client import Counter, Histogram, generate_latest
admin_requests = Counter(
"admin_requests_total",
"Total admin requests",
["method", "endpoint", "status"],
)
admin_latency = Histogram(
"admin_request_duration_seconds",
"Admin request latency",
["method", "endpoint"],
)
Audit Logging¶
Configure audit logging for compliance:
from litestar_admin.audit import AuditLogger, AuditEntry
class DatabaseAuditLogger:
"""Store audit logs in database."""
def __init__(self, session_factory):
self._session_factory = session_factory
async def log(self, entry: AuditEntry) -> None:
async with self._session_factory() as session:
record = AuditLog(**entry.to_dict())
session.add(record)
await session.commit()
async def query(self, filters) -> list[AuditEntry]:
# Query implementation
...
Scaling Considerations¶
Horizontal Scaling¶
For multiple instances, ensure:
Shared Session Storage: Use Redis or database-backed sessions
Distributed Rate Limiting: Use Redis-backed rate limit store
Load Balancer Configuration: Enable sticky sessions if needed
Redis for Rate Limiting¶
# Example Redis rate limit store (implement based on your needs)
class RedisRateLimitStore:
def __init__(self, redis_url: str):
self._redis = aioredis.from_url(redis_url)
async def increment(self, key: str, window: str) -> int:
full_key = f"rate_limit:{key}:{window}"
pipe = self._redis.pipeline()
pipe.incr(full_key)
pipe.expire(full_key, self._get_window_seconds(window))
results = await pipe.execute()
return results[0]
Caching¶
Add caching for frequently accessed data:
from litestar.stores.redis import RedisStore
cache_store = RedisStore.with_client(redis_url=os.environ["REDIS_URL"])
Troubleshooting Production Issues¶
Common Issues¶
502 Bad Gateway
Check if the application is running
Verify proxy configuration
Check for startup errors in logs
Authentication Failures
Verify JWT secret is set correctly
Check cookie settings for HTTPS
Verify user loader function
Slow Performance
Enable connection pooling
Check database query performance
Add caching for static data
Rate Limit Issues
Verify rate limit configuration
Check for proper client IP detection behind proxy
Consider distributed rate limiting
Debug Mode¶
Temporarily enable debug mode for troubleshooting (never in production traffic):
admin_config = AdminConfig(
debug=True, # Enable only for debugging
)
Log Analysis¶
Key log patterns to watch:
# Failed authentication attempts
grep "Authentication required" /var/log/app/admin.log
# Rate limit hits
grep "429" /var/log/nginx/access.log
# Database errors
grep "DatabaseError" /var/log/app/admin.log