Skip to content

Fresh Ubuntu 24.04 Production Setup Guide

This guide provides step-by-step instructions for setting up Uplink on a new Ubuntu 24.04 LTS VM with Docker, running it in parallel with your existing Ubuntu 18.04 production server for safe migration.

Migration Strategy: Parallel Operation - Set up new Ubuntu 24.04 VM with Docker - Run both old and new servers simultaneously - Test thoroughly on new server - Gradually shift traffic to new server - Decommission old server after validation


Table of Contents

  1. Server Specifications
  2. Pre-Setup Checklist
  3. Phase 1: New Server Setup
  4. Phase 2: Running in Parallel
  5. Phase 3: Traffic Cutover
  6. Phase 4: Decommissioning Old Server

Server Specifications

For Current Application Load: - CPU: 2-4 vCPUs (2 cores minimum, 4 recommended) - RAM: 4GB minimum, 8GB recommended - MySQL container: ~512MB-1GB - Redis container: ~256MB-512MB - Web container: ~512MB-1GB - Daphne container: ~256MB-512MB - Huey container: ~256MB-512MB - OS + overhead: ~1GB - Disk: 40GB minimum, 80GB+ recommended - OS: ~10GB - Docker images: ~5GB - Database: ~5-10GB (depends on your data) - Media files: ~5-10GB (depends on your uploads) - Logs: ~2GB - Free space for growth: ~20GB+ - Network: 100 Mbps minimum, 1 Gbps recommended - OS: Ubuntu 24.04 LTS (Server)

Calculating Your Actual Requirements

# On your current Ubuntu 18.04 server, check current usage:

# CPU usage
top -bn1 | grep "Cpu(s)"

# Memory usage
free -h

# Disk usage
df -h
du -sh /srv/uplink/app
du -sh /srv/uplink/app/media
du -sh /var/lib/mysql

# Database size
mysql -u root -p -e "SELECT table_schema AS 'Database', ROUND(SUM(data_length + index_length) / 1024 / 1024, 2) AS 'Size (MB)' FROM information_schema.tables GROUP BY table_schema;"

Sizing Formula: - CPU: Current usage + 50% headroom - RAM: Current usage × 1.5 (Docker overhead) - Disk: Current usage × 2 (for Docker images, volumes, backups)

Example Configurations

Small Setup (Development/Staging): - 2 vCPUs - 4GB RAM - 40GB SSD

Medium Setup (Production - Low/Medium Traffic): - 4 vCPUs - 8GB RAM - 80GB SSD

Large Setup (Production - High Traffic): - 8 vCPUs - 16GB RAM - 160GB SSD


Pre-Setup Checklist

1. Backup Current Production Data

On your Ubuntu 18.04 server:

# Create backups directory
mkdir -p /srv/uplink/backups

# Backup database
mysqldump -u uplink_user -p uplink | gzip > /srv/uplink/backups/uplink_db_full_$(date +%Y%m%d_%H%M%S).sql.gz

# Backup media files
tar -czf /srv/uplink/backups/media_full_$(date +%Y%m%d_%H%M%S).tar.gz /srv/uplink/app/media/

# Backup .env file
cp /srv/uplink/app/.env /srv/uplink/backups/.env.backup.$(date +%Y%m%d)

# Verify backups
ls -lh /srv/uplink/backups/

2. Transfer Backups to New Server

You'll need these on the new server. Options:

Option A: Direct transfer via scp

# From old server (Ubuntu 18.04)
scp /srv/uplink/backups/uplink_db_full_*.sql.gz user@new-server:/tmp/
scp /srv/uplink/backups/media_full_*.tar.gz user@new-server:/tmp/
scp /srv/uplink/backups/.env.backup.* user@new-server:/tmp/

Option B: Via intermediate machine (your laptop)

# Download from old server to laptop
scp user@old-server:/srv/uplink/backups/uplink_db_full_*.sql.gz ~/Downloads/
scp user@old-server:/srv/uplink/backups/media_full_*.tar.gz ~/Downloads/
scp user@old-server:/srv/uplink/backups/.env.backup.* ~/Downloads/

# Upload from laptop to new server
scp ~/Downloads/uplink_db_full_*.sql.gz user@new-server:/tmp/
scp ~/Downloads/media_full_*.tar.gz user@new-server:/tmp/
scp ~/Downloads/.env.backup.* user@new-server:/tmp/

3. Document Current Production Setup

On Ubuntu 18.04 server, document:

# Current services and ports
sudo netstat -tlnp | grep -E "python|mysql|redis|nginx"

# Current environment variables (sanitize secrets!)
cat /srv/uplink/app/.env | grep -v "PASSWORD\|SECRET\|KEY"

# Nginx configuration
cat /etc/nginx/sites-available/uplink

# DNS/domain setup
host yourdomain.com

# SSL certificates location
ls -la /etc/letsencrypt/live/

Phase 1: New Server Setup

Step 1.1: Provision Ubuntu 24.04 VM

  1. Create new VM with Ubuntu 24.04 LTS Server
  2. Assign sufficient resources (see specs above)
  3. Note the IP address
  4. Configure SSH access
  5. Set hostname: sudo hostnamectl set-hostname uplink-new

Step 1.2: Initial System Setup

SSH into the new Ubuntu 24.04 server:

# Update system
sudo apt-get update
sudo apt-get upgrade -y

# Install basic tools
sudo apt-get install -y \
    git \
    curl \
    wget \
    vim \
    htop \
    net-tools \
    unzip \
    build-essential

# Set timezone (match old server)
sudo timedatectl set-timezone Europe/London  # Adjust as needed

# Configure firewall
sudo ufw allow 22/tcp    # SSH
sudo ufw allow 80/tcp    # HTTP
sudo ufw allow 443/tcp   # HTTPS
sudo ufw enable

Step 1.3: Install Docker

# Remove old Docker versions if any
sudo apt-get remove docker docker-engine docker.io containerd runc

# Update package index
sudo apt-get update

# Install prerequisites
sudo apt-get install -y \
    ca-certificates \
    curl \
    gnupg \
    lsb-release

# Add Docker's official 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

# Set up 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 Engine
sudo apt-get update
sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

# Verify installation
sudo docker --version
sudo docker compose version

# Add user to docker group
sudo usermod -aG docker $USER

# Apply group changes
newgrp docker

# Test Docker
docker run hello-world

# Enable Docker to start on boot
sudo systemctl enable docker

Step 1.4: Clone Repository

# Create application directory
sudo mkdir -p /srv/uplink
sudo chown $USER:$USER /srv/uplink

# Clone repository
cd /srv/uplink
git clone https://github.com/your-org/uplink.git app
cd app

# Checkout Docker branch
git checkout 1130-dockerise-uplink-and-switch-to-using-uv

# Verify Docker files exist
ls -l Dockerfile docker-compose.yml docker-compose.prod.yml

Step 1.5: Configure Environment

cd /srv/uplink/app

# Create .env from backup or template
# If you transferred backup:
cp /tmp/.env.backup.* .env

# Or copy from example:
cp .env.production.example .env

# Edit .env for Docker
nano .env

# Required changes for Docker:
# DATABASE_HOST=db
# DATABASE_PORT=3306
# REDIS_HOST=redis
# REDIS_PORT=6379
# HUEY_IMMEDIATE=False
# 
# Important: Update these from old server:
# - SECRET_KEY (escape $ as $$)
# - DATABASE_NAME, DATABASE_USER, DATABASE_PASSWORD
# - ALLOWED_HOSTS (add new server IP)
# - PRESTASHOP URLs
# - Any API keys/credentials

Step 1.6: Import Database

cd /srv/uplink/app

# Create directory for backups
mkdir -p /srv/uplink/backups

# If backup is in /tmp, move it
mv /tmp/uplink_db_full_*.sql.gz /srv/uplink/backups/

# Build and start only MySQL container first
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d db

# Wait for MySQL to be ready (30 seconds)
sleep 30

# Check MySQL is running
docker compose ps db

# Import database
gunzip < /srv/uplink/backups/uplink_db_full_*.sql.gz | docker compose exec -T db mysql -u root -p$MYSQL_ROOT_PASSWORD uplink

# Verify import
docker compose exec db mysql -u uplink_user -p uplink -e "SHOW TABLES;"
docker compose exec db mysql -u uplink_user -p uplink -e "SELECT COUNT(*) FROM catalogue_product;"

Step 1.7: Import Media Files

cd /srv/uplink/app

# Extract media backup
tar -xzf /srv/uplink/backups/media_full_*.tar.gz -C /tmp/

# Copy media files to project (adjust path if needed)
cp -r /tmp/srv/uplink/app/media/* ./media/

# Fix permissions
sudo chown -R www-data:www-data media/

# Verify
ls -la media/

Step 1.8: Build and Start All Services

cd /srv/uplink/app

# Build all Docker images
docker compose -f docker-compose.yml -f docker-compose.prod.yml build

# Start all services
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d

# Check all containers running
docker compose ps

# Should see: db, redis, web, daphne, huey all "running"

Step 1.9: Run Migrations and Setup

cd /srv/uplink/app

# Run migrations (should be no-op if database already has schema)
docker compose exec web python manage.py migrate

# Collect static files
docker compose exec web python manage.py collectstatic --noinput

# Create superuser (if needed for testing)
docker compose exec web python manage.py createsuperuser

# Run system check
docker compose exec web python manage.py check

Step 1.10: Configure Systemd for Auto-Start

# Create systemd service
sudo nano /etc/systemd/system/uplink-docker.service

# Add this content:
[Unit]
Description=Uplink Docker Compose Service
Requires=docker.service
After=docker.service network-online.target
Wants=network-online.target

[Service]
Type=oneshot
RemainAfterExit=yes
WorkingDirectory=/srv/uplink/app
ExecStart=/usr/bin/docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d
ExecStop=/usr/bin/docker compose down
User=$USER
Group=docker

[Install]
WantedBy=multi-user.target

# Enable and start
sudo systemctl daemon-reload
sudo systemctl enable uplink-docker.service
sudo systemctl start uplink-docker.service

# Verify
sudo systemctl status uplink-docker.service

Step 1.11: Setup Cron Jobs

# Install crontab for Docker
cd /srv/uplink/app
crontab crontab.production

# Verify crontab
crontab -l

# Note: Cron jobs will use docker compose exec -T

Step 1.12: Install and Configure Nginx (Optional)

If you want SSL/domain on the new server:

# Install Nginx
sudo apt-get install -y nginx

# Create Nginx config
sudo nano /etc/nginx/sites-available/uplink

# Basic config (adjust domain):
server {
    listen 80;
    server_name new-uplink.yourdomain.com;  # Temporary subdomain

    location / {
        proxy_pass http://127.0.0.1:8000;
        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;
    }

    location /ws/ {
        proxy_pass http://127.0.0.1:9000;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header Host $host;
    }

    location /static/ {
        alias /srv/uplink/app/static/;
    }

    location /media/ {
        alias /srv/uplink/app/media/;
    }
}

# Enable site
sudo ln -s /etc/nginx/sites-available/uplink /etc/nginx/sites-enabled/
sudo rm /etc/nginx/sites-enabled/default

# Test and reload
sudo nginx -t
sudo systemctl reload nginx

# Install Certbot for SSL (optional, for testing subdomain)
sudo apt-get install -y certbot python3-certbot-nginx
sudo certbot --nginx -d new-uplink.yourdomain.com

Phase 2: Running in Parallel

Step 2.1: DNS Configuration for Testing

Option A: Subdomain for New Server (Recommended)

Create a subdomain pointing to new server: - uplink.yourdomain.com → Old server (Ubuntu 18.04) - new-uplink.yourdomain.com → New server (Ubuntu 24.04)

This allows testing without affecting production.

Option B: Hosts File Testing

On your local machine:

# Edit /etc/hosts (Mac/Linux) or C:\Windows\System32\drivers\etc\hosts (Windows)
# Add:
NEW_SERVER_IP   test-uplink.local

Step 2.2: Testing Checklist

Test these on the new server via subdomain or test URL:

Basic Functionality: - [ ] Access admin panel: https://new-uplink.yourdomain.com/admin/ - [ ] Login with superuser credentials - [ ] View product list - [ ] View order list - [ ] Create test product - [ ] Create test order - [ ] Upload media file - [ ] View uploaded media

PrestaShop Integration: - [ ] Test PrestaShop product import (check extra_hosts working) - [ ] Test PrestaShop order import - [ ] Test product sync to PrestaShop

WebSocket/Real-time: - [ ] Test printer command (WebSocket via Daphne) - [ ] Test device communication

Background Tasks: - [ ] Check Huey processing tasks - [ ] Monitor Huey logs: docker compose logs -f huey

Cron Jobs: - [ ] Wait for cron execution (2 min intervals) - [ ] Check cron ran: docker compose logs web | grep prestashop

Performance: - [ ] Page load times acceptable - [ ] Database queries performing well - [ ] Check resource usage: docker stats

Step 2.3: Monitoring Both Servers

Old Server (Ubuntu 18.04):

# Check services
sudo systemctl status uplink uplink-daphne uplink-huey

# Monitor logs
sudo journalctl -u uplink -f

# Check resource usage
htop

New Server (Ubuntu 24.04):

# Check containers
docker compose ps

# Monitor logs
docker compose logs -f

# Check resource usage
docker stats
htop

Step 2.4: Data Sync Strategy (If Needed)

If production data changes significantly during testing:

Option A: Periodic Database Snapshots

On old server:

# Daily snapshot script
#!/bin/bash
mysqldump -u uplink_user -p uplink | gzip > /srv/uplink/backups/daily_sync_$(date +%Y%m%d).sql.gz
# Transfer to new server
scp /srv/uplink/backups/daily_sync_*.sql.gz user@new-server:/tmp/

On new server:

# Import latest snapshot (during low-traffic period)
docker compose stop web daphne huey
gunzip < /tmp/daily_sync_*.sql.gz | docker compose exec -T db mysql -u root -p uplink
docker compose start web daphne huey

Option B: Read-only Mode on New Server

Keep new server read-only for testing, sync data periodically.

Step 2.5: Validation Period

Run in parallel for 1-2 weeks minimum:

Week 1: Internal Testing - Team tests new server extensively - Identify any issues - Fix bugs/configuration - Performance tuning

Week 2: Limited Production Traffic - Route small percentage of traffic to new server (if using load balancer) - Or use for specific non-critical operations - Monitor closely for issues


Phase 3: Traffic Cutover

Step 3.1: Pre-Cutover Checklist

  • [ ] All tests passing on new server
  • [ ] Team comfortable with new server
  • [ ] Backups of both servers completed
  • [ ] Rollback plan documented and tested
  • [ ] Maintenance window scheduled
  • [ ] Stakeholders notified
  • [ ] DNS TTL reduced to 300s (5 min) 24 hours before cutover

Step 3.2: Final Data Sync

During maintenance window:

# On OLD server - stop application
sudo systemctl stop uplink uplink-daphne uplink-huey

# Create final backup
mysqldump -u uplink_user -p uplink | gzip > /srv/uplink/backups/final_cutover_$(date +%Y%m%d_%H%M%S).sql.gz

# Transfer to new server
scp /srv/uplink/backups/final_cutover_*.sql.gz user@new-server:/tmp/

# On NEW server - import final data
cd /srv/uplink/app
docker compose stop web daphne huey
gunzip < /tmp/final_cutover_*.sql.gz | docker compose exec -T db mysql -u root -p uplink
docker compose start web daphne huey

# Verify
docker compose exec web python manage.py check
docker compose ps

Step 3.3: DNS Cutover

Update DNS records to point to new server:

uplink.yourdomain.com → NEW_SERVER_IP
  • If using Cloudflare/DNS provider, update A record
  • Wait for DNS propagation (5-30 minutes with low TTL)
  • Monitor traffic shifting to new server

Step 3.4: SSL Certificate Transfer (If Needed)

If using Let's Encrypt on old server:

Option A: Generate new certificate on new server

# On new server
sudo certbot --nginx -d uplink.yourdomain.com

Option B: Transfer existing certificate

# On old server
sudo tar -czf /tmp/letsencrypt-backup.tar.gz /etc/letsencrypt/

# Transfer to new server
scp /tmp/letsencrypt-backup.tar.gz user@new-server:/tmp/

# On new server
sudo tar -xzf /tmp/letsencrypt-backup.tar.gz -C /
sudo systemctl reload nginx

Step 3.5: Post-Cutover Verification

Immediately after cutover:

  • [ ] Website accessible at production URL
  • [ ] SSL certificate valid
  • [ ] Login works
  • [ ] Can create/edit data
  • [ ] PrestaShop integration working
  • [ ] Cron jobs executing
  • [ ] No errors in logs
  • [ ] Monitor for 1-2 hours continuously

Monitor closely:

# New server
docker compose logs -f
docker stats
htop

# Check error logs specifically
docker compose logs web | grep -i error
docker compose logs daphne | grep -i error
docker compose logs huey | grep -i error

Step 3.6: Rollback Procedure (If Needed)

If critical issues arise:

# 1. Update DNS back to old server IP
#    (5-30 min for propagation)

# 2. On old server - restart services
sudo systemctl start uplink uplink-daphne uplink-huey
sudo systemctl status uplink

# 3. Verify old server working
curl -I http://localhost:8000/admin/

# 4. Communicate status to users

Phase 4: Decommissioning Old Server

Wait 2-4 weeks after successful cutover before decommissioning.

Step 4.1: Keep Old Server as Backup

For first 2-4 weeks: - Keep old server running but not serving traffic - Take regular backups from new server - Monitor new server stability - Document any issues and fixes

Step 4.2: Final Cleanup (After 2-4 Weeks)

When confident new server is stable:

# On old server
# 1. Stop services
sudo systemctl stop uplink uplink-daphne uplink-huey mysql redis

# 2. Disable auto-start
sudo systemctl disable uplink uplink-daphne uplink-huey mysql redis

# 3. Archive final backup
tar -czf /srv/uplink/final-archive-$(date +%Y%m%d).tar.gz /srv/uplink/

# 4. Transfer archive to safe storage
# 5. Consider destroying VM or repurposing

Step 4.3: Documentation Updates

Update all documentation with: - New server IP - New Docker-based deployment procedures - Updated troubleshooting for Docker - New backup/restore procedures


Troubleshooting Parallel Operation

Issue: Both Servers Accessing Same PrestaShop

Problem: Old and new servers both trying to sync with PrestaShop

Solution:

# On new server during testing, disable PrestaShop cron jobs
crontab -e
# Comment out prestashop_* commands temporarily
# Or set PrestaShop to read-only mode on new server

Issue: Database Conflicts

Problem: Changes made on old server not reflected on new

Solution: - Keep new server read-only during testing - Sync database nightly from old to new - Or route all writes to old server until cutover

Issue: SSL Certificate Conflicts

Problem: Can't get SSL for both servers with same domain

Solution: - Use subdomain for new server (new-uplink.yourdomain.com) - Get SSL for subdomain - After cutover, get SSL for main domain on new server

Issue: Session Conflicts

Problem: Users get logged out switching between servers

Solution: - Use different session cookie names - Or use only one server at a time - Or use shared session storage (Redis)


Cost and Resource Planning

Expected Costs (Example - AWS)

Small Setup: - t3.medium (2 vCPU, 4GB RAM): ~$30/month - 40GB SSD: ~$4/month - Data transfer: ~$5/month - Total: ~$39/month

Medium Setup: - t3.large (2 vCPU, 8GB RAM): ~$60/month - 80GB SSD: ~$8/month - Data transfer: ~$10/month - Total: ~$78/month

During Parallel Operation: - Old server + New server = ~2x normal costs - Run parallel for 2-4 weeks - Additional cost: ~$80-160 for testing period

Resource Monitoring

# On new server, monitor and adjust
docker stats

# If containers are resource-constrained, scale up VM
# If over-provisioned, can scale down after testing

Success Criteria

Migration complete when:

  • ✅ New server running Ubuntu 24.04 LTS
  • ✅ All services in Docker containers
  • ✅ Database migrated successfully
  • ✅ Media files migrated
  • ✅ DNS pointing to new server
  • ✅ SSL certificates valid
  • ✅ All functionality tested and working
  • ✅ Cron jobs executing correctly
  • ✅ Performance acceptable
  • ✅ No errors in logs for 24+ hours
  • ✅ Team trained on Docker deployment
  • ✅ Old server decommissioned (after validation period)

Congratulations! You're now running on Ubuntu 24.04 with Docker! 🎉🐳