-
Notifications
You must be signed in to change notification settings - Fork 0
Deployment Guide
Complete guide for deploying tail-lookup in various environments, from simple Docker containers to production Kubernetes clusters.
Pull and run the latest image:
docker run -d \
--name tail-lookup \
-p 8080:8080 \
--restart unless-stopped \
ryakel/tail-lookup:latestAccess at: http://localhost:8080
Create docker-compose.yml:
version: "3.8"
services:
tail-lookup:
image: ryakel/tail-lookup:latest
container_name: tail-lookup
ports:
- "8080:8080"
restart: unless-stoppedDeploy:
docker compose up -dUse case: Local development, testing, personal use
Steps:
-
Pull latest image:
docker pull ryakel/tail-lookup:latest
-
Run container:
docker run -d \ --name tail-lookup \ -p 8080:8080 \ --restart unless-stopped \ ryakel/tail-lookup:latest
-
Verify:
curl http://localhost:8080/api/v1/health
-
View logs:
docker logs -f tail-lookup
-
Stop/Start:
docker stop tail-lookup docker start tail-lookup
-
Update:
docker pull ryakel/tail-lookup:latest docker stop tail-lookup docker rm tail-lookup docker run -d --name tail-lookup -p 8080:8080 --restart unless-stopped ryakel/tail-lookup:latest
Pros:
- Simple, single command
- No configuration needed
- Fast startup
Cons:
- Manual updates
- No automatic restart on reboot (unless using --restart)
Use case: Production single-host deployments, easier management
Steps:
-
Create docker-compose.yml:
version: "3.8" services: tail-lookup: image: ryakel/tail-lookup:latest container_name: tail-lookup ports: - "8080:8080" restart: unless-stopped healthcheck: test: ["CMD", "python", "-c", "import httpx; httpx.get('http://localhost:8080/api/v1/health').raise_for_status()"] interval: 30s timeout: 5s retries: 3 start_period: 5s
-
Deploy:
docker compose up -d
-
View logs:
docker compose logs -f
-
Update:
docker compose pull docker compose up -d
-
Stop:
docker compose down
Pros:
- Declarative configuration
- Easy updates with docker compose pull
- Health check built-in
- Simple scaling (if needed)
Cons:
- Requires docker-compose.yml file
- Still manual updates
Use case: Production with web UI, automatic updates
Prerequisites:
- Portainer installed: https://docs.portainer.io/start/install
Steps:
-
In Portainer UI:
- Navigate to "Stacks" β "Add stack"
- Name:
tail-lookup - Web editor, paste:
version: "3.8" services: tail-lookup: image: ryakel/tail-lookup:latest ports: - "8080:8080" restart: unless-stopped
- Click "Deploy the stack"
-
Enable webhook for automatic updates:
- Navigate to stack β "Webhook"
- Toggle "Service webhook"
- Copy webhook URL
- Add to GitHub repository secrets as
PORTAINER_WEBHOOK_URL
-
Manual update:
- Stack β "Update" β "Pull and redeploy"
Pros:
- Web UI for management
- Automatic updates via webhook
- Easy rollback
- Multi-host support (Portainer Business)
Cons:
- Requires Portainer installation
- Additional complexity
Use case: Automatic updates without webhooks
Steps:
-
Create docker-compose.yml with Watchtower:
version: "3.8" services: tail-lookup: image: ryakel/tail-lookup:latest container_name: tail-lookup ports: - "8080:8080" restart: unless-stopped watchtower: image: containrrr/watchtower container_name: watchtower volumes: - /var/run/docker.sock:/var/run/docker.sock command: --interval 3600 tail-lookup restart: unless-stopped
-
Deploy:
docker compose up -d
How it works:
- Watchtower checks for image updates every hour
- Automatically pulls new image if available
- Gracefully restarts tail-lookup container
- Zero manual intervention
Pros:
- Fully automatic updates
- No webhook configuration needed
- Works with any Docker deployment
Cons:
- Additional container running
- Polls Docker Hub (might hit rate limits)
- Updates happen automatically (no approval)
Use case: Large-scale, multi-instance deployments
Prerequisites:
- Kubernetes cluster (minikube, EKS, GKE, AKS, etc.)
- kubectl configured
Deployment Manifest (tail-lookup-deployment.yaml):
apiVersion: apps/v1
kind: Deployment
metadata:
name: tail-lookup
labels:
app: tail-lookup
spec:
replicas: 3
selector:
matchLabels:
app: tail-lookup
template:
metadata:
labels:
app: tail-lookup
spec:
containers:
- name: tail-lookup
image: ryakel/tail-lookup:latest
imagePullPolicy: Always
ports:
- containerPort: 8080
name: http
livenessProbe:
httpGet:
path: /api/v1/health
port: 8080
initialDelaySeconds: 5
periodSeconds: 30
readinessProbe:
httpGet:
path: /api/v1/health
port: 8080
initialDelaySeconds: 5
periodSeconds: 10
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "256Mi"
cpu: "500m"
---
apiVersion: v1
kind: Service
metadata:
name: tail-lookup
spec:
type: LoadBalancer
selector:
app: tail-lookup
ports:
- port: 80
targetPort: 8080
protocol: TCP
name: httpDeploy:
kubectl apply -f tail-lookup-deployment.yamlGet service URL:
kubectl get svc tail-lookupUpdate:
kubectl rollout restart deployment/tail-lookupScale:
kubectl scale deployment/tail-lookup --replicas=5Pros:
- High availability (multiple replicas)
- Automatic load balancing
- Rolling updates
- Self-healing
- Horizontal scaling
Cons:
- Complex setup
- Requires Kubernetes knowledge
- Overkill for small deployments
Task Definition:
{
"family": "tail-lookup",
"containerDefinitions": [
{
"name": "tail-lookup",
"image": "ryakel/tail-lookup:latest",
"memory": 256,
"cpu": 256,
"essential": true,
"portMappings": [
{
"containerPort": 8080,
"protocol": "tcp"
}
],
"healthCheck": {
"command": ["CMD-SHELL", "curl -f http://localhost:8080/api/v1/health || exit 1"],
"interval": 30,
"timeout": 5,
"retries": 3,
"startPeriod": 10
}
}
]
}Deploy via:
- AWS Console β ECS β Create Service
- AWS CLI:
aws ecs create-service ... - Terraform/CloudFormation for IaC
gcloud run deploy tail-lookup \
--image ryakel/tail-lookup:latest \
--platform managed \
--region us-central1 \
--allow-unauthenticated \
--port 8080Pros: Fully managed, auto-scaling, pay-per-use
az container create \
--resource-group myResourceGroup \
--name tail-lookup \
--image ryakel/tail-lookup:latest \
--ports 8080 \
--dns-name-label tail-lookup-aci \
--restart-policy AlwaysUse case: TLS termination, custom domain, multiple services
nginx.conf:
server {
listen 80;
server_name tail-lookup.example.com;
# Redirect HTTP to HTTPS
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
server_name tail-lookup.example.com;
ssl_certificate /etc/nginx/ssl/cert.pem;
ssl_certificate_key /etc/nginx/ssl/key.pem;
location / {
proxy_pass http://localhost:8080;
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;
}
# Optional: serve static files directly
location /static/ {
proxy_pass http://localhost:8080/static/;
expires 1d;
add_header Cache-Control "public, immutable";
}
}docker-compose.yml with Traefik:
version: "3.8"
services:
tail-lookup:
image: ryakel/tail-lookup:latest
restart: unless-stopped
labels:
- "traefik.enable=true"
- "traefik.http.routers.tail-lookup.rule=Host(`tail-lookup.example.com`)"
- "traefik.http.routers.tail-lookup.entrypoints=websecure"
- "traefik.http.routers.tail-lookup.tls.certresolver=letsencrypt"
- "traefik.http.services.tail-lookup.loadbalancer.server.port=8080"
networks:
- traefik
networks:
traefik:
external: trueCaddyfile:
tail-lookup.example.com {
reverse_proxy localhost:8080
}Default internal port: 8080
Map to different external port:
docker run -p 3000:8080 ryakel/tail-lookup:latest # Access on port 3000If building custom image with different database:
FROM ryakel/tail-lookup:latest
COPY my-custom.db /app/data/aircraft.dbNo environment variable needed; path is hardcoded in Dockerfile.
Built into Dockerfile:
HEALTHCHECK --interval=30s --timeout=5s --start-period=5s --retries=3 \
CMD python -c "import httpx; httpx.get('http://localhost:8080/api/v1/health').raise_for_status()"Check container health:
docker inspect --format='{{.State.Health.Status}}' tail-lookupLiveness probe:
livenessProbe:
httpGet:
path: /api/v1/health
port: 8080
initialDelaySeconds: 5
periodSeconds: 30Readiness probe:
readinessProbe:
httpGet:
path: /api/v1/health
port: 8080
initialDelaySeconds: 5
periodSeconds: 10UptimeRobot:
- Monitor:
https://tail-lookup.example.com/api/v1/health - Interval: 5 minutes
- Alert on: Status != 200
Prometheus:
scrape_configs:
- job_name: 'tail-lookup'
metrics_path: '/api/v1/health'
static_configs:
- targets: ['localhost:8080']Database snapshots:
- Automatically stored in GitHub Releases (daily)
- Download:
curl -L -o aircraft.db https://github.com/ryakel/tail-lookup/releases/latest/download/aircraft.db
Container backup (not typically needed):
- Database is immutable in container
- Just pull latest image to restore
Scenario: Container lost, need to restore
-
Pull latest image:
docker pull ryakel/tail-lookup:latest
-
Redeploy:
docker run -d -p 8080:8080 --restart unless-stopped ryakel/tail-lookup:latest
Scenario: Need specific historical database
-
Download from release:
curl -L -o aircraft.db https://github.com/ryakel/tail-lookup/releases/download/data-2025-11-20/aircraft.db
-
Build custom image:
FROM ryakel/tail-lookup:latest COPY aircraft.db /app/data/aircraft.db
-
Build and run:
docker build -t tail-lookup:custom . docker run -d -p 8080:8080 tail-lookup:custom
Adjust resource limits:
Docker:
docker run -d \
--name tail-lookup \
-p 8080:8080 \
--memory=512m \
--cpus=1.0 \
ryakel/tail-lookup:latestKubernetes:
resources:
requests:
memory: "256Mi"
cpu: "250m"
limits:
memory: "512Mi"
cpu: "1000m"Load Balancer Required:
- Nginx, HAProxy, Traefik, or cloud load balancer
- Round-robin or least-connections algorithm
Docker Compose (simple):
version: "3.8"
services:
tail-lookup:
image: ryakel/tail-lookup:latest
deploy:
replicas: 3
ports:
- "8080-8082:8080"Kubernetes (production):
spec:
replicas: 5 # Run 5 instancesDatabase considerations:
- Each container has full database copy
- Read-only workload, no synchronization needed
- No shared state between containers
- Perfect for horizontal scaling
Database optimization (already done):
- SQLite with JOIN index
- Batch inserts during build
- Read-only mode (no write locks)
Application optimization (already done):
- Async FastAPI with Uvicorn
- Pydantic validation
- No external API calls
Container optimization:
- Use date-tagged images (not latest) for consistency
- Enable Docker BuildKit for faster builds
- Use multi-stage builds (not applicable here)
Firewall rules:
- Allow inbound on port 8080 (or your mapped port)
- Deny all other inbound traffic
- Allow outbound (for Docker Hub pulls)
TLS/SSL:
- Use reverse proxy (Nginx, Traefik, Caddy) for TLS termination
- Let's Encrypt for free certificates
- Redirect HTTP to HTTPS
CORS:
- Currently allows all origins (
Allow-Origin: *) - For production, consider restricting to specific domains
Run as non-root (future improvement):
USER nobodyRead-only root filesystem (future improvement):
docker run --read-only --tmpfs /tmp ryakel/tail-lookup:latestSecurity scanning:
docker scan ryakel/tail-lookup:latestNo built-in authentication (by design):
- Public FAA data, open access
- Add authentication with reverse proxy if needed
Example with Nginx Basic Auth:
location / {
auth_basic "Restricted";
auth_basic_user_file /etc/nginx/.htpasswd;
proxy_pass http://localhost:8080;
}Check logs:
docker logs tail-lookupCommon causes:
- Port 8080 already in use: Change mapping
-p 8081:8080 - Database file missing: Use official image, not custom build
- Python errors: Check Python version in Dockerfile
Manual check:
curl http://localhost:8080/api/v1/healthExpected response:
{
"status": "healthy",
"database_exists": true,
"record_count": 297431,
"last_updated": "2025-11-28T06:15:23Z"
}If failing:
- Verify container is running:
docker ps - Check database exists:
docker exec tail-lookup ls -lh /app/data/aircraft.db - Restart container:
docker restart tail-lookup
Symptom: last_updated is old (more than 24 hours)
Solutions:
- Pull latest image:
docker pull ryakel/tail-lookup:latest - Recreate container
- Check nightly build workflow for failures
Normal: ~100-150MB RAM usage
High (>500MB): Possible memory leak
Solutions:
- Restart container:
docker restart tail-lookup - Check for long-running requests
- Monitor with
docker stats tail-lookup
View logs:
docker logs -f tail-lookupLog to file:
docker logs tail-lookup > tail-lookup.log 2>&1Docker Compose logging:
services:
tail-lookup:
image: ryakel/tail-lookup:latest
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"Prometheus metrics (not yet implemented):
# Add to main.py
from prometheus_fastapi_instrumentator import Instrumentator
Instrumentator().instrument(app).expose(app)Access at: http://localhost:8080/metrics
Self-hosted (Uptime Kuma):
docker run -d \
--name uptime-kuma \
-p 3001:3001 \
-v uptime-kuma:/app/data \
louislam/uptime-kuma:latestAdd monitor for http://tail-lookup:8080/api/v1/health
Self-hosted (VPS):
- Small VPS (1 vCPU, 1GB RAM): $5-10/month
- Can run many containers
- Example: DigitalOcean, Linode, Vultr
Cloud Platforms:
- AWS ECS: ~$15-30/month (Fargate)
- Google Cloud Run: ~$5-15/month (pay-per-use)
- Azure Container Instances: ~$10-20/month
Kubernetes Cluster:
- Managed: $50-150/month (EKS, GKE, AKS)
- Self-hosted: VPS costs only
Docker Hub pulls:
- Free tier: 100 pulls per 6 hours (unauthenticated)
- Paid tier: Unlimited pulls
- Our usage: ~30 pulls/day (nightly builds + updates)
API traffic:
- Minimal bandwidth (JSON responses ~1-5KB)
- 100K requests/month β 100-500MB
- Usually included in hosting
For production deployment, we recommend:
-
Infrastructure:
- Docker Compose on VPS (simple) or Kubernetes (complex)
- Nginx reverse proxy for TLS
- Automated updates (Watchtower or Portainer webhook)
-
Monitoring:
- Health check endpoint monitoring (UptimeRobot, Uptime Kuma)
- Container logs to external service (optional)
- Alerting on failures (email, Slack)
-
Security:
- TLS certificate (Let's Encrypt)
- Firewall rules (only ports 80/443 open)
- Regular image updates (automated)
-
Backup:
- No backup needed (database in GitHub Releases)
- Just redeploy latest image if container lost
Example Production Stack:
version: "3.8"
services:
nginx:
image: nginx:alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
- ./ssl:/etc/nginx/ssl:ro
depends_on:
- tail-lookup
tail-lookup:
image: ryakel/tail-lookup:latest
restart: unless-stopped
expose:
- "8080"
watchtower:
image: containrrr/watchtower
volumes:
- /var/run/docker.sock:/var/run/docker.sock
command: --interval 3600 tail-lookupπ View on GitHub | π³ Docker Hub
π Report Issue | π¬ Discussions
License: MIT License | Copyright (c) 2024-2025 FliteAxis
π Getting Started
π¦ Deployment
π§ Development
π Security
- Security Setup Guide
- Security CI/CD Pipeline
- Code Quality & Linting
- SBOM Management
- Vulnerability Scanning
π Dependencies
π³ Docker
π Reference
π Links