Local Development Guide¶
Last Updated: March 6, 2026
This guide covers setting up and using Uplink for local development using Docker.
🚀 First time setting up? Start with FIRST_TIME_SETUP.md for step-by-step instructions.
Table of Contents¶
- Quick Start
- Development vs Production
- Local Development Workflow
- Common Development Tasks
- Troubleshooting
- Alternative: Non-Docker Development
Quick Start¶
Important: Environment-Aware Docker Compose¶
This project uses Docker Compose override files for clean separation: - docker-compose.yml = Base configuration (shared) - docker-compose.dev.yml = Development overrides (volume mounts, host MySQL access) - docker-compose.prod.yml = Production overrides (no volumes, DigitalOcean MySQL)
The deploy.sh script automatically detects your environment and uses the right files:
- Local dev: docker compose -f docker-compose.yml -f docker-compose.dev.yml up
- Production: docker compose -f docker-compose.yml -f docker-compose.prod.yml up
Your .env file stays configured for pipenv (the old way). Docker overrides what it needs.
Prerequisites¶
- Docker Desktop (Mac/Windows) or Docker Engine (Linux)
- Git
- Code editor (VS Code recommended)
Linux Docker installation:
# Install Docker
curl -fsSL https://get.docker.com -o get-docker.sh
sudo sh get-docker.sh
# Add your user to docker group (logout/login required after)
sudo usermod -aG docker $USER
# Install Docker Compose plugin
sudo apt install docker-compose-plugin
Initial Setup¶
# Clone repository
git clone git@github.com:SensationalSystems/uplink.git
cd uplink
# Ensure .env is configured accordingly
# Build Docker images
docker compose -f docker-compose.yml -f docker-compose.dev.yml build
# Start all services (dev mode)
docker compose -f docker-compose.yml -f docker-compose.dev.yml up -d
# Or use the deploy script (auto-detects environment)
./deploy.sh docker-up
# Run database migrations
docker compose exec web python manage.py migrate
# Collect static
docker compose exec web python manage.py collectstatic --noinput --clear
# Check all containers are running
docker compose ps
Your local Uplink is now running at: - Main application: http://localhost:8001 - WebSocket (Daphne): http://localhost:9000 - Redis: localhost:6381
Development vs Production¶
Dual-Mode Architecture¶
Key insight: Your .env file stays configured for pipenv (the old way), and Docker automatically overrides the necessary settings through docker-compose.dev.yml.
How it works:
# In .env (for pipenv):
DATABASE_HOST=127.0.0.1 # For pipenv mode (if you have local MySQL)
REDIS_HOST=127.0.0.1 # For pipenv mode (if you have local Redis)
# Docker dev mode automatically overrides to:
DATABASE_HOST=db # Use Docker MySQL container
DATABASE_PORT=3306 # Inside container port
DATABASE_USER=uplink # Docker MySQL user
DATABASE_PASS=admin # Docker MySQL password
REDIS_HOST=redis # Use Docker Redis container
This means:
- ✅ No need to edit .env when switching between pipenv and Docker
- ✅ Both modes can coexist (docker uses different internal network)
- ✅ Can test both old and new deployment methods
- ✅ Safe transition period with easy rollback
Important: After changing docker-compose files or switching modes, you must recreate containers (not just restart):
docker compose -f docker-compose.yml -f docker-compose.dev.yml down
docker compose -f docker-compose.yml -f docker-compose.dev.yml up -d
Switching Between Modes¶
To use Docker (new way - RECOMMENDED):
# Docker uses its own MySQL container (completely isolated)
docker compose -f docker-compose.yml -f docker-compose.dev.yml up -d
# Runs on: http://localhost:8001
# Database: localhost:3309 (fresh Docker MySQL)
To use pipenv (old way):
# Requires your own local MySQL and Redis running
# Configure .env with your local credentials
pipenv install
pipenv run honcho start
# Runs on: http://localhost:8000
Note: Both modes use different databases - they are completely isolated from each other.
Key Differences¶
| Aspect | Local Development | Production |
|---|---|---|
| Base File | docker-compose.yml |
docker-compose.yml + docker-compose.prod.yml |
| DEBUG | True (most services) |
False |
| Code Reloading | Yes (volume mounted) | No (code baked into image) |
| Database | Docker MySQL container (localhost:3309) | DigitalOcean MySQL cluster |
| Redis | Docker container | Docker container |
| Ports | 8001 (web), 9000 (daphne) | 80/443 (nginx proxy) |
| SSL | No SSL (plain HTTP) | Let's Encrypt SSL |
| nginx | Running but optional | Required (reverse proxy) |
| certbot | Running but not needed | Required (SSL renewal) |
| Restart Policy | unless-stopped |
always |
Local MySQL Database¶
Development uses a local Docker MySQL container (completely isolated from production):
- ✅ Safe for testing and development
- ✅ Fresh database on first startup
- ✅ Data persists in Docker volume between restarts
- ✅ Can be reset anytime with docker compose down -v
Connect from host machine (MySQL Workbench, etc.):
Host: 127.0.0.1
Port: 3309 (NOT 3306 to avoid conflicts)
Database: uplink
User: uplink
Password: admin
Root credentials:
User: root
Password: rootadmin
The database starts empty - you need to run migrations and optionally load fixture data.
Local Development Workflow¶
Starting Your Day¶
cd ~/CODING/uplink
# Start all services (if not already running)
docker compose up -d
# Check services are healthy
docker compose ps
# View logs (useful if something's wrong)
docker compose logs -f web
Making Code Changes¶
Django views, models, templates:
# 1. Edit files in your editor (VS Code, etc.)
# 2. Code automatically reloads (volume mounted)
# - Check browser/logs to verify changes loaded
# 3. If you added/changed models:
docker compose exec web python manage.py makemigrations
docker compose exec web python manage.py migrate
Python dependencies (pyproject.toml):
# 1. Edit pyproject.toml to add/remove dependencies
# 2. Rebuild Docker image
docker compose build web
# 3. Restart service
docker compose restart web daphne huey
Docker configuration (Dockerfile, docker-compose.yml):
# 1. Edit Dockerfile or docker-compose.yml
# 2. Rebuild and restart
docker compose down
docker compose up -d --build
Ending Your Day¶
# Stop all containers (preserves data)
docker compose down
# Or leave running (uses minimal resources when idle)
# Containers will auto-start next reboot
Common Development Tasks¶
Running Management Commands¶
# General pattern
docker compose exec web python manage.py [command]
# Examples:
docker compose exec web python manage.py shell
docker compose exec web python manage.py check
docker compose exec web python manage.py test
docker compose exec web python manage.py dbshell
docker compose exec web python manage.py createsuperuser
Database Operations¶
# Run migrations
docker compose exec web python manage.py migrate
# Create new migration
docker compose exec web python manage.py makemigrations
# Reset database (CAUTION: Deletes all data!)
docker compose exec web python manage.py flush
# Backup local Docker database
docker compose exec db mysqldump -u uplink -padmin uplink > backup.sql
# Restore from backup
docker compose exec -T db mysql -u uplink -padmin uplink < backup.sql
# Load fixture data
docker compose exec web python manage.py loaddata fixtures/initial_data.json
Syncing Production Database to Local¶
The streamlined way to update your local database with production data:
What this script does: 1. Dumps the production database from DigitalOcean MySQL 2. Optionally sanitizes sensitive data (passwords, API keys) 3. Imports into your local Docker MySQL container 4. Runs migrations to ensure schema is up-to-date
First-time setup - Add production credentials to your .env:
# Production Database Connection (for sync-prod-db.sh)
PROD_DATABASE_HOST=your-cluster.db.ondigitalocean.com
PROD_DATABASE_PORT=25060
PROD_DATABASE_NAME=uplink
PROD_DATABASE_USER=doadmin
PROD_DATABASE_PASS=your-password
PROD_DATABASE_SSL_CA=/path/to/ca-certificate.crt # Optional, for SSL
Manual method (if you prefer):
# 1. Dump from production (on production server or with credentials)
mysqldump -h your-db-host.com -P 25060 -u doadmin -p uplink > prod_dump.sql
# 2. Import to local Docker MySQL
docker compose exec -T db mysql -u uplink -padmin uplink < prod_dump.sql
# 3. Run migrations
docker compose exec web python manage.py migrate
Tips: - Run this weekly or whenever you need current production data - Always sanitize data to avoid accidentally using production API keys - Your local database is completely isolated from production - Make a backup before syncing if you have local test data you want to keep
PrestaShop Integration Testing¶
# Import products from PrestaShop
docker compose exec web python manage.py prestashop_import_products
# Import orders
docker compose exec web python manage.py prestashop_import_orders
# Check integration status
docker compose logs -f web | grep prestashop
Background Tasks (Huey)¶
# View Huey worker logs
docker compose logs -f huey
# Restart Huey worker
docker compose restart huey
# Check task queue (Redis)
docker compose exec redis redis-cli KEYS "huey*"
Static Files¶
# Collect static files
docker compose exec web python manage.py collectstatic --noinput
# Clear static files cache
rm -rf /home/hannah/CODING/uplink/static/*
docker compose exec web python manage.py collectstatic --noinput
Debugging¶
Viewing Real-Time Logs (like Honcho):
# View real-time logs from all key services (RECOMMENDED)
docker compose logs -f web daphne huey
# View all services
docker compose logs -f
# View specific service logs
docker compose logs -f web # Django/Gunicorn requests
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 daphne huey
# With timestamps
docker compose logs -f --timestamps web daphne huey
💡 Tip: The web service logs all HTTP requests in real-time (GET, POST, etc.) just like Django's development server. You'll see: - Request URLs and methods - Response status codes - Client IP addresses - Response times
Other debugging commands:
# Access container shell
docker compose exec web bash
# Check environment variables
docker compose exec web env | grep DATABASE
# Test database connection
docker compose exec web python -c "import django; django.setup(); from django.db import connection; connection.ensure_connection(); print('DB connected!')"
Important: If you change the command: in docker-compose files, you must recreate the container:
# Won't work - restart doesn't apply command changes
docker compose restart web
# Correct way - recreate container with new command
docker compose -f docker-compose.yml -f docker-compose.dev.yml up -d web
Container Management¶
# Stop containers
docker compose down
# Stop and remove volumes (nuclear option)
docker compose down -v
# Restart specific service
docker compose restart web
# Rebuild specific service
docker compose build web
docker compose up -d web
# Force recreate container
docker compose up -d --force-recreate web
# View container resource usage
docker stats
Git Workflow with Docker¶
# Pull latest changes
git pull origin main
# Rebuild if dependencies changed
docker compose build
# Restart to apply code changes
docker compose restart web daphne huey
# Run new migrations
docker compose exec web python manage.py migrate
Troubleshooting¶
"Port is already allocated"¶
Error: Bind for 0.0.0.0:8001 failed: port is already allocated
Solution:
# Check what's using the port
sudo lsof -i :8001
# Option 1: Stop the conflicting process
sudo kill -9 <PID>
# Option 2: Change port in docker-compose.yml
# Edit: ports: - "8002:8000" # Changed 8001 to 8002
Containers Keep Restarting¶
# Check logs for errors
docker compose logs web
# Common issues:
# 1. Database connection failed
docker compose exec web python manage.py check --database default
# 2. Missing environment variables
docker compose exec web env | grep -E "DATABASE|REDIS|SECRET"
# 3. Python errors in code
docker compose logs web | grep -i error
Environment Variables Not Taking Effect¶
Issue: Changed docker-compose.dev.yml or .env but containers still using old values
Solution:
# Environment changes require container recreation (restart is not enough)
docker compose -f docker-compose.yml -f docker-compose.dev.yml down
docker compose -f docker-compose.yml -f docker-compose.dev.yml up -d
# Verify environment loaded correctly
docker compose exec web env | grep DATABASE
Why: Docker containers bake in environment variables at creation time. docker compose restart keeps the same container with old env vars. You need down + up to recreate containers with new environment.
Code Changes Not Reflecting¶
Issue: Made code changes but browser still shows old version
Solutions:
# 1. Check volume is mounted (should see .:/app)
docker compose config | grep volumes -A 5
# 2. Hard refresh browser (Ctrl+Shift+R)
# 3. Clear browser cache
# 4. Restart web container
docker compose restart web
# 5. Check file permissions
ls -la /home/hannah/CODING/uplink
# 6. Verify file actually saved (check git status)
git status
Database Connection Errors¶
Error: Can't connect to MySQL server
Solutions:
# 1. Check .env has correct database credentials
cat .env | grep DATABASE
# 2. Verify Docker MySQL is running
docker compose ps db
# 3. Test connection from container
docker compose exec web python manage.py dbshell
# 4. Check Docker MySQL logs
docker compose logs db
# 5. Restart MySQL container
docker compose restart db
Redis Connection Errors¶
Error: Error connecting to Redis
Solutions:
# 1. Check Redis is running
docker compose ps redis
# 2. Test Redis connection
docker compose exec redis redis-cli ping
# Should return: PONG
# 3. Check environment variable
docker compose exec web env | grep REDIS_URL
# 4. Restart Redis
docker compose restart redis
Out of Disk Space¶
# Check disk usage
df -h
# Clean Docker resources
docker system prune -a --volumes -f
# Remove old containers
docker container prune -f
# Remove old images
docker image prune -a -f
# Remove unused volumes
docker volume prune -f
Complete Reset¶
When nothing else works:
# Stop everything
docker compose down -v
# Remove all containers, images, 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
# Create superuser
docker compose exec web python manage.py createsuperuser
Alternative: Non-Docker Development¶
If you prefer developing without Docker (faster iteration, direct IDE debugging):
Setup¶
# Install uv
pip install uv
# Create virtual environment
uv venv
source .venv/bin/activate
# Install dependencies
uv pip install -e ".[dev]"
# Copy environment file
cp .env.dev .env
# Run migrations
python manage.py migrate
# Create superuser
python manage.py createsuperuser
Running¶
# Option 1: Run everything with Honcho (Procfile)
honcho start
# Option 2: Run services individually (separate terminals)
# Terminal 1: Django
python manage.py runserver
# Terminal 2: Daphne (WebSockets)
daphne -p 9000 uplink.asgi:application
# Terminal 3: Huey (background tasks)
python manage.py run_huey
# Terminal 4: Redis (needs Docker or local install)
docker run -d -p 6379:6379 redis:7-alpine
Pros/Cons¶
Non-Docker Pros: - ✅ Faster startup (no container overhead) - ✅ Direct IDE debugging - ✅ Native file system performance - ✅ Easier to use system tools
Non-Docker Cons: - ❌ Environment differs from production - ❌ More manual setup required - ❌ Dependency conflicts possible - ❌ Harder to share exact environment with team
Recommendation: Use Docker for most development. Use non-Docker for debugging complex issues or performance profiling.
Additional Resources¶
Tips & Best Practices¶
Development Best Practices¶
-
Always use branches: Never commit directly to
main -
Run tests before committing:
-
Check for migrations:
-
Keep Docker images updated:
-
Monitor logs during development:
Performance Tips¶
-
Use Docker BuildKit for faster builds:
-
Use Docker's layer caching: Order Dockerfile commands from least to most frequently changing
-
Limit log output:
-
Use healthchecks: Already configured in docker-compose.yml for redis
Security Reminders¶
- ⚠️ Never commit
.envfile (already in.gitignore) - ⚠️ Use different SECRET_KEY for local and production
- ⚠️ Local dev database is isolated (Docker MySQL on localhost:3309)
- ⚠️ Production uses DigitalOcean MySQL (completely separate from dev)
- ⚠️ Don't expose ports publicly (Docker binds to localhost by default)
Quick Reference¶
Essential Commands¶
# Start/stop
docker compose up -d # Start all services
docker compose down # Stop all services
docker compose restart web # Restart specific service
# Logs (real-time, like Honcho)
docker compose logs -f web daphne huey # All key services (RECOMMENDED)
docker compose logs -f web # Just web/HTTP requests
docker compose logs --tail=50 web # Last 50 lines
# Execute commands
docker compose exec web [command] # Run command in web container
docker compose exec web bash # Shell access
# Database
docker compose exec web python manage.py migrate
docker compose exec web python manage.py makemigrations
docker compose exec web python manage.py dbshell
./sync-prod-db.sh # Sync production DB to local
# Status
docker compose ps # Container status
docker stats # Resource usage
docker compose config # Validate compose file
# Cleanup
docker compose down -v # Remove containers + volumes
docker system prune -a --volumes -f # Nuclear cleanup
Common URLs¶
- Local application: http://localhost:8001
- Django admin: http://localhost:8001/admin/
- API root: http://localhost:8001/api/v1/
- WebSocket (Daphne): ws://localhost:9000
- Redis: localhost:6381
- MySQL (from host): localhost:3309
Database Connection (MySQL Workbench, etc.)¶
Host: 127.0.0.1
Port: 3309
Database: uplink
User: uplink
Password: admin
Root access:
User: root
Password: rootadmin
File Locations¶
- Code:
/home/hannah/CODING/uplink/ - Docker files:
Dockerfile,docker-compose.yml,docker-compose.prod.yml - Environment:
.env(local),.env.dev(template) - Logs: Docker logs (not file-based in dev)
- Static files:
static/(collected files) - Media files:
media/(uploaded files)
Questions or issues? Check Troubleshooting section or create a GitHub issue.