Skip to content

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

  1. Quick Reference
  2. Deployment Scenarios
  3. New Server Setup (Disaster Recovery)
  4. Production Deployment
  5. Monitoring & Maintenance
  6. 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

When to use: - Python code changes - Template changes - View logic updates - Model changes (with migrations) - Configuration changes (docker-compose.yml, .env)

cd /srv/uplink/app
git pull origin main
./deploy.sh deploy-full

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.

cd /srv/uplink/app
git pull origin main
./deploy.sh deploy-quick

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

cd /srv/uplink/app
git pull origin main
./deploy.sh deploy-rebuild

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

cd /srv/uplink/app

# Create .env file with production settings
nano .env

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

  1. SSH into production server:

    ssh uplink@uplink.sensational.systems
    cd ~/app
    

  2. Check current status:

    ./deploy.sh status
    git status
    

  3. Choose deployment method:

For code-only changes:

./deploy.sh deploy-quick

For changes with migrations:

./deploy.sh deploy-full

For dependency changes:

./deploy.sh deploy-rebuild

  1. Monitor deployment:

    # Watch logs during deployment
    ./deploy.sh logs
    
    # Check container status
    docker compose ps
    
    # Verify site is responding
    curl -I https://uplink.sensational.systems
    

  2. Post-deployment verification:

  3. Test critical functionality
  4. Check for errors in logs
  5. Monitor for 5-10 minutes
  6. 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)


Additional Resources