Production Checklist#

Complete pre-deployment checklist to ensure your Construbot installation is production-ready.

Danger

DO NOT deploy to production without completing this checklist! Skipping items can lead to security vulnerabilities, data loss, or application downtime.

Overview#

This checklist covers all critical configuration items for production deployment. Work through each section systematically and verify each item before going live.

Estimated time: 2-4 hours for first deployment

Critical Settings#

DEBUG Mode#

 Set DEBUG=False in production

Verify:

# In .env
DJANGO_DEBUG=False

Danger

Never run production with DEBUG=True! This exposes:

  • Full tracebacks with code

  • SQL queries and database schema

  • Environment variables and secrets

  • Internal file paths

Test:

# In Django shell
from django.conf import settings
assert settings.DEBUG is False, "DEBUG must be False in production!"

SECRET_KEY#

 Generate unique, strong SECRET_KEY
✓ Never use development SECRET_KEY in production
✓ Store SECRET_KEY securely (not in version control)

Generate:

python -c "from django.core.management.utils import get_random_secret_key; print(get_random_secret_key())"

Set in .env:

DJANGO_SECRET_KEY=<generated-key-here>

Verify:

# Key should be 50+ characters, random
echo $DJANGO_SECRET_KEY | wc -c

ALLOWED_HOSTS#

 Configure ALLOWED_HOSTS with production domains
✓ Include all domains (www and root) Include load balancer hostname if applicable

Set:

# Comma-separated list
DJANGO_ALLOWED_HOSTS=example.com,www.example.com,construbot.example.com

Test:

# Should work
curl -H "Host: example.com" https://example.com

# Should fail with 400 Bad Request
curl -H "Host: malicious.com" https://example.com

Settings Module#

 Set DJANGO_SETTINGS_MODULE=construbot.config.settings.production

Verify:

echo $DJANGO_SETTINGS_MODULE
# Should show: construbot.config.settings.production

Security Configuration#

HTTPS/SSL#

✓ Obtain SSL certificate (Let's Encrypt recommended)
✓ Configure web server for HTTPS
✓ Redirect HTTP to HTTPS
✓ Enable HSTS

Environment variables:

DJANGO_SECURE_SSL_REDIRECT=True
DJANGO_SECURE_PROXY_SSL_HEADER=HTTP_X_FORWARDED_PROTO,https

HSTS (HTTP Strict Transport Security):

DJANGO_SECURE_HSTS_SECONDS=31536000  # 1 year
DJANGO_SECURE_HSTS_INCLUDE_SUBDOMAINS=True
DJANGO_SECURE_HSTS_PRELOAD=True

Warning

Test HSTS carefully! Once enabled with preload, browsers will refuse non-HTTPS connections even if you disable it.

Content Security#

 Enable content type sniffing protection
✓ Enable XSS filter
✓ Configure X-Frame-Options

Set:

DJANGO_SECURE_CONTENT_TYPE_NOSNIFF=True
DJANGO_SECURE_BROWSER_XSS_FILTER=True
X_FRAME_OPTIONS=DENY

Run Django security check:

python manage.py check --deploy

Expected output should have no warnings.

Database Configuration#

PostgreSQL Setup#

 Use PostgreSQL (not SQLite) Configure production database credentials
✓ Enable SSL connection
✓ Set up connection pooling
✓ Configure automated backups

DATABASE_URL:

# With SSL (recommended)
DATABASE_URL=postgresql://user:pass@host:5432/db?sslmode=require

# AWS RDS example
DATABASE_URL=postgresql://construbot:password@construbot.abc123.us-east-1.rds.amazonaws.com:5432/construbot?sslmode=require

Verify connection:

docker-compose run --rm django python manage.py dbshell
# Should connect without errors

Database Migrations#

 Run migrations on production database
✓ Verify all migrations applied
✓ Test rollback procedure

Apply migrations:

docker-compose run --rm django python manage.py migrate

Check status:

docker-compose run --rm django python manage.py showmigrations

All migrations should show [X] (applied).

Backup Strategy#

 Configure automated database backups
✓ Test backup restoration
✓ Set backup retention policy (7-30 days) Store backups off-site (different region)

AWS RDS:

  • Enable automated backups

  • Set backup window (low-traffic time)

  • Configure backup retention (7-35 days)

  • Enable Multi-AZ for high availability

Self-hosted:

# Set up daily backup cron job
0 2 * * * /opt/construbot/scripts/backup.sh

Static and Media Files#

Static Files#

 Collect static files
✓ Configure WhiteNoise
✓ Set up CDN (optional but recommended) Enable compression

Collect static files:

docker-compose run --rm django python manage.py collectstatic --no-input

Verify:

ls staticfiles/
# Should contain admin/, css/, js/, etc.

WhiteNoise is configured by default in construbot.config.settings.production.

Media Files#

 Configure media file storage (S3 recommended) Set up bucket with proper permissions
✓ Configure CORS if accessing from different domain
✓ Enable CDN for media files (optional)

Using AWS S3:

USE_S3=True
AWS_ACCESS_KEY_ID=your-access-key
AWS_SECRET_ACCESS_KEY=your-secret-key
AWS_STORAGE_BUCKET_NAME=construbot-media
AWS_S3_REGION_NAME=us-east-1

S3 Bucket Configuration:

  1. Create S3 bucket

  2. Block public access (use signed URLs)

  3. Configure CORS if needed

  4. Set up lifecycle policies for old files

Test upload:

# Upload test file through Django admin
# Verify file appears in S3 bucket

Email Configuration#

Email Service#

 Configure production email backend
✓ Set up email service (Mailgun, SendGrid, SES) Verify sender domain
✓ Configure SPF, DKIM, DMARC records

Using Mailgun (recommended):

DJANGO_EMAIL_BACKEND=anymail.backends.mailgun.EmailBackend
MAILGUN_API_KEY=your-api-key
MAILGUN_SENDER_DOMAIN=mg.example.com
DEFAULT_FROM_EMAIL=noreply@example.com
SERVER_EMAIL=errors@example.com

Using Amazon SES:

DJANGO_EMAIL_BACKEND=anymail.backends.amazon_ses.EmailBackend
AWS_ACCESS_KEY_ID=your-key
AWS_SECRET_ACCESS_KEY=your-secret
AWS_SES_REGION_NAME=us-east-1
DEFAULT_FROM_EMAIL=noreply@example.com

Email Testing#

 Send test email
✓ Verify delivery
✓ Check spam score
✓ Verify sender reputation

Test:

docker-compose run --rm django python manage.py shell
from django.core.mail import send_mail
send_mail(
    'Test Email',
    'This is a test from Construbot.',
    'noreply@example.com',
    ['your-email@example.com'],
)

Check your inbox and spam folder.

Caching and Performance#

Redis Configuration#

 Configure production Redis instance
✓ Enable password authentication
✓ Configure persistence (if needed) Set up Redis backups

Using AWS ElastiCache:

REDIS_URL=redis://:password@your-redis-endpoint.cache.amazonaws.com:6379/0

Self-hosted:

REDIS_URL=redis://:password@localhost:6379/0

Test connection:

redis-cli -h your-redis-host -a your-password ping
# Should respond: PONG

Celery Configuration#

 Configure Celery broker (Redis) Configure result backend
✓ Set up Celery worker as systemd service or Docker container
✓ Configure Celery beat for periodic tasks (if needed)

Start Celery worker:

# Add to docker-compose.yml or run as separate service
docker-compose up -d celeryworker

Verify:

docker-compose logs celeryworker
# Should show: "[INFO/MainProcess] Connected to redis://..."

Monitoring and Logging#

Error Tracking#

 Configure Sentry for error tracking
✓ Test error reporting
✓ Set up email alerts
✓ Configure error notifications

Set up Sentry:

SENTRY_DSN=https://your-key@sentry.io/project-id
SENTRY_ENVIRONMENT=production
SENTRY_SAMPLE_RATE=1.0

Test:

# In Django shell
from sentry_sdk import capture_message
capture_message("Test error from production")

Check Sentry dashboard for the message.

Application Logging#

 Configure log level (INFO or WARNING for production) Set up log rotation
✓ Configure centralized logging (optional)

Set log level:

LOG_LEVEL=INFO

Log aggregation options:

  • CloudWatch Logs (AWS)

  • Papertrail

  • Loggly

  • ELK Stack

Server Monitoring#

 Set up uptime monitoring
✓ Configure performance monitoring
✓ Set up disk space alerts
✓ Monitor database performance

Uptime monitoring:

  • UptimeRobot (free)

  • Pingdom

  • StatusCake

Performance monitoring:

  • New Relic

  • DataDog

  • Application Insights

Server monitoring:

  • CloudWatch (AWS)

  • DigitalOcean Monitoring

  • Prometheus + Grafana

Infrastructure#

Server Configuration#

 Configure firewall (allow only 22, 80, 443) Set up SSH key authentication
✓ Disable root login
✓ Configure automatic security updates
✓ Set up fail2ban (brute force protection)

Firewall (UFW):

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

SSH hardening:

# Edit /etc/ssh/sshd_config
PermitRootLogin no
PasswordAuthentication no
PubkeyAuthentication yes

# Restart SSH
sudo systemctl restart sshd

DNS Configuration#

 Point domain to server IP
✓ Configure A record (root and www) Set up DNS propagation (may take 24-48 hours) Configure MX records for email (if needed)

Example DNS records:

Type    Name    Value                   TTL
A       @       123.45.67.89            3600
A       www     123.45.67.89            3600
CNAME   api     example.com             3600

Verify:

dig example.com
dig www.example.com

Load Balancer (Optional)#

 Configure load balancer (if using multiple servers) Set up health checks
✓ Configure SSL termination
✓ Enable sticky sessions

Application Testing#

Functionality Tests#

 Create test user account
✓ Log in to application
✓ Create test project/contract
✓ Generate PDF report
✓ Upload test image
✓ Send test email
✓ Test API endpoints (if using)

Checklist:

  • [ ] Homepage loads

  • [ ] User login works

  • [ ] Dashboard displays correctly

  • [ ] Create new client (Contraparte)

  • [ ] Create new contract (Contrato)

  • [ ] Add concepts (Conceptos) to contract

  • [ ] Create estimate (Estimación)

  • [ ] Generate PDF document

  • [ ] Upload image to media library

  • [ ] Receive email notification

Performance Testing#

 Test page load times (<2 seconds) Test concurrent users (use load testing tool) Verify database query performance
✓ Check static file loading speed

Tools:

# Apache Bench (simple load test)
ab -n 1000 -c 10 https://example.com/

# Locust (advanced load testing)
pip install locust
# Create locustfile.py and run load tests

Security Testing#

 Run Django security check
✓ Scan for common vulnerabilities
✓ Test SQL injection protection
✓ Test XSS protection
✓ Verify CSRF protection

Django security check:

python manage.py check --deploy

OWASP ZAP scan:

# Download OWASP ZAP
# Run automated scan against your domain
# Review and fix any critical/high vulnerabilities

Final Verification#

Pre-Launch Checklist#

Critical (must be complete):

  • [ ] DEBUG=False

  • [ ] Unique, strong SECRET_KEY

  • [ ] ALLOWED_HOSTS configured

  • [ ] Production database configured

  • [ ] SSL certificate installed

  • [ ] Static files collected

  • [ ] Media storage configured

  • [ ] Email service working

  • [ ] Backups configured

  • [ ] Error tracking (Sentry) configured

Important (strongly recommended):

  • [ ] HSTS enabled

  • [ ] Cookie security enabled

  • [ ] Redis configured

  • [ ] Celery worker running

  • [ ] Monitoring set up

  • [ ] Logs configured

  • [ ] DNS configured

  • [ ] Firewall configured

Optional (nice to have):

  • [ ] CDN for static files

  • [ ] CDN for media files

  • [ ] Load balancer

  • [ ] Multiple servers

  • [ ] Read replicas

Launch Day#

 Schedule deployment during low-traffic period
✓ Announce maintenance window to users
✓ Have rollback plan ready
✓ Monitor errors closely for first 24 hours
✓ Be available for immediate fixes

Deployment steps:

  1. Backup current database

  2. Put application in maintenance mode

  3. Pull latest code

  4. Run migrations

  5. Collect static files

  6. Restart services

  7. Verify functionality

  8. Remove maintenance mode

  9. Monitor logs and Sentry

Post-Launch Monitoring#

First 24 hours:

  • Monitor Sentry for errors every hour

  • Check server resources (CPU, memory, disk)

  • Verify email delivery

  • Check application performance

  • Review user feedback

First week:

  • Daily error reviews

  • Performance optimization based on real usage

  • Database query optimization

  • Adjust caching as needed

Ongoing:

  • Weekly security updates

  • Monthly dependency updates

  • Quarterly disaster recovery drills

  • Continuous monitoring and optimization

Troubleshooting Common Issues#

Application Won’t Start#

Check:

  1. Environment variables set correctly

  2. Database accessible

  3. Redis accessible

  4. Migrations applied

  5. No syntax errors in settings

Debug:

docker-compose logs django
# Look for Python tracebacks

502 Bad Gateway#

Causes:

  • Gunicorn not running

  • Upstream server timeout

  • Nginx misconfigured

Fix:

# Restart services
docker-compose restart django

# Check Gunicorn logs
docker-compose logs django

500 Internal Server Error#

Debug:

  1. Check Sentry for error details

  2. Review Django logs

  3. Test database connection

  4. Verify all settings correct

# Test in shell
docker-compose run --rm django python manage.py shell

from django.conf import settings
print(settings.DATABASES)

Static Files Not Loading#

Fix:

# Recollect static files
docker-compose run --rm django python manage.py collectstatic --clear --no-input

# Verify STATIC_ROOT
docker-compose run --rm django python manage.py shell
>>> from django.conf import settings
>>> print(settings.STATIC_ROOT)

Documentation#

Required Documentation#

 Document server credentials (store securely) Document deployment process
✓ Create runbook for common operations
✓ Document rollback procedure
✓ Create disaster recovery plan

Runbook should include:

  • How to deploy updates

  • How to restart services

  • How to restore from backup

  • Common troubleshooting steps

  • Emergency contacts

See Also#