Uplink Docker Deployment Guide¶
Last Updated: February 24, 2026
Production Server: uplink.sensational.systems
This guide covers deploying and managing the Dockerized Uplink application.
Table of Contents¶
- Quick Reference
- Deployment Scenarios
- New Server Setup (Disaster Recovery)
- Production Deployment
- Monitoring & Maintenance
- Troubleshooting
Quick Reference¶
Common Commands¶
# Full deployment (RECOMMENDED for production)
./deploy.sh deploy-full
# Quick deployment (code changes only, NOT RECOMMENDED for production)
./deploy.sh deploy-quick
# Complete rebuild (dependency changes)
./deploy.sh deploy-rebuild
# Check status
./deploy.sh status
docker compose ps
# View logs (real-time)
./deploy.sh logs # All services
docker compose logs -f web daphne huey # Key services
docker compose logs -f web # Just web/HTTP requests
tail -f /srv/uplink/logs/cron.log # Cron jobs
# Restart specific service
docker compose restart web
docker compose restart daphne huey
# ⚠️ Note: restart doesn't apply config changes!
# For config changes, use: ./deploy.sh deploy-full
Deployment Scenarios¶
Scenario 1: Code Changes with Migrations (RECOMMENDED DEFAULT)¶
When to use: - Python code changes - Template changes - View logic updates - Model changes (with migrations) - Configuration changes (docker-compose.yml, .env)
What it does: - Pulls latest code - Stops all containers (releases ports, clears state) - Starts containers with updated configuration - Runs database migrations - Collects static files - ⏱️ Downtime: ~10-15 seconds
Why down/up instead of restart: - Applies docker-compose.yml changes (health checks, environment vars, etc.) - Ensures clean container state - Reloads all configuration - More reliable for production
Scenario 2: Quick Code Changes Only (USE WITH CAUTION)¶
When to use: - Simple Python code changes ONLY - NO migrations - NO docker-compose.yml changes - NO environment variable changes
⚠️ WARNING: This does NOT reload docker-compose.yml changes. Use deploy-full instead.
What it does: - Pulls latest code - Restarts web, daphne, and huey containers (quick restart) - ⏱️ Downtime: ~5 seconds
Limitations: - Does NOT apply docker-compose.yml changes - Does NOT reload health check configurations - Does NOT run migrations - May cause unexpected behavior if compose config changed
Recommendation: Use deploy-full as your default. The extra 5-10 seconds of downtime is worth the reliability.
Scenario 3: Complete Rebuild (Dependency Changes)¶
When to use:
- Changes to pyproject.toml
- Python version updates
- System dependency changes
- Dockerfile modifications
What it does: - Pulls latest code - Stops all containers - Rebuilds Docker images from scratch - Starts containers - Runs migrations - Collects static files - ⏱️ Downtime: ~2-3 minutes
Scenario 4: Emergency Rollback¶
When to use: Critical issues after deployment
cd /srv/uplink/app
# Option A: Revert git commit
git log --oneline -5 # Find commit to revert to
git reset --hard <commit-hash>
./deploy.sh deploy-quick
# Option B: Revert to previous branch/tag
git checkout <previous-branch-or-tag>
./deploy.sh deploy-full
# If images need rebuilding
./deploy.sh deploy-rebuild
Important Deployment Notes¶
Container Restart vs Recreate¶
Critical: docker compose restart does NOT apply changes to:
- Command arguments
- Environment variables
- Volume mounts
- docker-compose.yml configuration
When config changes are made, you MUST recreate containers:
# ❌ WRONG - restart keeps old config in memory
docker compose restart web
# ✅ CORRECT - recreate applies new config
docker compose -f docker-compose.yml -f docker-compose.prod.yml down
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d
# Or use deploy script (RECOMMENDED)
./deploy.sh deploy-full
Examples of when you need to recreate:
- Changed command: in docker-compose.yml (e.g., adding logging flags)
- Modified environment variables in docker-compose files
- Changed volume mounts
- Updated health check configuration
- Modified network settings
The deploy.sh script handles this correctly - it uses down + up for full deployments.
New Server Setup (Disaster Recovery)¶
Complete Server Setup from Scratch¶
Use this guide if the current server fails and you need to set up Uplink on a completely new VM.
Step 1: Provision New Server¶
Requirements: - Ubuntu 24.04 LTS - 4GB+ RAM - 50GB+ disk space - Root/sudo access
# SSH into new server as root
ssh root@<new-server-ip>
# Create uplink user
adduser uplink
usermod -aG sudo uplink
sudo -u uplink -i
Step 2: Install System Dependencies¶
# Update system
sudo apt update && sudo apt upgrade -y
# Install essential tools
sudo apt install -y git curl wget vim htop build-essential
# Set timezone
sudo timedatectl set-timezone Europe/London
# Configure firewall
sudo ufw allow 22/tcp # SSH
sudo ufw allow 80/tcp # HTTP
sudo ufw allow 443/tcp # HTTPS
sudo ufw --force enable
Step 3: Install Docker¶
# Install Docker GPG key
sudo mkdir -p /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
# Add Docker repository
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
$(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
# Install Docker
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
# Add user to docker group
sudo usermod -aG docker $USER
newgrp docker
# Enable Docker on boot
sudo systemctl enable docker
# Verify installation
docker --version
docker compose version
Step 4: Set Up SSH Keys for GitHub¶
# Generate SSH key
ssh-keygen -t ed25519 -C "your-email@example.com"
# Display public key
cat ~/.ssh/id_ed25519.pub
# Add this key to GitHub:
# 1. Go to https://github.com/settings/keys
# 2. Click "New SSH key"
# 3. Paste the public key
# 4. Title: "Uplink Production Server"
Step 5: Clone Repository¶
# Create application directory
sudo mkdir -p /srv/uplink
sudo chown uplink:uplink /srv/uplink
cd /srv/uplink
# Clone repository
git clone git@github.com:SensationalSystems/uplink.git app
cd app
# Checkout main branch (or specific branch if needed)
git checkout main
# Create symlink for convenience
cd ~
ln -s /srv/uplink/app ~/app
echo "cd ~/app" >> ~/.bashrc
Step 6: Configure Environment¶
Critical .env variables:
# Django Core
DEBUG=False
SECRET_KEY='<your-secret-key>'
ALLOWED_HOSTS=localhost,127.0.0.1,<your-domain>,<server-ip>
# Database (DigitalOcean MySQL Cluster)
DATABASE_HOST=<digitalocean-mysql-host>
DATABASE_PORT=25060
DATABASE_NAME=uplink
DATABASE_USER=<db-user>
DATABASE_PASS=<db-password>
# Redis (Docker)
REDIS_HOST=redis
REDIS_URL=redis://redis:6379/0
HUEY_IMMEDIATE=False
# External APIs
PRESTASHOP_BASE_URL=<prestashop-url>
PRESTASHOP_API_KEY=<api-key>
FEDEX_PROD_CLIENT_ID=<fedex-client-id>
FEDEX_PROD_CLIENT_SECRET=<fedex-secret>
MAILGUN_API_KEY=<mailgun-key>
# Docker
DOCKER_CONTAINER=1
Step 7: Set Up Environment Detection¶
cd /srv/uplink/app
# Create production marker file (enables automatic production mode)
touch .production
# Verify environment detection
./deploy.sh status
# Should show: "Environment: [production]"
What this does:
- The .production file tells deploy.sh to use production compose files
- Automatically applies docker-compose.prod.yml overrides
- Ensures correct settings (no volume mounts, DEBUG=False, restart policies)
Step 8: Build and Start Docker Containers¶
cd /srv/uplink/app
# Build Docker images (uses production config automatically)
./deploy.sh docker-build
# Start containers
./deploy.sh docker-up
# Verify containers are running
docker compose ps
# Check logs for errors
./deploy.sh logs
Expected containers:
- uplink_web (gunicorn)
- uplink_daphne (WebSockets)
- uplink_huey (background tasks)
- uplink_redis (cache/queue)
- uplink_nginx (reverse proxy)
- uplink_certbot (SSL certificates)
Step 9: Run Database Migrations¶
# Run migrations
docker compose exec web python manage.py migrate
# Collect static files
docker compose exec web python manage.py collectstatic --noinput
# Create superuser (optional)
docker compose exec web python manage.py createsuperuser
Step 10: Set Up SSL Certificates¶
# Update nginx.conf with your domain
nano nginx.conf
# Change server_name to your domain
# Run init-letsencrypt script
chmod +x init-letsencrypt.sh
./init-letsencrypt.sh
# Restart nginx to use SSL
docker compose restart nginx
Step 11: Configure Cron Jobs¶
# Configure git identity
git config --global user.email "your-email@example.com"
git config --global user.name "Your Name"
# Create logs directory
mkdir -p /srv/uplink/logs
# Install crontab
crontab crontab.production
# Verify installation
crontab -l
# Monitor cron logs
tail -f /srv/uplink/logs/cron.log
Step 12: Update DNS¶
Once everything is working:
# Test the site works
curl -I https://<your-domain>
# Update DNS to point to new server IP
# (Done via your DNS provider)
# Monitor for traffic
./deploy.sh logs
Step 13: Verify Everything Works¶
# Check all containers
docker compose ps
# Check logs for errors
docker compose logs --tail=100
# Test critical functionality:
# - Login to admin
# - Create/view orders
# - Check PrestaShop sync (wait 2 minutes for cron)
# - Test WebSocket connections (if applicable)
# Monitor cron jobs
tail -f /srv/uplink/logs/cron.log
Production Deployment¶
Pre-Deployment Checklist¶
Before deploying to production:
- [ ] All tests pass locally
- [ ] Changes reviewed via Pull Request
- [ ] Merged to main branch
- [ ] Database migrations tested
- [ ] Backup recent database (automatic daily, but verify)
- [ ] Notify team of deployment window (if needed)
Standard Deployment Process¶
-
SSH into production server:
-
Check current status:
-
Choose deployment method:
For code-only changes:
For changes with migrations:
For dependency changes:
-
Monitor deployment:
-
Post-deployment verification:
- Test critical functionality
- Check for errors in logs
- Monitor for 5-10 minutes
- Verify PrestaShop sync is running (check cron.log)
Monitoring & Maintenance¶
Daily Monitoring¶
# Check container health
docker compose ps
# Check recent logs
docker compose logs --tail=100 web
# Check cron jobs are running
tail -20 /srv/uplink/logs/cron.log
# Check disk space
df -h
# Check memory usage
free -h
Weekly Maintenance¶
# Clean up Docker resources
docker system prune -a --volumes -f
# Check SSL certificate expiry
docker compose exec nginx ls -la /etc/letsencrypt/live/
# Review error logs
grep ERROR /srv/uplink/logs/cron.log
Container-Specific Commands¶
Viewing Real-Time Logs:
# View real-time logs from all key services (RECOMMENDED)
docker compose logs -f web daphne huey
# View specific service logs
docker compose logs -f web # HTTP requests, errors
docker compose logs -f daphne # WebSocket connections
docker compose logs -f huey # Background tasks
# Show last 100 lines then follow
docker compose logs -f --tail=100 web
# With timestamps (useful for debugging timing issues)
docker compose logs -f --timestamps web daphne huey
# View logs from a specific time period
docker compose logs --since 30m web # Last 30 minutes
docker compose logs --since "2024-01-15T10:00:00" web
💡 Monitoring Tip: The web service logs all HTTP requests in real-time: - Request methods and URLs - Response status codes (200, 302, 404, 500, etc.) - Client IP addresses - User agents
This is equivalent to watching Django's development server output.
Managing Services:
# Restart specific service (for simple restarts only)
docker compose restart web
docker compose restart daphne
docker compose restart huey
# Execute command in container
docker compose exec web python manage.py shell
docker compose exec web python manage.py check
# Access container shell
docker compose exec web /bin/bash
⚠️ Important: restart doesn't apply docker-compose.yml changes. See Important Deployment Notes for when to use recreate instead.
Troubleshooting¶
Container Won't Start¶
# Check container status
docker compose ps
# View full logs
docker compose logs web
# Check for port conflicts
sudo netstat -tulpn | grep :8001
# Try restarting container (for simple issues)
docker compose restart web
# If restart doesn't help OR if you changed docker-compose config, recreate
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d --force-recreate web
# Or use deploy script (applies all config changes)
./deploy.sh deploy-full
Remember: Restart only works for simple issues. Config changes require recreation (see Important Deployment Notes).
Database Connection Issues¶
# Check database settings in .env
cat .env | grep DATABASE
# Test connection from container
docker compose exec web python manage.py dbshell
# Verify DigitalOcean MySQL cluster is accessible
# Check IP whitelist in DigitalOcean dashboard
SSL Certificate Issues¶
# Check certificate files
docker compose exec nginx ls -la /etc/letsencrypt/live/
# Renew certificates manually
docker compose run --rm certbot renew
# Restart nginx
docker compose restart nginx
# View nginx logs
docker compose logs nginx
Static Files Not Loading (Images, CSS Styles Missing)¶
Symptoms: - Images not displaying (e.g., country flags) - CSS styling missing (e.g., star icons are black instead of yellow) - Font Awesome icons load but without colors
Cause: WhiteNoise's manifest cache is stale. The staticfiles.json manifest maps file names to hashed versions and needs to be regenerated.
Solution:
# The --clear flag forces manifest regeneration
docker compose exec web python manage.py collectstatic --noinput --clear
# Restart web containers to reload the manifest
docker compose restart web daphne
# Clear browser cache (Ctrl+Shift+R or Cmd+Shift+R)
Prevention:
- The deploy-full and deploy-rebuild commands now include --clear flag automatically
- Always hard-refresh browser after deployment (Ctrl+Shift+R)
PrestaShop Sync Not Running¶
# Check cron jobs are installed
crontab -l
# Check cron log
tail -f /srv/uplink/logs/cron.log
# Test PrestaShop import manually
docker compose exec -T web python manage.py prestashop_import_products
# Check .env has correct PrestaShop API keys
cat .env | grep PRESTASHOP
High Memory Usage¶
# Check container resource usage
docker stats
# Restart containers
docker compose restart web daphne huey
# If persistent, reduce gunicorn workers in docker-compose.yml
# Change: --workers 4 to --workers 2
Disk Space Issues¶
# Check disk usage
df -h
# Clean Docker resources
docker system prune -a --volumes -f
# Clean logs
sudo journalctl --vacuum-time=7d
# Find large files
sudo du -sh /* | sort -h
Complete System Reset¶
⚠️ Use only as last resort
# Stop everything
docker compose down
# Remove all containers and volumes
docker system prune -a --volumes -f
# Rebuild from scratch
docker compose build --no-cache
docker compose up -d
# Run migrations
docker compose exec web python manage.py migrate
docker compose exec web python manage.py collectstatic --noinput
Emergency Contacts¶
Primary: Hannah Killoh
GitHub Issues: https://github.com/SensationalSystems/uplink/issues
Production Server: uplink.sensational.systems
Database: DigitalOcean MySQL Cluster (managed)