Phase 1: DNS Configuration
Configure DNS in your provider's website
- Add the following records (all sensitive values replaced):
| Type | Host | Value | TTL |
|---|---|---|---|
| A Record | @ |
<your_server_ip> |
Automatic |
| CNAME Record | www |
<your_root_domain> |
Automatic |
- Save the records
- Wait 5–30 minutes for DNS propagation
Verify DNS
# Check A record
dig <your_root_domain> +short
# Should return: <your_server_ip>
# Check WWW
dig www.<your_root_domain> +short
# Should return: <your_root_domain>.
Phase 2: DigitalOcean Droplet Setup
Create Droplet
- Sign in to your hosting provider
- Select Create → Droplets
- Region:
<your_region> - Image:
<your_os_version> - Size:
- Type: Basic
- CPU: Regular
- Plan:
<your_droplet_specs>
- Authentication: SSH keys or password
- Hostname:
<your_hostname> - Create Droplet
Initial Server Access
# SSH into server
ssh root@<your_server_ip>
# Update system
apt update && apt upgrade -y
# Set timezone (optional)
timedatectl set-timezone <your_timezone>
Create Non-Root User
# Create user
adduser <your_deploy_user>
# Add to sudo group
usermod -aG sudo <your_deploy_user>
# Allow sudo without password (optional)
echo "<your_deploy_user> ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers.d/<your_deploy_user>
# Switch to new user
su - <your_deploy_user>
Phase 3: SSH Key Setup
Generate SSH Key (Local Machine)
# Generate Ed25519 key
ssh-keygen -t ed25519 -C "<your_email>"
# Save to: ~/.ssh/<your_key_filename>
# Set passphrase: <your_passphrase>
Add Key to SSH Agent
# Start ssh-agent
eval "$(ssh-agent -s)"
# Add key
ssh-add ~/.ssh/<your_key_filename>
Add Key to GitHub
# Copy public key
cat ~/.ssh/<your_key_filename>.pub
# Go to your Git host:
# Settings → SSH keys → Add new key
# Paste your public key
Test GitHub Connection
ssh -T git@github.com
# Expected: confirmation of authentication for <your_github_username>
Phase 4: Docker Installation
Add Docker Repository
# Install prerequisites
sudo apt install -y ca-certificates curl gnupg lsb-release
# Create keyrings directory
sudo mkdir -p /etc/apt/keyrings
# Add Docker's GPG key
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
# Update package index
sudo apt update
# Install Docker
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
# Verify installation
docker --version
docker compose version
Configure Docker Permissions
# Add current user to docker group
sudo usermod -aG docker $USER
# Apply group changes
newgrp docker
# Test Docker (should run without sudo)
docker run hello-world
Phase 5: PostgreSQL Installation & Configuration
Install PostgreSQL
# Install packages
sudo apt install -y postgresql postgresql-contrib libpq-dev python3-dev
# Start service
sudo systemctl start postgresql
sudo systemctl enable postgresql
# Check status
sudo systemctl status postgresql
Create Database & User
# Switch to postgres user
sudo -u postgres psql
# Run SQL commands
CREATE DATABASE <your_db_name>;
CREATE USER <your_db_user> WITH PASSWORD '<your_db_password>';
# Set user parameters
ALTER ROLE <your_db_user> SET client_encoding TO 'utf8';
ALTER ROLE <your_db_user> SET default_transaction_isolation TO 'read committed';
ALTER ROLE <your_db_user> SET timezone TO 'UTC';
# Grant database privileges
GRANT ALL PRIVILEGES ON DATABASE <your_db_name> TO <your_db_user>;
# Exit psql
\q
Grant Schema Permissions (PostgreSQL 15+)
# Connect to database
sudo -u postgres psql <your_db_name>
# Grant schema permissions
GRANT ALL ON SCHEMA public TO <your_db_user>;
ALTER SCHEMA public OWNER TO <your_db_user>;
GRANT CREATE, USAGE ON SCHEMA public TO <your_db_user>;
# Exit
\q
Configure Network Access
Edit postgresql.conf
# Find PostgreSQL version
ls /etc/postgresql/
# Edit config
sudo nano /etc/postgresql/<your_pg_version>/main/postgresql.conf
Change:
# Before
# listen_addresses = 'localhost'
# After
listen_addresses = '*'
Edit pg_hba.conf
sudo nano /etc/postgresql/<your_pg_version>/main/pg_hba.conf
Add:
# Docker bridge networks
host all all 172.17.0.0/16 md5
host all all 172.18.0.0/16 md5
Restart PostgreSQL
sudo systemctl restart postgresql
# Verify listening on all interfaces
ss -tln | grep 5432
# Should show binding on 0.0.0.0:5432
Phase 6: Application Deployment
Clone Repository
# Create project directory
sudo mkdir -p <your_project_path>
sudo chown -R $USER:$USER <your_project_path>
# Navigate to directory
cd <your_project_path>
# Clone repository
git clone git@github.com:<your_repo_owner>/<your_repo_name>.git .
Create Environment File
vim .env
Add the following (all secrets replaced with placeholders):
# Django Settings
DJANGO_SECRET_KEY=<your_django_secret_key>
DJANGO_SETTINGS_MODULE=<your_settings_module>
# Database Configuration
DB_NAME=<your_db_name>
DB_USER=<your_db_user>
DB_PASSWORD=<your_db_password>
DB_HOST=<your_db_host>
DB_PORT=<your_db_port>
Generate secret key:
python3 -c 'from django.core.management.utils import get_random_secret_key; print(get_random_secret_key())'
Secure the file:
chmod 600 .env
Build & Start Docker Container
# Build image
docker compose build
# Start container
docker compose up -d
# Check status
docker compose ps
# View logs
docker compose logs -f web
Run Migrations & Setup
# Run database migrations
docker compose exec web python manage.py migrate
# Collect static files
docker compose exec web python manage.py collectstatic --noinput
# Create required pages
docker compose exec web python manage.py <your_page_sync_command>
# Create superuser
docker compose exec web python manage.py createsuperuser
Verify Container
# Test Django response
curl http://localhost:<your_app_port>
# Should return HTML
Phase 7: Nginx Configuration
Install Nginx
sudo apt install -y nginx
# Start service
sudo systemctl start nginx
sudo systemctl enable nginx
# Check status
sudo systemctl status nginx
Create Site Configuration
sudo nano <your_nginx_conf_path>
Add this configuration (all sensitive values replaced):
server {
listen 80;
server_name <your_domain> <your_domain_www>;
# Static files
location /static/ {
alias <your_project_path>/staticfiles/;
expires 1y;
add_header Cache-Control "public, immutable";
}
# Media files
location /media/ {
alias <your_project_path>/media/;
expires 7d;
}
# Max upload size
client_max_body_size 20M;
# Proxy to application
location / {
proxy_pass http://127.0.0.1:<your_app_port>;
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;
# Timeouts
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}
}
Enable Site
# Create symlink
sudo ln -s <your_nginx_conf_path> /etc/nginx/sites-enabled/
# Remove default site (optional)
sudo rm /etc/nginx/sites-enabled/default
# Test configuration
sudo nginx -t
# Reload Nginx
sudo systemctl reload nginx
Verify HTTP Access
# Test from server
curl -I http://<your_domain>
# Test from browser:
# http://<your_domain>
Phase 8: SSL Certificate (Let's Encrypt)
Install Certbot
sudo apt install -y certbot python3-certbot-nginx
Obtain Certificate
sudo certbot --nginx -d <your_domain> -d <your_domain_www>
# Prompts:
# 1. Enter <your_email>
# 2. Agree to TOS
# 3. Choose redirect: yes
Certbot will automatically:
- Generate certificates
- Update the Nginx server block
- Add HTTP → HTTPS redirect
- Insert SSL parameters
Verify SSL
# Test HTTPS
curl -I https://<your_domain>
# Check certificate metadata
echo | openssl s_client -servername <your_domain> -connect <your_domain>:443 2>/dev/null | openssl x509 -noout -dates
Auto-Renewal Setup
Certbot installs a systemd timer automatically.
# Check timer
sudo systemctl status certbot.timer
# Dry-run renewal
sudo certbot renew --dry-run
# Manual renewal (if required)
sudo certbot renew
Phase 9: Django Security Settings
Update Production Settings
Verify the production settings include secure values (all secrets and paths replaced):
# <your_project>/settings/production.py
SECURE_SSL_REDIRECT = True
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
# Optional HSTS (enable only after confirming HTTPS stability)
# SECURE_HSTS_SECONDS = <your_hsts_seconds>
# SECURE_HSTS_INCLUDE_SUBDOMAINS = True
# SECURE_HSTS_PRELOAD = True
Restart Application
docker compose restart web
Phase 10: Verification Checklist
Test All Endpoints
# Homepage
curl -I https://<your_domain>
# Expected: 200 OK
# Static file
curl -I https://<your_domain>/static/<your_static_file>
# Expected: 200 OK
# Media file
curl -I https://<your_domain>/media/<your_media_file>
# Expected: 200 OK
# Admin panel
curl -I https://<your_domain>/admin/
# Expected: 200 OK or redirect
# API
curl https://<your_domain>/api/<your_endpoint>/ | jq
# Expected: JSON output
Security Headers Check
curl -I https://<your_domain> | grep -E "(Strict-Transport|X-Frame|X-Content)"
Expected Headers:
Strict-Transport-Security: max-age=<your_hsts_value>; includeSubDomains
X-Frame-Options: DENY
X-Content-Type-Options: nosniff
Performance Test
# Measure response time
time curl -s https://<your_domain> > /dev/null
# Check static file caching
curl -I https://<your_domain>/static/<your_static_file> | grep -i cache
Common Commands Reference
Restart Services
| Service | Command | When to Use |
|---|---|---|
| Nginx | sudo systemctl restart nginx |
Config changes |
| Nginx (reload) | sudo systemctl reload nginx |
Minor tweaks |
| PostgreSQL | sudo systemctl restart postgresql |
DB config changes |
| Docker | sudo systemctl restart docker |
Engine issues |
| Application | docker compose restart web |
Code changes |
| Full restart | docker compose down && docker compose up -d |
Major updates |
Application Management
# View logs
docker compose logs -f web
# Execute management command
docker compose exec web python manage.py <your_command>
# Shell access
docker compose exec web bash
# Database shell
docker compose exec web python manage.py dbshell
Code Deployment
# Pull latest code
cd <your_project_path>
git pull origin <your_branch>
# Rebuild and restart
docker compose down
docker compose build
docker compose up -d
# Recollect static files
docker compose exec web rm -rf <your_static_dir>/*
docker compose exec web python manage.py collectstatic --noinput
# Run migrations
docker compose exec web python manage.py migrate
Full Reset
# WARNING: Destroys data
docker compose down
docker compose build
docker compose up -d
docker compose exec web rm -rf <your_static_dir>/*
docker compose exec web python manage.py collectstatic --noinput
docker compose exec web python manage.py migrate
docker compose exec web python manage.py createsuperuser
Troubleshooting
Issue: Container Won’t Start
# Check logs
docker compose logs web
# Common checks:
# 1. .env file exists and values are correct (<your_env_values>)
# 2. DB_HOST matches your setup (<your_db_host>)
# 3. PostgreSQL is running
sudo systemctl status postgresql
Issue: Static Files Not Loading
# Check permissions
ls -la <your_static_dir>
# Fix permissions
sudo chown -R $USER:$USER <your_static_dir>
chmod -R 755 <your_static_dir>
# Recollect static files
docker compose exec web python manage.py collectstatic --noinput --clear
# Validate Nginx config
sudo nginx -t
# Reload Nginx
sudo systemctl reload nginx
Issue: Database Connection Failed
# Check PostgreSQL status
sudo systemctl status postgresql
# Verify listener
ss -tln | grep 5432
# Test connection from container
docker compose exec web python manage.py check --database default
# Check pg_hba.conf
sudo nano /etc/postgresql/<your_pg_version>/main/pg_hba.conf
Issue: 502 Bad Gateway
# Check container health
docker compose ps
# Check logs
docker compose logs web
# Verify Gunicorn port
curl http://localhost:<your_app_port>
# Restart app
docker compose restart web
Issue: SSL Renewal Failed
# Check certbot timer
sudo systemctl status certbot.timer
# Manual renewal
sudo certbot renew --nginx
# Check certificate expiration
echo | openssl s_client -servername <your_domain> -connect <your_domain>:443 2>/dev/null | openssl x509 -noout -dates