1011 lines
21 KiB
Markdown
1011 lines
21 KiB
Markdown
# Production Setup Guide for University ATS
|
|
|
|
This guide covers the complete setup for deploying the University ATS application to production, with special focus on frontend asset optimization.
|
|
|
|
## Table of Contents
|
|
|
|
1. [Frontend Asset Optimization](#frontend-asset-optimization)
|
|
2. [Django Production Configuration](#django-production-configuration)
|
|
3. [Static & Media Files](#static--media-files)
|
|
4. [Performance Optimization](#performance-optimization)
|
|
5. [Security Configuration](#security-configuration)
|
|
6. [Deployment Setup](#deployment-setup)
|
|
7. [Additional Services](#additional-services)
|
|
|
|
---
|
|
|
|
## Frontend Asset Optimization
|
|
|
|
### Current Setup (Development)
|
|
|
|
Your `base.html` currently uses CDN links for:
|
|
|
|
```html
|
|
<script src="https://cdn.tailwindcss.com"></script>
|
|
<script src="https://unpkg.com/lucide@latest"></script>
|
|
<script src="https://cdn.jsdelivr.net/npm/htmx.org@2.0.7/dist/htmx.min.js"></script>
|
|
```
|
|
|
|
**This is NOT suitable for production** because:
|
|
- Depends on external CDNs (reliability & privacy concerns)
|
|
- Slower initial page load
|
|
- No optimization/minification control
|
|
- Potential CSP violations
|
|
- No offline capability
|
|
|
|
---
|
|
|
|
### 1. Tailwind CSS Production Setup
|
|
|
|
#### Step 1: Install Tailwind CSS Locally
|
|
|
|
```bash
|
|
npm install -D tailwindcss postcss autoprefixer
|
|
```
|
|
|
|
#### Step 2: Create Tailwind Configuration
|
|
|
|
Create `kaauh_ats/tailwind.config.js`:
|
|
|
|
```javascript
|
|
/** @type {import('tailwindcss').Config} */
|
|
module.exports = {
|
|
content: [
|
|
"./kaauh_ats/templates/**/*.html",
|
|
"./kaauh_ats/templates/**/*.django",
|
|
"./kaauh_ats/static/**/*.html",
|
|
],
|
|
theme: {
|
|
extend: {
|
|
colors: {
|
|
'temple-red': '#9d2235',
|
|
'temple-dark': '#1a1a1a',
|
|
'temple-cream': '#f8f7f2',
|
|
'dashboard-blue': '#4e73df',
|
|
}
|
|
}
|
|
},
|
|
plugins: [],
|
|
}
|
|
```
|
|
|
|
#### Step 3: Create PostCSS Configuration
|
|
|
|
Create `kaauh_ats/postcss.config.js`:
|
|
|
|
```javascript
|
|
module.exports = {
|
|
plugins: {
|
|
tailwindcss: {},
|
|
autoprefixer: {},
|
|
},
|
|
}
|
|
```
|
|
|
|
#### Step 4: Create Main CSS File
|
|
|
|
Create `kaauh_ats/static/css/styles.css`:
|
|
|
|
```css
|
|
@tailwind base;
|
|
@tailwind components;
|
|
@tailwind utilities;
|
|
```
|
|
|
|
#### Step 5: Build Tailwind CSS for Production
|
|
|
|
Add to `kaauh_ats/package.json` scripts:
|
|
|
|
```json
|
|
{
|
|
"scripts": {
|
|
"build:css": "tailwindcss -i ./kaauh_ats/static/css/styles.css -o ./kaauh_ats/static/css/styles.min.css --minify",
|
|
"watch:css": "tailwindcss -i ./kaauh_ats/static/css/styles.css -o ./kaauh_ats/static/css/styles.css --watch"
|
|
}
|
|
}
|
|
```
|
|
|
|
Build command:
|
|
```bash
|
|
npm run build:css
|
|
```
|
|
|
|
#### Step 6: Update base.html Template
|
|
|
|
**Replace:**
|
|
```html
|
|
<script src="https://cdn.tailwindcss.com"></script>
|
|
<script>
|
|
tailwind.config = {
|
|
theme: {
|
|
extend: {
|
|
colors: {
|
|
'temple-red': '#9d2235',
|
|
'temple-dark': '#1a1a1a',
|
|
'temple-cream': '#f8f7f2',
|
|
'dashboard-blue': '#4e73df',
|
|
}
|
|
}
|
|
}
|
|
}
|
|
</script>
|
|
```
|
|
|
|
**With:**
|
|
```html
|
|
{% load static %}
|
|
<link rel="stylesheet" href="{% static 'css/styles.min.css' %}">
|
|
```
|
|
|
|
---
|
|
|
|
### 2. Lucide Icons Production Setup
|
|
|
|
#### Step 1: Install Lucide Icons Locally
|
|
|
|
```bash
|
|
npm install lucide
|
|
```
|
|
|
|
#### Step 2: Create Icons Bundle Script
|
|
|
|
Create `kaauh_ats/static/js/build-icons.js`:
|
|
|
|
```javascript
|
|
// Import only the icons you use (tree-shaking)
|
|
import {
|
|
createIcons,
|
|
LayoutGrid,
|
|
Briefcase,
|
|
Building2,
|
|
Users,
|
|
User,
|
|
Calendar,
|
|
Mail,
|
|
Globe,
|
|
Settings,
|
|
LogOut,
|
|
Menu,
|
|
PanelLeft,
|
|
Maximize,
|
|
Minimize,
|
|
X,
|
|
Tag,
|
|
Clock,
|
|
Timer,
|
|
Hash,
|
|
Lock,
|
|
Link,
|
|
DoorOpen,
|
|
MapPin,
|
|
Video,
|
|
CheckCircle,
|
|
AlertCircle,
|
|
AlertTriangle,
|
|
Info,
|
|
Save
|
|
} from 'lucide';
|
|
|
|
// Register icons globally
|
|
createIcons({
|
|
icons: {
|
|
'layout-grid': LayoutGrid,
|
|
'briefcase': Briefcase,
|
|
'building-2': Building2,
|
|
'users': Users,
|
|
'user': User,
|
|
'calendar': Calendar,
|
|
'mail': Mail,
|
|
'globe': Globe,
|
|
'settings': Settings,
|
|
'log-out': LogOut,
|
|
'menu': Menu,
|
|
'panel-left': PanelLeft,
|
|
'maximize': Maximize,
|
|
'minimize': Minimize,
|
|
'x': X,
|
|
'tag': Tag,
|
|
'clock': Clock,
|
|
'timer': Timer,
|
|
'hash': Hash,
|
|
'lock': Lock,
|
|
'link': Link,
|
|
'door-open': DoorOpen,
|
|
'map-pin': MapPin,
|
|
'video': Video,
|
|
'check-circle': CheckCircle,
|
|
'alert-circle': AlertCircle,
|
|
'alert-triangle': AlertTriangle,
|
|
'info': Info,
|
|
'save': Save
|
|
}
|
|
});
|
|
```
|
|
|
|
#### Step 3: Build Icons Bundle
|
|
|
|
Add to `package.json`:
|
|
|
|
```json
|
|
{
|
|
"scripts": {
|
|
"build:icons": "esbuild kaauh_ats/static/js/build-icons.js --bundle --outfile=kaauh_ats/static/js/icons.min.js --minify"
|
|
}
|
|
}
|
|
```
|
|
|
|
Install esbuild:
|
|
```bash
|
|
npm install -D esbuild
|
|
```
|
|
|
|
Build:
|
|
```bash
|
|
npm run build:icons
|
|
```
|
|
|
|
#### Step 4: Update base.html Template
|
|
|
|
**Replace:**
|
|
```html
|
|
<script src="https://unpkg.com/lucide@latest"></script>
|
|
```
|
|
|
|
**With:**
|
|
```html
|
|
{% load static %}
|
|
<script src="{% static 'js/icons.min.js' %}"></script>
|
|
```
|
|
|
|
---
|
|
|
|
### 3. HTMX Production Setup
|
|
|
|
#### Step 1: Install HTMX Locally
|
|
|
|
```bash
|
|
npm install htmx.org
|
|
```
|
|
|
|
#### Step 2: Copy HTMX to Static Files
|
|
|
|
```bash
|
|
cp node_modules/htmx.org/dist/htmx.min.js kaauh_ats/static/js/
|
|
```
|
|
|
|
Or add to build script:
|
|
```json
|
|
{
|
|
"scripts": {
|
|
"copy:htmx": "cp node_modules/htmx.org/dist/htmx.min.js kaauh_ats/static/js/"
|
|
}
|
|
}
|
|
```
|
|
|
|
#### Step 3: Update base.html Template
|
|
|
|
**Replace:**
|
|
```html
|
|
<script src="https://cdn.jsdelivr.net/npm/htmx.org@2.0.7/dist/htmx.min.js"></script>
|
|
```
|
|
|
|
**With:**
|
|
```html
|
|
{% load static %}
|
|
<script src="{% static 'js/htmx.min.js' %}"></script>
|
|
```
|
|
|
|
---
|
|
|
|
### 4. Combined Build Process
|
|
|
|
Update `package.json` with all build commands:
|
|
|
|
```json
|
|
{
|
|
"name": "kaauh-ats",
|
|
"version": "1.0.0",
|
|
"scripts": {
|
|
"build": "npm run build:css && npm run build:icons && npm run copy:htmx",
|
|
"build:css": "tailwindcss -i ./kaauh_ats/static/css/styles.css -o ./kaauh_ats/static/css/styles.min.css --minify",
|
|
"build:icons": "esbuild kaauh_ats/static/js/build-icons.js --bundle --outfile=kaauh_ats/static/js/icons.min.js --minify",
|
|
"copy:htmx": "cp node_modules/htmx.org/dist/htmx.min.js kaauh_ats/static/js/",
|
|
"watch": "npm run watch:css",
|
|
"watch:css": "tailwindcss -i ./kaauh_ats/static/css/styles.css -o ./kaauh_ats/static/css/styles.css --watch"
|
|
},
|
|
"devDependencies": {
|
|
"autoprefixer": "^10.4.17",
|
|
"esbuild": "^0.19.11",
|
|
"htmx.org": "^1.9.10",
|
|
"lucide": "^0.294.0",
|
|
"postcss": "^8.4.33",
|
|
"tailwindcss": "^3.4.1"
|
|
}
|
|
}
|
|
```
|
|
|
|
Install all dependencies:
|
|
```bash
|
|
npm install
|
|
```
|
|
|
|
Build everything:
|
|
```bash
|
|
npm run build
|
|
```
|
|
|
|
---
|
|
|
|
## Django Production Configuration
|
|
|
|
### 1. Update Settings
|
|
|
|
Create `kaauh_ats/NorahUniversity/settings_production.py`:
|
|
|
|
```python
|
|
from .settings import *
|
|
|
|
# SECURITY WARNING: keep the secret key used in production secret!
|
|
SECRET_KEY = os.environ.get('DJANGO_SECRET_KEY')
|
|
|
|
# SECURITY WARNING: don't run with debug turned on in production!
|
|
DEBUG = False
|
|
|
|
ALLOWED_HOSTS = [
|
|
'yourdomain.com',
|
|
'www.yourdomain.com',
|
|
'api.yourdomain.com',
|
|
]
|
|
|
|
# Email Configuration
|
|
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
|
|
EMAIL_HOST = os.environ.get('EMAIL_HOST', 'smtp.gmail.com')
|
|
EMAIL_PORT = int(os.environ.get('EMAIL_PORT', '587'))
|
|
EMAIL_USE_TLS = True
|
|
EMAIL_HOST_USER = os.environ.get('EMAIL_HOST_USER')
|
|
EMAIL_HOST_PASSWORD = os.environ.get('EMAIL_HOST_PASSWORD')
|
|
DEFAULT_FROM_EMAIL = os.environ.get('DEFAULT_FROM_EMAIL', 'noreply@yourdomain.com')
|
|
|
|
# Database Configuration
|
|
DATABASES = {
|
|
'default': {
|
|
'ENGINE': 'django.db.backends.postgresql_psycopg2',
|
|
'NAME': os.environ.get('DB_NAME'),
|
|
'USER': os.environ.get('DB_USER'),
|
|
'PASSWORD': os.environ.get('DB_PASSWORD'),
|
|
'HOST': os.environ.get('DB_HOST', 'localhost'),
|
|
'PORT': os.environ.get('DB_PORT', '5432'),
|
|
'CONN_MAX_AGE': 600,
|
|
'OPTIONS': {
|
|
'sslmode': 'require',
|
|
}
|
|
}
|
|
}
|
|
|
|
# Static Files
|
|
STATIC_URL = '/static/'
|
|
STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles')
|
|
STATICFILES_STORAGE = 'django.contrib.staticfiles.storage.ManifestStaticFilesStorage'
|
|
|
|
# Media Files
|
|
MEDIA_URL = '/media/'
|
|
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
|
|
|
|
# Security Settings
|
|
SECURE_SSL_REDIRECT = True
|
|
SECURE_HSTS_SECONDS = 31536000
|
|
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
|
|
SECURE_HSTS_PRELOAD = True
|
|
SESSION_COOKIE_SECURE = True
|
|
CSRF_COOKIE_SECURE = True
|
|
SECURE_BROWSER_XSS_FILTER = True
|
|
SECURE_CONTENT_TYPE_NOSNIFF = True
|
|
X_FRAME_OPTIONS = 'DENY'
|
|
SECURE_PROXY_SSL_HEADER = 'HTTP_X_FORWARDED_PROTO'
|
|
|
|
# Redis Configuration
|
|
CACHES = {
|
|
'default': {
|
|
'BACKEND': 'django_redis.cache.RedisCache',
|
|
'LOCATION': f"redis://{os.environ.get('REDIS_HOST', '127.0.0.1')}:{os.environ.get('REDIS_PORT', '6379')}/1",
|
|
'OPTIONS': {
|
|
'CLIENT_CLASS': 'django_redis.client.DefaultClient',
|
|
}
|
|
}
|
|
}
|
|
|
|
# Celery Configuration
|
|
CELERY_BROKER_URL = f"redis://{os.environ.get('REDIS_HOST', '127.0.0.1')}:{os.environ.get('REDIS_PORT', '6379')}/0"
|
|
CELERY_RESULT_BACKEND = 'django-db'
|
|
|
|
# Logging
|
|
LOGGING = {
|
|
'version': 1,
|
|
'disable_existing_loggers': False,
|
|
'formatters': {
|
|
'verbose': {
|
|
'format': '{levelname} {asctime} {module} {process:d} {thread:d} {message}',
|
|
},
|
|
},
|
|
'handlers': {
|
|
'file': {
|
|
'level': 'INFO',
|
|
'class': 'logging.FileHandler',
|
|
'filename': '/var/log/django/django.log',
|
|
'formatter': 'verbose',
|
|
},
|
|
},
|
|
'loggers': {
|
|
'django': {
|
|
'handlers': ['file'],
|
|
'level': 'INFO',
|
|
'propagate': True,
|
|
},
|
|
},
|
|
}
|
|
```
|
|
|
|
### 2. Environment Variables
|
|
|
|
Create `.env` file (do NOT commit to Git):
|
|
|
|
```bash
|
|
# Django
|
|
DJANGO_SECRET_KEY=your-super-secret-key-change-this
|
|
DJANGO_ALLOWED_HOSTS=yourdomain.com,www.yourdomain.com
|
|
|
|
# Database
|
|
DB_NAME=your_database_name
|
|
DB_USER=your_database_user
|
|
DB_PASSWORD=your_database_password
|
|
DB_HOST=localhost
|
|
DB_PORT=5432
|
|
|
|
# Redis
|
|
REDIS_HOST=localhost
|
|
REDIS_PORT=6379
|
|
|
|
# Email
|
|
EMAIL_HOST=smtp.gmail.com
|
|
EMAIL_PORT=587
|
|
EMAIL_HOST_USER=your-email@gmail.com
|
|
EMAIL_HOST_PASSWORD=your-app-password
|
|
DEFAULT_FROM_EMAIL=noreply@yourdomain.com
|
|
|
|
# Social Login (LinkedIn)
|
|
LINKEDIN_CLIENT_ID=your-linkedin-client-id
|
|
LINKEDIN_CLIENT_SECRET=your-linkedin-client-secret
|
|
|
|
# Zoom
|
|
ZOOM_API_KEY=your-zoom-api-key
|
|
ZOOM_API_SECRET=your-zoom-api-secret
|
|
ZOOM_WEBHOOK_API_KEY=your-webhook-api-key
|
|
```
|
|
|
|
### 3. Install Production Dependencies
|
|
|
|
```bash
|
|
pip install python-decouple gunicorn psycopg2-binary django-storages[boto3]
|
|
```
|
|
|
|
---
|
|
|
|
## Static & Media Files
|
|
|
|
### 1. Collect Static Files
|
|
|
|
```bash
|
|
cd kaauh_ats
|
|
python manage.py collectstatic --noinput --clear
|
|
```
|
|
|
|
This creates optimized, versioned static files in `staticfiles/`.
|
|
|
|
### 2. Configure Nginx for Static Files
|
|
|
|
```nginx
|
|
server {
|
|
listen 80;
|
|
server_name yourdomain.com www.yourdomain.com;
|
|
|
|
# Redirect HTTP to HTTPS
|
|
return 301 https://$server_name$request_uri;
|
|
}
|
|
|
|
server {
|
|
listen 443 ssl http2;
|
|
server_name yourdomain.com www.yourdomain.com;
|
|
|
|
root /var/www/kaauh_ats;
|
|
|
|
# SSL Configuration
|
|
ssl_certificate /etc/ssl/certs/yourdomain.com.crt;
|
|
ssl_certificate_key /etc/ssl/private/yourdomain.com.key;
|
|
ssl_protocols TLSv1.2 TLSv1.3;
|
|
ssl_ciphers HIGH:!aNULL:!MD5;
|
|
ssl_prefer_server_ciphers on;
|
|
|
|
# Static Files with Caching
|
|
location /static/ {
|
|
alias /var/www/kaauh_ats/staticfiles/;
|
|
expires 1y;
|
|
add_header Cache-Control "public, immutable";
|
|
access_log off;
|
|
gzip_static on;
|
|
}
|
|
|
|
# Media Files
|
|
location /media/ {
|
|
alias /var/www/kaauh_ats/media/;
|
|
expires 30d;
|
|
add_header Cache-Control "public";
|
|
access_log off;
|
|
gzip_static on;
|
|
}
|
|
|
|
# Django Application
|
|
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;
|
|
proxy_redirect off;
|
|
}
|
|
}
|
|
```
|
|
|
|
### 3. Media Files with S3 (Optional)
|
|
|
|
If using S3 for media storage:
|
|
|
|
```python
|
|
# settings_production.py
|
|
import os
|
|
|
|
DEFAULT_FILE_STORAGE = 'storages.backends.s3boto3.S3Boto3Storage'
|
|
|
|
AWS_ACCESS_KEY_ID = os.environ.get('AWS_ACCESS_KEY_ID')
|
|
AWS_SECRET_ACCESS_KEY = os.environ.get('AWS_SECRET_ACCESS_KEY')
|
|
AWS_STORAGE_BUCKET_NAME = os.environ.get('AWS_STORAGE_BUCKET_NAME')
|
|
AWS_S3_REGION_NAME = os.environ.get('AWS_S3_REGION_NAME', 'us-east-1')
|
|
AWS_S3_ENDPOINT_URL = os.environ.get('AWS_S3_ENDPOINT_URL')
|
|
AWS_S3_OBJECT_PARAMETERS = {'CacheControl': 'max-age=86400'}
|
|
AWS_DEFAULT_ACL = None
|
|
```
|
|
|
|
---
|
|
|
|
## Performance Optimization
|
|
|
|
### 1. Enable Gzip Compression
|
|
|
|
In `settings_production.py`:
|
|
|
|
```python
|
|
MIDDLEWARE = [
|
|
'django.middleware.gzip.GZipMiddleware',
|
|
# ... other middleware
|
|
]
|
|
|
|
GZIP_CONTENT_TYPES = [
|
|
'text/plain',
|
|
'text/css',
|
|
'text/xml',
|
|
'text/javascript',
|
|
'application/javascript',
|
|
'application/json',
|
|
'application/xml',
|
|
]
|
|
```
|
|
|
|
### 2. Browser Caching
|
|
|
|
```python
|
|
# Cache configuration
|
|
CACHES = {
|
|
'default': {
|
|
'BACKEND': 'django.core.cache.backends.redis.RedisCache',
|
|
'LOCATION': 'redis://127.0.0.1:6379/1',
|
|
'OPTIONS': {
|
|
'CLIENT_CLASS': 'django_redis.client.DefaultClient',
|
|
},
|
|
'TIMEOUT': 300,
|
|
'OPTIONS': {
|
|
'COMPRESSOR': 'django_redis.compressors.zlib.ZlibCompressor',
|
|
}
|
|
},
|
|
'staticfiles': {
|
|
'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache',
|
|
'LOCATION': '/var/cache/django/staticfiles/',
|
|
}
|
|
}
|
|
|
|
# Static files storage with manifest for cache busting
|
|
STATICFILES_STORAGE = 'django.contrib.staticfiles.storage.ManifestStaticFilesStorage'
|
|
```
|
|
|
|
### 3. Connection Pooling
|
|
|
|
```python
|
|
# Database connection pooling
|
|
DATABASES['default']['OPTIONS'] = {
|
|
'MAX_CONNS': 20,
|
|
'MAX_CONN_AGE': 0,
|
|
'CONN_MAX_AGE': 600,
|
|
}
|
|
```
|
|
|
|
### 4. Whitenoise for Static Files (Alternative)
|
|
|
|
Install Whitenoise:
|
|
```bash
|
|
pip install whitenoise[brotli]
|
|
```
|
|
|
|
Update `wsgi.py`:
|
|
|
|
```python
|
|
from whitenoise.middleware import WhiteNoiseMiddleware
|
|
from django.core.wsgi import get_wsgi_application
|
|
|
|
application = get_wsgi_application()
|
|
application = WhiteNoiseMiddleware(application, static_root='/var/www/kaauh_ats/staticfiles/')
|
|
```
|
|
|
|
---
|
|
|
|
## Security Configuration
|
|
|
|
### 1. SSL/TLS Setup
|
|
|
|
```bash
|
|
# Let's Encrypt (Free SSL)
|
|
sudo apt install certbot python3-certbot-nginx
|
|
sudo certbot --nginx -d yourdomain.com -d www.yourdomain.com
|
|
```
|
|
|
|
Auto-renewal:
|
|
```bash
|
|
sudo certbot renew --dry-run
|
|
sudo systemctl status certbot.timer
|
|
```
|
|
|
|
### 2. Firewall Configuration
|
|
|
|
```bash
|
|
# UFW Firewall
|
|
sudo ufw allow 22/tcp # SSH
|
|
sudo ufw allow 80/tcp # HTTP
|
|
sudo ufw allow 443/tcp # HTTPS
|
|
sudo ufw enable
|
|
```
|
|
|
|
### 3. Content Security Policy
|
|
|
|
Add to `settings_production.py`:
|
|
|
|
```python
|
|
CSP_DEFAULT_SRC = ("'self'", "https://cdn.tailwindcss.com")
|
|
CSP_IMG_SRC = ("'self'", "data:", "https:")
|
|
CSP_SCRIPT_SRC = ("'self'",)
|
|
CSP_STYLE_SRC = ("'self'", "'unsafe-inline'")
|
|
```
|
|
|
|
---
|
|
|
|
## Deployment Setup
|
|
|
|
### 1. Gunicorn WSGI Server
|
|
|
|
Install Gunicorn:
|
|
```bash
|
|
pip install gunicorn
|
|
```
|
|
|
|
Create Gunicorn systemd service:
|
|
|
|
`/etc/systemd/system/gunicorn.service`:
|
|
|
|
```ini
|
|
[Unit]
|
|
Description=gunicorn daemon for Kaauh ATS
|
|
After=network.target
|
|
|
|
[Service]
|
|
User=www-data
|
|
Group=www-data
|
|
WorkingDirectory=/var/www/kaauh_ats
|
|
ExecStart=/var/www/kaauh_ats/venv/bin/gunicorn \
|
|
--workers 3 \
|
|
--bind unix:/var/www/kaauh_ats/gunicorn.sock \
|
|
--access-logfile /var/log/gunicorn/access.log \
|
|
--error-logfile /var/log/gunicorn/error.log \
|
|
--log-level info \
|
|
--timeout 120 \
|
|
--keepalive 5 \
|
|
--max-requests 1000 \
|
|
--max-requests-jitter 100 \
|
|
NorahUniversity.wsgi:application
|
|
|
|
[Install]
|
|
WantedBy=multi-user.target
|
|
```
|
|
|
|
Start service:
|
|
```bash
|
|
sudo systemctl start gunicorn
|
|
sudo systemctl enable gunicorn
|
|
sudo systemctl status gunicorn
|
|
```
|
|
|
|
### 2. Celery Worker Service
|
|
|
|
`/etc/systemd/system/celery.service`:
|
|
|
|
```ini
|
|
[Unit]
|
|
Description=Celery Worker
|
|
After=network.target
|
|
|
|
[Service]
|
|
Type=forking
|
|
User=www-data
|
|
Group=www-data
|
|
WorkingDirectory=/var/www/kaauh_ats
|
|
Environment="PATH=/var/www/kaauh_ats/venv/bin"
|
|
ExecStart=/var/www/kaauh_ats/venv/bin/celery -A NorahUniversity worker \
|
|
--loglevel=info \
|
|
--logfile=/var/log/celery/worker.log \
|
|
--pidfile=/var/run/celery/worker.pid \
|
|
--concurrency=4
|
|
|
|
[Install]
|
|
WantedBy=multi-user.target
|
|
```
|
|
|
|
`/etc/systemd/system/celerybeat.service`:
|
|
|
|
```ini
|
|
[Unit]
|
|
Description=Celery Beat
|
|
After=network.target
|
|
|
|
[Service]
|
|
Type=forking
|
|
User=www-data
|
|
Group=www-data
|
|
WorkingDirectory=/var/www/kaauh_ats
|
|
Environment="PATH=/var/www/kaauh_ats/venv/bin"
|
|
ExecStart=/var/www/kaauh_ats/venv/bin/celery -A NorahUniversity beat \
|
|
--loglevel=info \
|
|
--logfile=/var/log/celery/beat.log \
|
|
--pidfile=/var/run/celery/beat.pid \
|
|
--scheduler django_celery_beat.schedulers:DatabaseScheduler
|
|
|
|
[Install]
|
|
WantedBy=multi-user.target
|
|
```
|
|
|
|
Start services:
|
|
```bash
|
|
sudo systemctl start celery celerybeat
|
|
sudo systemctl enable celery celerybeat
|
|
```
|
|
|
|
### 3. Redis Service
|
|
|
|
```bash
|
|
sudo apt install redis-server
|
|
sudo systemctl start redis
|
|
sudo systemctl enable redis
|
|
```
|
|
|
|
### 4. PostgreSQL Setup
|
|
|
|
```bash
|
|
sudo apt install postgresql postgresql-contrib
|
|
sudo -u postgres psql
|
|
|
|
CREATE DATABASE kaauh_ats;
|
|
CREATE USER kaauh_ats_user WITH PASSWORD 'secure_password';
|
|
GRANT ALL PRIVILEGES ON DATABASE kaauh_ats TO kaauh_ats_user;
|
|
\q
|
|
```
|
|
|
|
### 5. Deployment Script
|
|
|
|
Create `deploy.sh`:
|
|
|
|
```bash
|
|
#!/bin/bash
|
|
set -e
|
|
|
|
echo "Starting deployment..."
|
|
|
|
# Pull latest code
|
|
cd /var/www/kaauh_ats
|
|
git pull origin main
|
|
|
|
# Activate virtual environment
|
|
source venv/bin/activate
|
|
|
|
# Install dependencies
|
|
pip install -r requirements.txt
|
|
|
|
# Install and build frontend assets
|
|
npm install
|
|
npm run build
|
|
|
|
# Run migrations
|
|
python manage.py migrate --noinput
|
|
|
|
# Collect static files
|
|
python manage.py collectstatic --noinput --clear
|
|
|
|
# Restart services
|
|
sudo systemctl restart gunicorn
|
|
sudo systemctl restart celery
|
|
sudo systemctl restart celerybeat
|
|
|
|
echo "Deployment completed successfully!"
|
|
```
|
|
|
|
Make executable:
|
|
```bash
|
|
chmod +x deploy.sh
|
|
```
|
|
|
|
---
|
|
|
|
## Additional Services
|
|
|
|
### 1. Monitoring & Logging
|
|
|
|
Install Sentry for error tracking:
|
|
|
|
```bash
|
|
pip install sentry-sdk[django]
|
|
```
|
|
|
|
Configure in `settings_production.py`:
|
|
|
|
```python
|
|
import sentry_sdk
|
|
from sentry_sdk.integrations.django import DjangoIntegration
|
|
|
|
sentry_sdk.init(
|
|
dsn=os.environ.get('SENTRY_DSN'),
|
|
integrations=[DjangoIntegration()],
|
|
traces_sample_rate=1.0,
|
|
profiles_sample_rate=1.0,
|
|
)
|
|
```
|
|
|
|
### 2. Backup Strategy
|
|
|
|
Create backup script `backup.sh`:
|
|
|
|
```bash
|
|
#!/bin/bash
|
|
BACKUP_DIR="/var/backups/kaauh-ats"
|
|
DATE=$(date +%Y%m%d_%H%M%S)
|
|
|
|
# Backup Database
|
|
pg_dump -U kaauh_ats_user kaauh_ats > $BACKUP_DIR/db_$DATE.sql
|
|
|
|
# Backup Media Files
|
|
tar -czf $BACKUP_DIR/media_$DATE.tar.gz /var/www/kaauh_ats/media/
|
|
|
|
# Delete backups older than 30 days
|
|
find $BACKUP_DIR -type f -mtime +30 -delete
|
|
```
|
|
|
|
### 3. Health Checks
|
|
|
|
Create health check endpoint in `urls.py`:
|
|
|
|
```python
|
|
from django.http import JsonResponse
|
|
|
|
def health_check(request):
|
|
try:
|
|
# Check database
|
|
from django.db import connections
|
|
connections['default'].cursor()
|
|
|
|
# Check Redis
|
|
from django.core.cache import cache
|
|
cache.set('health_check', 'ok', 10)
|
|
cache.get('health_check')
|
|
|
|
return JsonResponse({'status': 'healthy'})
|
|
except Exception as e:
|
|
return JsonResponse({'status': 'unhealthy', 'error': str(e)}, status=503)
|
|
```
|
|
|
|
---
|
|
|
|
## Pre-Deployment Checklist
|
|
|
|
- [ ] All CDNs replaced with local assets
|
|
- [ ] Tailwind CSS built and minified
|
|
- [ ] Lucide icons bundled and minified
|
|
- [ ] HTMX downloaded and minified
|
|
- [ ] DEBUG = False in production settings
|
|
- [ ] SECRET_KEY changed from development
|
|
- [ ] ALLOWED_HOSTS configured
|
|
- [ ] Database connection configured
|
|
- [ ] Redis connection configured
|
|
- [ ] Static files collected
|
|
- [ ] SSL/TLS certificates installed
|
|
- [ ] Firewall configured
|
|
- [ ] Gunicorn service running
|
|
- [ ] Celery workers running
|
|
- [ ] Redis service running
|
|
- [ ] Nginx configured
|
|
- [ ] Environment variables set
|
|
- [ ] Email backend configured
|
|
- [ ] Logging configured
|
|
- [ ] Monitoring setup (Sentry)
|
|
- [ ] Backup strategy in place
|
|
- [ ] Health check endpoint accessible
|
|
- [ ] All tests passing
|
|
|
|
---
|
|
|
|
## Troubleshooting
|
|
|
|
### Issue: Static files not loading
|
|
|
|
**Solution:**
|
|
```bash
|
|
python manage.py collectstatic --clear --noinput
|
|
sudo systemctl reload nginx
|
|
```
|
|
|
|
### Issue: Permission denied errors
|
|
|
|
**Solution:**
|
|
```bash
|
|
sudo chown -R www-data:www-data /var/www/kaauh_ats
|
|
sudo chmod -R 755 /var/www/kaauh_ats
|
|
```
|
|
|
|
### Issue: Database connection errors
|
|
|
|
**Solution:**
|
|
```bash
|
|
# Check PostgreSQL is running
|
|
sudo systemctl status postgresql
|
|
|
|
# Check connection settings in .env
|
|
# Test connection
|
|
psql -h localhost -U kaauh_ats_user -d kaauh_ats
|
|
```
|
|
|
|
### Issue: Celery tasks not executing
|
|
|
|
**Solution:**
|
|
```bash
|
|
# Check Celery logs
|
|
sudo tail -f /var/log/celery/worker.log
|
|
|
|
# Restart Celery
|
|
sudo systemctl restart celery
|
|
```
|
|
|
|
---
|
|
|
|
## Resources
|
|
|
|
- [Django Production Deployment](https://docs.djangoproject.com/en/stable/howto/deployment/)
|
|
- [Tailwind CSS Documentation](https://tailwindcss.com/docs/installation)
|
|
- [Gunicorn Documentation](https://docs.gunicorn.org/)
|
|
- [Nginx Documentation](https://nginx.org/en/docs/)
|
|
- [Let's Encrypt](https://letsencrypt.org/)
|
|
|
|
---
|
|
|
|
**Last Updated:** February 1, 2026
|
|
**Version:** 1.0.0 |