# Docker Deployment Guide This guide provides comprehensive instructions for deploying the Multi-Tenant SaaS Platform using Docker containers. ## Prerequisites ### System Requirements - **OS**: Linux (Ubuntu 20.04+ recommended) - **RAM**: 8GB+ (16GB recommended for production) - **CPU**: 4+ cores (8+ recommended for production) - **Storage**: 50GB+ (100GB+ recommended for production) - **Docker**: 20.10+ - **Docker Compose**: 1.29+ - **SSL**: Valid SSL certificate for production ### Malaysian Requirements - **Domain**: Malaysian domain name - **SSL**: Valid SSL certificate - **Data Center**: Malaysian cloud region recommended - **Payment Gateway**: Malaysian payment provider credentials - **Compliance**: PDPA compliance configuration ## Quick Start ### 1. Install Docker and Docker Compose ```bash # Update system packages sudo apt update && sudo apt upgrade -y # Install Docker curl -fsSL https://get.docker.com -o get-docker.sh sudo sh get-docker.sh # Add user to docker group sudo usermod -aG docker $USER # Install Docker Compose sudo curl -L "https://github.com/docker/compose/releases/download/1.29.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose sudo chmod +x /usr/local/bin/docker-compose # Log out and log back in for group changes to take effect newgrp docker ``` ### 2. Clone Repository ```bash git clone https://github.com/your-org/multi-tenant-saas.git cd multi-tenant-saas ``` ### 3. Configure Environment ```bash # Copy environment files cp .env.example .env cp frontend/.env.example frontend/.env # Edit environment variables vim .env vim frontend/.env ``` ### 4. Start Services ```bash # Build and start all services docker-compose up -d --build # View logs docker-compose logs -f ``` ## Production Deployment ### 1. Production Configuration ```bash # Create production directory mkdir -p /opt/multi-tenant-saas cd /opt/multi-tenant-saas # Clone repository git clone https://github.com/your-org/multi-tenant-saas.git . # Copy production compose file cp docker-compose.yml docker-compose.override.yml ``` ### 2. Production Docker Compose ```bash # Create production docker-compose.yml vim docker-compose.yml ``` ```yaml version: '3.8' services: # PostgreSQL Database db: image: postgres:13 environment: POSTGRES_DB: multi_tenant_saas_prod POSTGRES_USER: multi_tenant_prod_user POSTGRES_PASSWORD: ${DB_PASSWORD} volumes: - postgres_data:/var/lib/postgresql/data - ./backups:/backups networks: - app-network restart: unless-stopped healthcheck: test: ["CMD-SHELL", "pg_isready -U multi_tenant_prod_user -d multi_tenant_saas_prod"] interval: 30s timeout: 10s retries: 3 # Redis Cache redis: image: redis:6-alpine command: redis-server --appendonly yes volumes: - redis_data:/data networks: - app-network restart: unless-stopped healthcheck: test: ["CMD", "redis-cli", "ping"] interval: 30s timeout: 10s retries: 3 # Django Backend backend: build: context: . dockerfile: Dockerfile.backend environment: - DEBUG=False - SECRET_KEY=${SECRET_KEY} - DATABASE_URL=postgresql://multi_tenant_prod_user:${DB_PASSWORD}@db:5432/multi_tenant_saas_prod - REDIS_URL=redis://redis:6379/0 - ALLOWED_HOSTS=${DOMAIN_NAME},www.${DOMAIN_NAME} - CORS_ALLOWED_ORIGINS=https://${DOMAIN_NAME},https://www.${DOMAIN_NAME} - TIMEZONE=Asia/Kuala_Lumpur - CURRENCY=MYR - SST_RATE=0.06 volumes: - staticfiles:/app/staticfiles - media:/app/media - logs:/app/logs depends_on: db: condition: service_healthy redis: condition: service_healthy networks: - app-network restart: unless-stopped healthcheck: test: ["CMD", "python", "manage.py", "health"] interval: 30s timeout: 10s retries: 3 # React Frontend frontend: build: context: . dockerfile: Dockerfile.frontend environment: - REACT_APP_API_URL=https://${DOMAIN_NAME}/api - REACT_APP_WS_URL=wss://${DOMAIN_NAME}/ws - REACT_APP_ENVIRONMENT=production depends_on: - backend networks: - app-network restart: unless-stopped # Nginx Reverse Proxy nginx: image: nginx:alpine ports: - "80:80" - "443:443" volumes: - ./nginx.conf:/etc/nginx/nginx.conf:ro - ./ssl:/etc/nginx/ssl:ro - staticfiles:/var/www/static - media:/var/www/media depends_on: - backend - frontend networks: - app-network restart: unless-stopped # Celery Worker celery: build: context: . dockerfile: Dockerfile.backend command: celery -A config worker --loglevel=info environment: - DEBUG=False - SECRET_KEY=${SECRET_KEY} - DATABASE_URL=postgresql://multi_tenant_prod_user:${DB_PASSWORD}@db:5432/multi_tenant_saas_prod - REDIS_URL=redis://redis:6379/0 volumes: - logs:/app/logs depends_on: db: condition: service_healthy redis: condition: service_healthy networks: - app-network restart: unless-stopped # Celery Beat (Scheduler) celery-beat: build: context: . dockerfile: Dockerfile.backend command: celery -A config beat --loglevel=info environment: - DEBUG=False - SECRET_KEY=${SECRET_KEY} - DATABASE_URL=postgresql://multi_tenant_prod_user:${DB_PASSWORD}@db:5432/multi_tenant_saas_prod - REDIS_URL=redis://redis:6379/0 volumes: - logs:/app/logs depends_on: db: condition: service_healthy redis: condition: service_healthy networks: - app-network restart: unless-stopped # Flower (Celery Monitoring) flower: image: mher/flower:0.9.7 environment: - CELERY_BROKER_URL=redis://redis:6379/0 - FLOWER_PORT=5555 ports: - "5555:5555" depends_on: - redis networks: - app-network restart: unless-stopped volumes: postgres_data: driver: local redis_data: driver: local staticfiles: driver: local media: driver: local logs: driver: local networks: app-network: driver: bridge ipam: config: - subnet: 172.20.0.0/16 ``` ### 3. Create Dockerfiles #### Backend Dockerfile ```bash # Create backend Dockerfile vim Dockerfile.backend ``` ```dockerfile # Backend Dockerfile FROM python:3.9-slim # Install system dependencies RUN apt-get update && apt-get install -y \ gcc \ libpq-dev \ && rm -rf /var/lib/apt/lists/* # Create app user RUN groupadd -r app && useradd -r -g app app # Set working directory WORKDIR /app # Copy requirements first for better layer caching COPY requirements.txt . # Install Python dependencies RUN pip install --no-cache-dir -r requirements.txt # Copy application code COPY . . # Create directories RUN mkdir -p staticfiles media logs # Set correct permissions RUN chown -R app:app /app # Switch to app user USER app # Collect static files RUN python manage.py collectstatic --noinput # Expose port EXPOSE 8000 # Health check HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \ CMD python manage.py health # Start application CMD ["gunicorn", "--bind", "0.0.0.0:8000", "--workers", "4", "config.wsgi:application"] ``` #### Frontend Dockerfile ```bash # Create frontend Dockerfile vim Dockerfile.frontend ``` ```dockerfile # Frontend Dockerfile FROM node:16-alpine as build # Set working directory WORKDIR /app # Copy package files COPY frontend/package*.json ./ # Install dependencies RUN npm ci --only=production # Copy source code COPY frontend/ . # Build application RUN npm run build # Production stage FROM nginx:alpine # Copy built application COPY --from=build /app/build /usr/share/nginx/html # Copy nginx configuration COPY nginx-default.conf /etc/nginx/conf.d/default.conf # Expose port EXPOSE 80 # Start nginx CMD ["nginx", "-g", "daemon off;"] ``` ### 4. Nginx Configuration ```bash # Create nginx configuration vim nginx.conf ``` ```nginx events { worker_connections 1024; } http { upstream backend { server backend:8000; } upstream frontend { server frontend:80; } # HTTP redirect to HTTPS server { listen 80; server_name ${DOMAIN_NAME} www.${DOMAIN_NAME}; return 301 https://$server_name$request_uri; } # HTTPS server server { listen 443 ssl http2; server_name ${DOMAIN_NAME} www.${DOMAIN_NAME}; # SSL configuration ssl_certificate /etc/nginx/ssl/cert.pem; ssl_certificate_key /etc/nginx/ssl/key.pem; ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384; ssl_prefer_server_ciphers off; # Security headers add_header X-Frame-Options DENY; add_header X-Content-Type-Options nosniff; add_header X-XSS-Protection "1; mode=block"; add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; # Static files location /static/ { alias /var/www/static/; expires 1y; add_header Cache-Control "public, immutable"; } # Media files location /media/ { alias /var/www/media/; expires 1y; add_header Cache-Control "public"; } # Frontend location / { proxy_pass http://frontend; 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; } # API location /api/ { proxy_pass http://backend; 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; proxy_connect_timeout 30s; proxy_send_timeout 30s; proxy_read_timeout 30s; } # WebSocket location /ws/ { proxy_pass http://backend; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; 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; } # Health check location /health/ { proxy_pass http://backend; access_log off; } } } ``` ### 5. Environment Variables ```bash # Create .env file vim .env ``` ```bash # Database DB_PASSWORD=your-secure-database-password # Application SECRET_KEY=your-production-secret-key-here DOMAIN_NAME=your-domain.com # Malaysian Configuration TIMEZONE=Asia/Kuala_Lumpur CURRENCY=MYR SST_RATE=0.06 DEFAULT_COUNTRY=Malaysia # Payment Gateways TOUCH_N_GO_API_KEY=your-touch-n-go-api-key TOUCH_N_GO_SECRET=your-touch-n-go-secret GRABPAY_API_KEY=your-grabpay-api-key GRABPAY_SECRET=your-grabpay-secret # Email Configuration EMAIL_HOST=smtp.your-email-provider.com EMAIL_PORT=587 EMAIL_HOST_USER=your-email@domain.com EMAIL_HOST_PASSWORD=your-email-password # Security DJANGO_SETTINGS_MODULE=config.production SECURE_BROWSER_XSS_FILTER=True SECURE_CONTENT_TYPE_NOSNIFF=True SECURE_HSTS_INCLUDE_SUBDOMAINS=True SECURE_HSTS_PRELOAD=True SECURE_HSTS_SECONDS=31536000 SECURE_SSL_REDIRECT=True SESSION_COOKIE_SECURE=True CSRF_COOKIE_SECURE=True ``` ## SSL Certificate Setup ### 1. Using Let's Encrypt ```bash # Create SSL directory mkdir -p ssl # Obtain SSL certificate docker run --rm -p 80:80 -p 443:443 \ -v $(pwd)/ssl:/etc/letsencrypt \ certbot/certbot certonly --standalone \ -d your-domain.com -d www.your-domain.com # Copy certificates cp ssl/live/your-domain.com/fullchain.pem ssl/cert.pem cp ssl/live/your-domain.com/privkey.pem ssl/key.pem # Set up auto-renewal # Create renewal script vim scripts/renew-ssl.sh ``` ```bash #!/bin/bash docker run --rm \ -v $(pwd)/ssl:/etc/letsencrypt \ certbot/certbot renew --nginx # Copy renewed certificates cp ssl/live/your-domain.com/fullchain.pem ssl/cert.pem cp ssl/live/your-domain.com/privkey.pem ssl/key.pem # Reload nginx docker-compose exec nginx nginx -s reload ``` ```bash # Make script executable chmod +x scripts/renew-ssl.sh # Add to crontab 0 0 1 * * /opt/multi-tenant-saas/scripts/renew-ssl.sh ``` ## Malaysian Configuration ### 1. Payment Gateway Setup ```bash # Create payment configuration vim config/payments.py ``` ```python PAYMENT_GATEWAYS = { 'touch_n_go': { 'enabled': True, 'environment': 'production', 'api_key': os.environ.get('TOUCH_N_GO_API_KEY'), 'secret': os.environ.get('TOUCH_N_GO_SECRET'), 'merchant_id': os.environ.get('TOUCH_N_GO_MERCHANT_ID'), 'currency': 'MYR', 'country': 'MY', }, 'grabpay': { 'enabled': True, 'environment': 'production', 'api_key': os.environ.get('GRABPAY_API_KEY'), 'secret': os.environ.get('GRABPAY_SECRET'), 'merchant_id': os.environ.get('GRABPAY_MERCHANT_ID'), 'currency': 'MYR', 'country': 'MY', }, 'online_banking': { 'enabled': True, 'banks': [ {'code': 'maybank2u', 'name': 'Maybank2u'}, {'code': 'cimb_clicks', 'name': 'CIMB Clicks'}, {'code': 'rhbb', 'name': 'RHB Banking'}, ], } } ``` ### 2. SST Configuration ```bash # Create SST configuration vim config/sst.py ``` ```python SST_SETTINGS = { 'enabled': True, 'rate': 0.06, 'registration_number': os.environ.get('SST_REGISTRATION_NUMBER'), 'currency': 'MYR', 'invoice_prefix': 'SST', 'tax_inclusive': True, 'exempt_categories': [ 'education', 'healthcare', 'financial_services', ], } ``` ## Production Deployment Steps ### 1. Build and Start Services ```bash # Build images docker-compose build # Start services docker-compose up -d # Check status docker-compose ps ``` ### 2. Initialize Database ```bash # Run migrations docker-compose exec backend python manage.py migrate # Create superuser docker-compose exec backend python manage.py createsuperuser # Load initial data docker-compose exec backend python manage.py load_initial_data ``` ### 3. Verify Deployment ```bash # Check logs docker-compose logs backend docker-compose logs frontend # Health check curl -f https://your-domain.com/health/ # Access application # Frontend: https://your-domain.com # API: https://your-domain.com/api # Admin: https://your-domain.com/admin # Flower: https://your-domain.com:5555 ``` ## Monitoring and Logging ### 1. Container Monitoring ```bash # View container status docker-compose ps # View resource usage docker stats # View logs docker-compose logs -f backend docker-compose logs -f frontend docker-compose logs -f celery ``` ### 2. Health Checks ```bash # Application health curl -f https://your-domain.com/health/ # Database health docker-compose exec db pg_isready -U multi_tenant_prod_user -d multi_tenant_saas_prod # Redis health docker-compose exec redis redis-cli ping ``` ### 3. Log Management ```bash # View aggregated logs docker-compose logs --tail=100 # Access container logs docker-compose exec backend tail -f logs/app.log # Configure log rotation # Add to docker-compose.yml for each service logging: driver: "json-file" options: max-size: "10m" max-file: "3" ``` ## Backup and Recovery ### 1. Database Backup ```bash # Create backup script vim scripts/backup-database.sh ``` ```bash #!/bin/bash BACKUP_DIR="/opt/multi-tenant-saas/backups" DATE=$(date +%Y%m%d_%H%M%S) BACKUP_FILE="$BACKUP_DIR/database_backup_$DATE.sql" # Create backup directory mkdir -p $BACKUP_DIR # Create database backup docker-compose exec -T db pg_dump -U multi_tenant_prod_user -d multi_tenant_saas_prod > $BACKUP_FILE # Compress backup gzip $BACKUP_FILE # Keep only last 30 days of backups find $BACKUP_DIR -name "*.sql.gz" -mtime +30 -delete echo "Database backup completed: $BACKUP_FILE.gz" ``` ```bash # Make script executable chmod +x scripts/backup-database.sh # Set up cron job for daily backups 0 2 * * * /opt/multi-tenant-saas/scripts/backup-database.sh ``` ### 2. Volume Backup ```bash # Backup all volumes docker run --rm \ -v multi_tenant_saas_postgres_data:/source \ -v $(pwd)/backups:/backup \ alpine tar czf /backup/postgres_data_$(date +%Y%m%d).tar.gz -C /source . docker run --rm \ -v multi_tenant_saas_redis_data:/source \ -v $(pwd)/backups:/backup \ alpine tar czf /backup/redis_data_$(date +%Y%m%d).tar.gz -C /source . ``` ## Security Hardening ### 1. Container Security ```bash # Update base images regularly docker-compose pull # Use specific image versions # Avoid using 'latest' tag # Run containers as non-root users # Already configured in Dockerfiles # Limit container capabilities # Add to docker-compose.yml cap_drop: - ALL cap_add: - CHOWN - SETGID - SETUID - NET_BIND_SERVICE ``` ### 2. Network Security ```bash # Use custom networks # Already configured in docker-compose.yml # Isolate sensitive services # Database is not exposed to host # Use internal networks for service communication # All services use app-network ``` ### 3. Environment Variable Security ```bash # Use Docker secrets for sensitive data # Create secrets file echo "your-secret-key" | docker secret create secret_key - # Use secrets in docker-compose.yml secrets: secret_key: external: true ``` ## Scaling and Performance ### 1. Horizontal Scaling ```bash # Scale backend services docker-compose up -d --scale backend=4 # Use load balancer for multiple instances # Update nginx upstream configuration ``` ### 2. Resource Limits ```yaml # Add to docker-compose.yml deploy: resources: limits: cpus: '2.0' memory: 2G reservations: cpus: '1.0' memory: 1G ``` ### 3. Caching Strategy ```bash # Use Redis for caching # Already configured in docker-compose.yml # Configure Django cache settings CACHES = { 'default': { 'BACKEND': 'django.core.cache.backends.redis.RedisCache', 'LOCATION': 'redis://redis:6379/1', } } ``` ## Troubleshooting ### Common Issues 1. **Container fails to start** ```bash # Check container logs docker-compose logs backend docker-compose logs frontend # Check container status docker-compose ps # Restart specific service docker-compose restart backend ``` 2. **Database connection issues** ```bash # Check database health docker-compose exec db pg_isready # Check network connectivity docker-compose exec backend ping db # Verify environment variables docker-compose exec backend env | grep DATABASE ``` 3. **SSL certificate issues** ```bash # Check certificate validity openssl x509 -in ssl/cert.pem -text -noout # Test SSL connection openssl s_client -connect your-domain.com:443 -servername your-domain.com ``` 4. **Performance issues** ```bash # Check resource usage docker stats # Monitor database performance docker-compose exec db top # Check slow queries docker-compose exec db psql -c "SELECT query, mean_time, calls FROM pg_stat_statements ORDER BY mean_time DESC LIMIT 10;" ``` ## Production Checklist - [ ] Docker and Docker Compose installed - [ ] Repository cloned and configured - [ ] SSL certificate obtained and configured - [ ] Environment variables set correctly - [ ] Docker Compose file configured for production - [ ] Security hardening applied - [ ] Malaysian payment gateways configured - [ ] SST configuration completed - [ ] Database initialized with production data - [ ] Health checks passing - [ ] Monitoring and logging configured - [ ] Backup procedures implemented - [ ] Security testing completed - [ ] Performance testing completed - [ ] Load testing performed - [ ] Disaster recovery plan in place ## Support Resources - **Documentation**: https://docs.yourplatform.com - **GitHub Issues**: https://github.com/your-org/multi-tenant-saas/issues - **Community**: https://community.yourplatform.com - **Support**: support@yourplatform.com - **Emergency**: emergency@yourplatform.com