Skip to content

Deploying Django-CRM

Django-CRM, rebranded as BottleCRM, is a full-featured customer relationship management system purpose-built for startups and small businesses that need enterprise capabilities without the enterprise complexity. This modern CRM combines a powerful Django REST API backend with a sleek SvelteKit frontend, offering everything from lead tracking and opportunity management to invoicing and customer support case handling. What sets BottleCRM apart is its sophisticated multi-tenant architecture powered by PostgreSQL Row-Level Security (RLS), ensuring complete data isolation between organizations—a feature typically found only in expensive enterprise platforms.

The platform tackles the real-world problem of teams juggling multiple disconnected tools. Instead of maintaining separate subscriptions for lead management, contact databases, support tickets, task tracking, and invoicing, BottleCRM consolidates these essential functions into a single, self-hosted application. The Django backend provides rock-solid reliability and scalability, while the SvelteKit frontend delivers a responsive, modern interface that feels snappy even with thousands of records. Built-in features like JWT authentication, team management with role-based access, comprehensive activity tracking, email integration via AWS SES, and background task processing with Celery make it production-ready from day one. Whether you’re a five-person startup tracking your first hundred leads or a growing company managing thousands of customer relationships across multiple teams, BottleCRM scales with you while keeping your data under your complete control.

Why Deploy Django-CRM on Klutch.sh?

Deploying Django-CRM on Klutch.sh offers several compelling advantages for CRM operations:

  • Complete Data Sovereignty: Keep all customer data, leads, and business intelligence on infrastructure you control
  • Enterprise Security: Multi-tenant architecture with PostgreSQL RLS provides bank-level data isolation
  • Always-On Reliability: Keep your CRM accessible 24/7 without maintaining physical servers
  • Automatic HTTPS: Secure all customer data transmission with built-in SSL certificates
  • Persistent Storage: All leads, contacts, opportunities, and documents persist safely across deployments
  • Scalable Infrastructure: Handle growth from startup to enterprise on the same platform
  • Cost Effective: Eliminate per-user SaaS fees with self-hosted CRM
  • Background Processing: Celery + Redis for async tasks like email campaigns and data imports
  • API-First Architecture: Full REST API for integrations with other business tools

Prerequisites

Before deploying Django-CRM to Klutch.sh, ensure you have:

  • A Klutch.sh account (sign up here)
  • A GitHub account with a repository for your Django-CRM deployment
  • Basic understanding of Docker and containerization
  • Familiarity with Django and Python applications
  • Understanding of PostgreSQL and database concepts
  • Git installed on your local development machine
  • Basic knowledge of environment variables and secrets management
  • Optional: Experience with SvelteKit for frontend customization

Understanding Django-CRM Architecture

Django-CRM (BottleCRM) operates as a modern full-stack application with a clear separation between backend and frontend:

Backend Architecture:

  • Django 5.x with Django REST Framework: Provides the API layer and business logic
  • PostgreSQL with Row-Level Security (RLS): Enterprise-grade multi-tenant data isolation where each organization’s data is automatically filtered at the database level
  • Redis: Powers caching and serves as the message broker for Celery
  • Celery: Handles background tasks like bulk email sending, data imports, and report generation
  • JWT Authentication: Secure token-based authentication for API access
  • AWS S3: File storage for attachments and documents (configurable)
  • AWS SES: Transactional email delivery (configurable)

Frontend Architecture:

  • SvelteKit 2.x with Svelte 5: Modern reactive framework with excellent performance
  • TailwindCSS 4: Utility-first styling for responsive design
  • shadcn-svelte: Beautiful, accessible UI components
  • Zod: Runtime schema validation for form handling
  • Axios: HTTP client for API communication

Core CRM Modules:

  • Leads: Track and manage sales leads through your pipeline
  • Accounts: Manage company and organization records
  • Contacts: Store and organize contact information with custom fields
  • Opportunities: Track deals and sales opportunities with revenue forecasting
  • Cases: Customer support case management with priority levels
  • Tasks: Task management with calendar and Kanban board views
  • Invoices: Create and manage invoices with customizable templates

The multi-tenant architecture means a single deployment can securely serve multiple organizations, with PostgreSQL RLS ensuring that queries automatically filter data by organization context—a powerful feature that eliminates the risk of data leakage between tenants.

Step 1: Prepare Your Repository

Let’s set up a GitHub repository for Django-CRM with a production-ready Dockerfile.

    Create a new directory for your project:

    Terminal window
    mkdir django-crm-app
    cd django-crm-app
    git init

    Create a production-optimized Dockerfile:

    FROM python:3.11-slim as backend-builder
    # Install system dependencies
    RUN apt-get update && apt-get install -y \
    build-essential \
    libpq-dev \
    postgresql-client \
    && rm -rf /var/lib/apt/lists/*
    # Set working directory
    WORKDIR /app
    # Copy backend requirements
    COPY backend/requirements.txt .
    # Install Python dependencies
    RUN pip install --no-cache-dir -r requirements.txt
    # Stage 2: Frontend build
    FROM node:18-alpine as frontend-builder
    WORKDIR /app/frontend
    # Copy frontend package files
    COPY frontend/package.json frontend/pnpm-lock.yaml ./
    # Install pnpm and dependencies
    RUN npm install -g pnpm && pnpm install --frozen-lockfile
    # Copy frontend source
    COPY frontend/ ./
    # Build frontend
    RUN pnpm run build
    # Stage 3: Final production image
    FROM python:3.11-slim
    # Install runtime dependencies
    RUN apt-get update && apt-get install -y \
    libpq5 \
    postgresql-client \
    nginx \
    supervisor \
    && rm -rf /var/lib/apt/lists/*
    # Create app user
    RUN useradd -m -u 1001 crm && \
    mkdir -p /app /var/log/supervisor && \
    chown -R crm:crm /app
    WORKDIR /app
    # Copy Python dependencies from builder
    COPY --from=backend-builder /usr/local/lib/python3.11/site-packages /usr/local/lib/python3.11/site-packages
    COPY --from=backend-builder /usr/local/bin /usr/local/bin
    # Copy backend application
    COPY --chown=crm:crm backend/ ./backend/
    # Copy built frontend from builder
    COPY --from=frontend-builder /app/frontend/build ./frontend/build
    COPY --from=frontend-builder /app/frontend/package.json ./frontend/
    # Configure nginx
    COPY docker/nginx.conf /etc/nginx/sites-available/default
    # Configure supervisor
    COPY docker/supervisord.conf /etc/supervisor/conf.d/supervisord.conf
    # Create required directories
    RUN mkdir -p /app/media /app/static /app/logs && \
    chown -R crm:crm /app/media /app/static /app/logs
    # Collect static files
    WORKDIR /app/backend
    RUN python manage.py collectstatic --noinput || true
    # Expose port
    EXPOSE 8000
    # Switch to app user
    USER crm
    # Start supervisor
    CMD ["/usr/bin/supervisord", "-c", "/etc/supervisor/conf.d/supervisord.conf"]

    Create a .env.example file with all required configuration:

    # Django Configuration
    SECRET_KEY=your-secret-key-change-in-production
    DEBUG=False
    ALLOWED_HOSTS=django-crm.klutch.sh,localhost
    CORS_ALLOWED_ORIGINS=https://django-crm.klutch.sh
    # Database Configuration
    DATABASE_URL=postgresql://crm_user:your_password@localhost:5432/crm_db
    DB_HOST=localhost
    DB_PORT=5432
    DB_NAME=crm_db
    DB_USER=crm_user
    DB_PASSWORD=your_db_password
    # Redis Configuration
    REDIS_URL=redis://localhost:6379/0
    CELERY_BROKER_URL=redis://localhost:6379/0
    CELERY_RESULT_BACKEND=redis://localhost:6379/1
    # AWS Configuration (Optional)
    AWS_ACCESS_KEY_ID=
    AWS_SECRET_ACCESS_KEY=
    AWS_STORAGE_BUCKET_NAME=
    AWS_S3_REGION_NAME=us-east-1
    USE_S3=False
    # Email Configuration (AWS SES or SMTP)
    EMAIL_BACKEND=django.core.mail.backends.smtp.EmailBackend
    EMAIL_HOST=smtp.gmail.com
    EMAIL_PORT=587
    EMAIL_USE_TLS=True
    EMAIL_HOST_USER=
    EMAIL_HOST_PASSWORD=
    DEFAULT_FROM_EMAIL=noreply@example.com
    # JWT Configuration
    JWT_SECRET_KEY=your-jwt-secret-key
    JWT_ALGORITHM=HS256
    JWT_EXPIRATION_DELTA=3600
    # Application Settings
    SITE_URL=https://django-crm.klutch.sh
    FRONTEND_URL=https://django-crm.klutch.sh
    DOMAIN=django-crm.klutch.sh
    # Security Settings
    SECURE_SSL_REDIRECT=True
    SESSION_COOKIE_SECURE=True
    CSRF_COOKIE_SECURE=True
    SECURE_HSTS_SECONDS=31536000
    # Multi-tenancy RLS
    ENABLE_RLS=True
    RLS_ROLE_PREFIX=crm_org_
    # Celery Settings
    CELERY_TASK_ALWAYS_EAGER=False
    CELERY_TASK_EAGER_PROPAGATES=False
    # Logging
    LOG_LEVEL=INFO

    Create docker/nginx.conf:

    upstream django_backend {
    server 127.0.0.1:8001;
    }
    server {
    listen 8000;
    server_name _;
    client_max_body_size 20M;
    # Security headers
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-XSS-Protection "1; mode=block" always;
    add_header Referrer-Policy "strict-origin-when-cross-origin" always;
    # API requests to Django
    location /api/ {
    proxy_pass http://django_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_redirect off;
    }
    # Admin panel
    location /admin/ {
    proxy_pass http://django_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;
    }
    # Static files
    location /static/ {
    alias /app/static/;
    expires 30d;
    add_header Cache-Control "public, immutable";
    }
    # Media files
    location /media/ {
    alias /app/media/;
    expires 7d;
    add_header Cache-Control "public";
    }
    # Swagger/API documentation
    location /swagger-ui/ {
    proxy_pass http://django_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;
    }
    # Frontend (SvelteKit)
    location / {
    root /app/frontend/build;
    try_files $uri $uri/ /index.html;
    expires 1h;
    add_header Cache-Control "public";
    }
    # Health check endpoint
    location /health {
    access_log off;
    return 200 "healthy\n";
    add_header Content-Type text/plain;
    }
    }

    Create docker/supervisord.conf:

    [supervisord]
    nodaemon=true
    user=crm
    logfile=/app/logs/supervisord.log
    pidfile=/tmp/supervisord.pid
    [program:django]
    command=gunicorn crm.wsgi:application --bind 127.0.0.1:8001 --workers 4 --timeout 120 --access-logfile /app/logs/gunicorn-access.log --error-logfile /app/logs/gunicorn-error.log
    directory=/app/backend
    autostart=true
    autorestart=true
    stdout_logfile=/app/logs/django.log
    stderr_logfile=/app/logs/django-error.log
    [program:celery_worker]
    command=celery -A crm worker --loglevel=info --concurrency=2
    directory=/app/backend
    autostart=true
    autorestart=true
    stdout_logfile=/app/logs/celery-worker.log
    stderr_logfile=/app/logs/celery-worker-error.log
    stopwaitsecs=600
    killasgroup=true
    priority=998
    [program:celery_beat]
    command=celery -A crm beat --loglevel=info
    directory=/app/backend
    autostart=true
    autorestart=true
    stdout_logfile=/app/logs/celery-beat.log
    stderr_logfile=/app/logs/celery-beat-error.log
    stopwaitsecs=10
    priority=999
    [program:nginx]
    command=/usr/sbin/nginx -g "daemon off;"
    autostart=true
    autorestart=true
    stdout_logfile=/app/logs/nginx-access.log
    stderr_logfile=/app/logs/nginx-error.log

    Create backend/requirements.txt:

    Django==5.0.3
    djangorestframework==3.14.0
    django-cors-headers==4.3.1
    django-filter==24.1
    psycopg2-binary==2.9.9
    redis==5.0.1
    celery==5.3.6
    python-decouple==3.8
    PyJWT==2.8.0
    gunicorn==21.2.0
    whitenoise==6.6.0
    Pillow==10.2.0
    boto3==1.34.51
    django-storages==1.14.2
    drf-yasg==1.21.7

    Create a .dockerignore file:

    .git
    .github
    .env
    .env.*
    !.env.example
    *.pyc
    __pycache__/
    *.sqlite3
    db.sqlite3
    media/
    static/
    .vscode
    .idea
    *.md
    !README.md
    node_modules/
    frontend/.svelte-kit/
    frontend/build/
    frontend/dist/
    .DS_Store
    *.log

    Create a README.md with setup instructions:

    # Django-CRM Deployment on Klutch.sh
    Modern open-source CRM system with Django REST Framework backend and SvelteKit frontend.
    ## Features
    - Lead and opportunity management
    - Contact and account tracking
    - Customer support case management
    - Task management with calendar views
    - Invoice creation and management
    - Multi-tenant architecture with RLS
    - JWT authentication
    - Background task processing with Celery
    - Email integration (AWS SES/SMTP)
    - Comprehensive REST API
    ## Local Development
    ### Backend Setup
    \`\`\`bash
    cd backend
    python -m venv venv
    source venv/bin/activate
    pip install -r requirements.txt
    cp .env.example .env
    # Edit .env with your configuration
    python manage.py migrate
    python manage.py createsuperuser
    python manage.py runserver
    \`\`\`
    ### Frontend Setup
    \`\`\`bash
    cd frontend
    pnpm install
    pnpm run dev
    \`\`\`
    ### Start Celery Worker
    \`\`\`bash
    celery -A crm worker --loglevel=info
    \`\`\`
    ## Deployment
    This application is configured for deployment on Klutch.sh. See the deployment guide for details.
    ## Environment Variables
    See `.env.example` for all required environment variables.
    ## License
    MIT License - See LICENSE file for details.

    Clone the Django-CRM repository and copy necessary files:

    Terminal window
    # Clone the official repository (for reference)
    git clone https://github.com/MicroPyramid/Django-CRM.git temp-django-crm
    # Copy backend and frontend directories
    cp -r temp-django-crm/backend ./
    cp -r temp-django-crm/frontend ./
    # Clean up
    rm -rf temp-django-crm

    Initialize Git and push to GitHub:

    Terminal window
    git add .
    git commit -m "Initial Django-CRM setup for Klutch.sh deployment"
    git branch -M main
    git remote add origin https://github.com/YOUR_USERNAME/django-crm-app.git
    git push -u origin main

Step 2: Set Up PostgreSQL Database

Django-CRM requires PostgreSQL with Row-Level Security (RLS) enabled for multi-tenancy.

    In Klutch.sh dashboard, create a new PostgreSQL database:

    • Click Create New App
    • App Name: django-crm-db
    • Select: PostgreSQL 15
    • Traffic Type: TCP
    • Internal Port: 5432

    Add persistent storage for the database:

    • Mount Path: /var/lib/postgresql/data
    • Size: 20GB

    Once deployed, note the connection details:

    • Host: Internal host provided by Klutch.sh
    • Port: 8000 (external), 5432 (internal)
    • Database: crm_db
    • User: crm_user
    • Password: Generated or set by you

    Connect to the database and enable RLS:

    -- Create database
    CREATE DATABASE crm_db;
    -- Connect to the database
    \c crm_db
    -- Create user with proper permissions
    CREATE USER crm_user WITH PASSWORD 'your_secure_password';
    GRANT ALL PRIVILEGES ON DATABASE crm_db TO crm_user;
    -- Enable RLS on all CRM tables (after migrations)
    ALTER TABLE accounts_account ENABLE ROW LEVEL SECURITY;
    ALTER TABLE contacts_contact ENABLE ROW LEVEL SECURITY;
    ALTER TABLE leads_lead ENABLE ROW LEVEL SECURITY;
    ALTER TABLE opportunity_opportunity ENABLE ROW LEVEL SECURITY;
    ALTER TABLE cases_case ENABLE ROW LEVEL SECURITY;
    ALTER TABLE tasks_task ENABLE ROW LEVEL SECURITY;
    ALTER TABLE invoices_invoice ENABLE ROW LEVEL SECURITY;
    -- Create RLS policies (example for accounts)
    CREATE POLICY account_isolation_policy ON accounts_account
    USING (org_id = current_setting('app.current_org_id')::uuid);
    -- Grant permissions to crm_user
    GRANT ALL ON ALL TABLES IN SCHEMA public TO crm_user;
    GRANT ALL ON ALL SEQUENCES IN SCHEMA public TO crm_user;

Step 3: Set Up Redis for Caching and Celery

Django-CRM uses Redis for caching and as a Celery message broker.

    Create a Redis instance on Klutch.sh:

    • Click Create New App
    • App Name: django-crm-redis
    • Select: Redis 7
    • Traffic Type: TCP
    • Internal Port: 6379

    Add persistent storage:

    • Mount Path: /data
    • Size: 5GB

    Once deployed, note the Redis URL:

    • Format: redis://django-crm-redis:6379/0

Step 4: Deploy Django-CRM to Klutch.sh

Now let’s deploy the main Django-CRM application.

    Log in to your Klutch.sh dashboard at klutch.sh/app.

    Click Create New App and fill in the details:

    • App Name: django-crm
    • Git Source: Select GitHub
    • Repository: Choose your django-crm-app repository
    • Branch: main

    Klutch.sh will automatically detect your Dockerfile and use it for deployment.

    Configure the traffic settings:

    • Traffic Type: Select HTTP
    • Internal Port: 8000

    Add persistent storage for media files and logs:

    Volume 1 - Media Files:

    • Mount Path: /app/media
    • Size: 50GB

    Volume 2 - Static Files:

    • Mount Path: /app/static
    • Size: 10GB

    Volume 3 - Logs:

    • Mount Path: /app/logs
    • Size: 5GB

    Add environment variables (from your .env file):

    SECRET_KEY=your-long-random-secret-key-here
    DEBUG=False
    ALLOWED_HOSTS=django-crm.klutch.sh
    CORS_ALLOWED_ORIGINS=https://django-crm.klutch.sh
    DATABASE_URL=postgresql://crm_user:your_password@django-crm-db:5432/crm_db
    DB_HOST=django-crm-db
    DB_PORT=5432
    DB_NAME=crm_db
    DB_USER=crm_user
    DB_PASSWORD=your_db_password
    REDIS_URL=redis://django-crm-redis:6379/0
    CELERY_BROKER_URL=redis://django-crm-redis:6379/0
    CELERY_RESULT_BACKEND=redis://django-crm-redis:6379/1
    EMAIL_BACKEND=django.core.mail.backends.smtp.EmailBackend
    EMAIL_HOST=smtp.gmail.com
    EMAIL_PORT=587
    EMAIL_USE_TLS=True
    EMAIL_HOST_USER=your-email@gmail.com
    EMAIL_HOST_PASSWORD=your-app-password
    DEFAULT_FROM_EMAIL=noreply@yourcompany.com
    JWT_SECRET_KEY=your-jwt-secret-key
    JWT_ALGORITHM=HS256
    JWT_EXPIRATION_DELTA=3600
    SITE_URL=https://django-crm.klutch.sh
    FRONTEND_URL=https://django-crm.klutch.sh
    DOMAIN=django-crm.klutch.sh
    SECURE_SSL_REDIRECT=True
    SESSION_COOKIE_SECURE=True
    CSRF_COOKIE_SECURE=True
    SECURE_HSTS_SECONDS=31536000
    ENABLE_RLS=True
    RLS_ROLE_PREFIX=crm_org_
    LOG_LEVEL=INFO

    Click Deploy to start the deployment process.

    Wait for the deployment to complete. Klutch.sh will:

    • Clone your repository
    • Build the multi-stage Docker image
    • Start Django, Celery workers, and Nginx
    • Assign a URL like django-crm.klutch.sh

    Once deployment succeeds, run database migrations by accessing the container:

    Terminal window
    # Connect to your app container via Klutch.sh terminal
    python backend/manage.py migrate
    python backend/manage.py createsuperuser
    python backend/manage.py collectstatic --noinput

    Access your Django-CRM instance at django-crm.klutch.sh.

Step 5: Initial Configuration and Setup

After your first deployment, configure Django-CRM for production use.

    Access the admin panel at https://django-crm.klutch.sh/admin and log in with your superuser credentials.

    Create your first organization:

    • Navigate to Organizations in the admin
    • Click Add Organization
    • Enter organization details (name, domain, settings)
    • Save the organization

    Set up Row-Level Security for the organization:

    -- Connect to your database
    \c crm_db
    -- Create RLS role for the organization
    CREATE ROLE crm_org_abc123 NOINHERIT;
    -- Grant role to crm_user
    GRANT crm_org_abc123 TO crm_user;
    -- Set default organization context
    ALTER ROLE crm_org_abc123 SET app.current_org_id = 'organization-uuid-here';

    Configure email settings:

    Using Gmail SMTP:

    EMAIL_BACKEND=django.core.mail.backends.smtp.EmailBackend
    EMAIL_HOST=smtp.gmail.com
    EMAIL_PORT=587
    EMAIL_USE_TLS=True
    EMAIL_HOST_USER=your-email@gmail.com
    EMAIL_HOST_PASSWORD=your-app-specific-password
    DEFAULT_FROM_EMAIL=noreply@yourcompany.com

    Using AWS SES:

    EMAIL_BACKEND=django_ses.SESBackend
    AWS_SES_REGION_NAME=us-east-1
    AWS_SES_REGION_ENDPOINT=email.us-east-1.amazonaws.com
    AWS_ACCESS_KEY_ID=your-aws-access-key
    AWS_SECRET_ACCESS_KEY=your-aws-secret-key
    DEFAULT_FROM_EMAIL=noreply@yourcompany.com

    Test email configuration:

    # In Django shell
    from django.core.mail import send_mail
    send_mail(
    'Test Email',
    'This is a test email from Django-CRM',
    'noreply@yourcompany.com',
    ['recipient@example.com'],
    fail_silently=False,
    )

    Create your first users and teams:

    • Go to Users in the admin panel
    • Add team members with appropriate roles
    • Assign users to teams
    • Set permissions for each role

    Configure frontend settings:

    • Update FRONTEND_URL environment variable
    • Ensure CORS settings allow your frontend domain
    • Configure authentication tokens

    Access the frontend at https://django-crm.klutch.sh and complete the onboarding flow.

Step 6: Configure CRM Modules and Workflows

Customize Django-CRM for your business processes.

    Configure Lead Pipeline:

    # In Django admin or via API
    LEAD_STAGES = [
    'New',
    'Contacted',
    'Qualified',
    'Proposal Sent',
    'Negotiation',
    'Closed Won',
    'Closed Lost'
    ]
    # Set up lead sources
    LEAD_SOURCES = [
    'Website',
    'Referral',
    'Cold Call',
    'Trade Show',
    'Social Media',
    'Email Campaign'
    ]

    Set up Opportunity Stages:

    OPPORTUNITY_STAGES = [
    {'name': 'Prospecting', 'probability': 10},
    {'name': 'Qualification', 'probability': 20},
    {'name': 'Needs Analysis', 'probability': 40},
    {'name': 'Proposal', 'probability': 60},
    {'name': 'Negotiation', 'probability': 80},
    {'name': 'Closed Won', 'probability': 100},
    {'name': 'Closed Lost', 'probability': 0}
    ]

    Configure Case Priority Levels:

    CASE_PRIORITIES = [
    'Low',
    'Medium',
    'High',
    'Urgent'
    ]
    CASE_STATUSES = [
    'New',
    'In Progress',
    'Waiting on Customer',
    'Waiting on Third Party',
    'Resolved',
    'Closed'
    ]

    Set up Invoice Templates:

    Create custom invoice templates in the admin panel:

    • Configure company branding
    • Set payment terms and conditions
    • Add tax rates and discount rules
    • Configure invoice numbering

    Configure Task Types and Categories:

    TASK_TYPES = [
    'Call',
    'Email',
    'Meeting',
    'To-Do',
    'Follow-up'
    ]
    TASK_PRIORITIES = [
    'Low',
    'Normal',
    'High',
    'Urgent'
    ]

    Set up Custom Fields:

    Django-CRM allows custom fields for each module:

    # Add custom fields via Django admin
    # Example: Add "Industry" field to Accounts
    {
    'field_name': 'industry',
    'field_type': 'choice',
    'choices': ['Technology', 'Healthcare', 'Finance', 'Retail', 'Manufacturing'],
    'required': False,
    'module': 'accounts'
    }

    Configure Email Templates:

    Create email templates for automated communications:

    • Welcome emails for new contacts
    • Follow-up sequences for leads
    • Task reminders
    • Invoice notifications
    • Case status updates

Step 7: Set Up Background Tasks and Automation

Configure Celery for automated tasks and workflows.

    Schedule Periodic Tasks:

    # In backend/crm/celery.py
    from celery.schedules import crontab
    app.conf.beat_schedule = {
    'send-daily-lead-digest': {
    'task': 'leads.tasks.send_daily_digest',
    'schedule': crontab(hour=9, minute=0), # 9 AM daily
    },
    'update-opportunity-stages': {
    'task': 'opportunity.tasks.auto_update_stages',
    'schedule': crontab(hour='*/6'), # Every 6 hours
    },
    'send-task-reminders': {
    'task': 'tasks.tasks.send_reminders',
    'schedule': crontab(hour='*', minute=0), # Every hour
    },
    'cleanup-old-activities': {
    'task': 'common.tasks.cleanup_activities',
    'schedule': crontab(hour=2, minute=0, day_of_week=0), # Sunday 2 AM
    },
    }

    Create Custom Celery Tasks:

    backend/leads/tasks.py
    from celery import shared_task
    from django.core.mail import send_mail
    from .models import Lead
    @shared_task
    def send_lead_notification(lead_id):
    """Send notification when new lead is created"""
    lead = Lead.objects.get(id=lead_id)
    send_mail(
    f'New Lead: {lead.title}',
    f'A new lead has been created: {lead.first_name} {lead.last_name}',
    'noreply@yourcompany.com',
    [lead.assigned_to.email],
    fail_silently=False,
    )
    @shared_task
    def bulk_import_leads(file_path, org_id):
    """Import leads from CSV file"""
    import csv
    from .models import Lead
    with open(file_path, 'r') as file:
    reader = csv.DictReader(file)
    for row in reader:
    Lead.objects.create(
    organization_id=org_id,
    first_name=row['first_name'],
    last_name=row['last_name'],
    email=row['email'],
    company=row['company'],
    status='New'
    )

    Set up Email Campaigns:

    backend/common/tasks.py
    @shared_task
    def send_bulk_email_campaign(campaign_id):
    """Send email campaign to contacts"""
    from contacts.models import Contact
    from campaigns.models import Campaign
    campaign = Campaign.objects.get(id=campaign_id)
    contacts = campaign.target_contacts.all()
    for contact in contacts:
    send_mail(
    campaign.subject,
    campaign.body,
    campaign.from_email,
    [contact.email],
    html_message=campaign.html_body,
    fail_silently=True,
    )
    # Track send
    campaign.track_send(contact)

    Configure Task Monitoring:

    Monitor Celery tasks in the Django admin:

    • View task history
    • Check task status (pending, success, failure)
    • Retry failed tasks
    • Schedule one-time tasks

    Set up Webhooks for Integrations:

    backend/common/webhooks.py
    @shared_task
    def send_webhook(webhook_url, event_type, payload):
    """Send webhook notification"""
    import requests
    try:
    response = requests.post(
    webhook_url,
    json={
    'event': event_type,
    'data': payload,
    'timestamp': datetime.now().isoformat()
    },
    headers={'Content-Type': 'application/json'},
    timeout=10
    )
    return response.status_code == 200
    except requests.exceptions.RequestException:
    return False

Step 8: Implement Security Best Practices

Harden your Django-CRM deployment for production.

    Enable Rate Limiting:

    backend/crm/settings.py
    REST_FRAMEWORK = {
    'DEFAULT_THROTTLE_CLASSES': [
    'rest_framework.throttling.AnonRateThrottle',
    'rest_framework.throttling.UserRateThrottle'
    ],
    'DEFAULT_THROTTLE_RATES': {
    'anon': '100/hour',
    'user': '1000/hour'
    }
    }

    Configure CORS Properly:

    # Only allow your frontend domain
    CORS_ALLOWED_ORIGINS = [
    'https://django-crm.klutch.sh',
    ]
    CORS_ALLOW_CREDENTIALS = True
    CORS_ALLOWED_METHODS = [
    'DELETE',
    'GET',
    'OPTIONS',
    'PATCH',
    'POST',
    'PUT',
    ]

    Set Security Headers:

    backend/crm/settings.py
    SECURE_BROWSER_XSS_FILTER = True
    SECURE_CONTENT_TYPE_NOSNIFF = True
    X_FRAME_OPTIONS = 'DENY'
    SECURE_HSTS_SECONDS = 31536000
    SECURE_HSTS_INCLUDE_SUBDOMAINS = True
    SECURE_HSTS_PRELOAD = True

    Implement IP Whitelisting (optional):

    backend/crm/middleware.py
    from django.core.exceptions import PermissionDenied
    class IPWhitelistMiddleware:
    def __init__(self, get_response):
    self.get_response = get_response
    self.whitelist = [
    '192.168.1.0/24',
    '10.0.0.0/8',
    ]
    def __call__(self, request):
    ip = self.get_client_ip(request)
    if not self.is_whitelisted(ip):
    raise PermissionDenied
    return self.get_response(request)

    Enable Audit Logging:

    # Track all important actions
    from django.db.models.signals import post_save, post_delete
    @receiver(post_save, sender=Lead)
    def log_lead_changes(sender, instance, created, **kwargs):
    action = 'created' if created else 'updated'
    AuditLog.objects.create(
    user=instance.modified_by,
    action=action,
    model='Lead',
    object_id=instance.id,
    changes=instance.get_changes()
    )

    Set up Two-Factor Authentication:

    backend/crm/settings.py
    # Install django-otp
    # pip install django-otp qrcode
    INSTALLED_APPS += [
    'django_otp',
    'django_otp.plugins.otp_totp',
    ]
    MIDDLEWARE += [
    'django_otp.middleware.OTPMiddleware',
    ]

    Regular Security Audits:

    Terminal window
    # Check for security issues
    python manage.py check --deploy
    # Update dependencies regularly
    pip list --outdated
    pip install -U django djangorestframework

Common Use Cases

Django-CRM excels in various business scenarios:

Sales Team Management

Pipeline View:
- Track leads through stages
- Forecast revenue from opportunities
- Monitor team performance
- Automate follow-up tasks
- Generate sales reports

Customer Support

Case Management:
- Track support tickets by priority
- Assign cases to team members
- Monitor SLA compliance
- Customer communication history
- Knowledge base integration

Small Business CRM

All-in-One Solution:
- Manage customer contacts
- Track sales opportunities
- Send invoices and track payments
- Schedule tasks and appointments
- Email marketing campaigns

Multi-Tenant SaaS

Enterprise Features:
- Complete data isolation with RLS
- Organization-specific customization
- Role-based access control
- Per-tenant usage tracking
- White-label capabilities

Troubleshooting

Database Connection Issues

Problem: Django can’t connect to PostgreSQL

Solution:

Terminal window
# Check database service is running
# In Klutch.sh dashboard, verify django-crm-db is active
# Test connection from Django container
psql -h django-crm-db -U crm_user -d crm_db
# Verify DATABASE_URL format
DATABASE_URL=postgresql://crm_user:password@django-crm-db:5432/crm_db

Celery Workers Not Processing Tasks

Problem: Background tasks are not executing

Solution:

Terminal window
# Check Redis connection
redis-cli -h django-crm-redis ping
# Verify Celery is running in supervisor
supervisorctl status celery_worker
# Check Celery logs
tail -f /app/logs/celery-worker.log
# Restart Celery if needed
supervisorctl restart celery_worker

RLS Policies Not Working

Problem: Users can see data from other organizations

Solution:

-- Verify RLS is enabled
SELECT tablename, rowsecurity
FROM pg_tables
WHERE schemaname = 'public';
-- Check policy exists
SELECT * FROM pg_policies
WHERE tablename = 'accounts_account';
-- Test policy
SET app.current_org_id = 'org-uuid';
SELECT * FROM accounts_account;

Static Files Not Loading

Problem: CSS/JS files return 404 errors

Solution:

Terminal window
# Collect static files
python backend/manage.py collectstatic --noinput
# Check nginx configuration
nginx -t
# Verify static directory permissions
ls -la /app/static/
# Restart nginx
supervisorctl restart nginx

Email Sending Fails

Problem: Emails are not being sent

Solution:

# Test email configuration
from django.core.mail import send_mail
send_mail(
'Test',
'Test message',
'noreply@yourcompany.com',
['test@example.com'],
)
# Check email backend settings
print(settings.EMAIL_BACKEND)
print(settings.EMAIL_HOST)
# View email logs
tail -f /app/logs/django.log | grep email

Frontend Build Fails

Problem: SvelteKit build errors during deployment

Solution:

Terminal window
# Check Node.js version
node --version # Should be 18+
# Clear pnpm cache
pnpm store prune
# Rebuild frontend
cd frontend
rm -rf node_modules .svelte-kit build
pnpm install
pnpm run build
# Check for TypeScript errors
pnpm run check

Memory Issues with Large Datasets

Problem: Application crashes with large imports

Solution:

# Use chunked processing for imports
from django.db import transaction
@shared_task
def import_large_dataset(file_path):
CHUNK_SIZE = 1000
records = []
for i, row in enumerate(read_csv(file_path)):
records.append(create_record(row))
if i % CHUNK_SIZE == 0:
with transaction.atomic():
Lead.objects.bulk_create(records)
records = []
# Import remaining records
if records:
Lead.objects.bulk_create(records)

JWT Token Expiration

Problem: Users getting logged out frequently

Solution:

# Increase JWT expiration time
JWT_EXPIRATION_DELTA = 86400 # 24 hours
# Implement token refresh
from rest_framework_simplejwt.views import TokenRefreshView
urlpatterns = [
path('api/token/refresh/', TokenRefreshView.as_view()),
]

Advanced Configuration

Custom API Endpoints

backend/custom_api/views.py
from rest_framework.decorators import api_view
from rest_framework.response import Response
@api_view(['GET'])
def sales_dashboard(request):
"""Custom sales dashboard endpoint"""
org_id = request.user.organization_id
return Response({
'total_leads': Lead.objects.filter(organization_id=org_id).count(),
'open_opportunities': Opportunity.objects.filter(
organization_id=org_id,
stage__in=['Proposal', 'Negotiation']
).count(),
'revenue_forecast': calculate_forecast(org_id),
'conversion_rate': calculate_conversion_rate(org_id),
})

Webhooks Integration

backend/integrations/webhooks.py
from django.dispatch import receiver
from django.db.models.signals import post_save
from leads.models import Lead
@receiver(post_save, sender=Lead)
def trigger_lead_webhook(sender, instance, created, **kwargs):
if created:
webhooks = Webhook.objects.filter(
organization=instance.organization,
event='lead.created',
active=True
)
for webhook in webhooks:
send_webhook.delay(
webhook.url,
'lead.created',
instance.to_dict()
)

Multi-Language Support

backend/crm/settings.py
from django.utils.translation import gettext_lazy as _
LANGUAGES = [
('en', _('English')),
('es', _('Spanish')),
('fr', _('French')),
('de', _('German')),
]
LOCALE_PATHS = [
os.path.join(BASE_DIR, 'locale'),
]
# Generate translations
# python manage.py makemessages -l es
# python manage.py compilemessages

Custom Reporting

backend/reports/views.py
from django.db.models import Count, Sum, Avg
from django.utils import timezone
from datetime import timedelta
def generate_monthly_report(org_id):
last_month = timezone.now() - timedelta(days=30)
report = {
'leads': {
'total': Lead.objects.filter(
organization_id=org_id,
created_at__gte=last_month
).count(),
'by_source': Lead.objects.filter(
organization_id=org_id,
created_at__gte=last_month
).values('source').annotate(count=Count('id')),
},
'opportunities': {
'total_value': Opportunity.objects.filter(
organization_id=org_id,
created_at__gte=last_month
).aggregate(Sum('amount'))['amount__sum'],
'win_rate': calculate_win_rate(org_id, last_month),
},
'activities': {
'total': Activity.objects.filter(
organization_id=org_id,
created_at__gte=last_month
).count(),
'by_type': Activity.objects.filter(
organization_id=org_id,
created_at__gte=last_month
).values('type').annotate(count=Count('id')),
}
}
return report

Production Best Practices

Database Optimization

# Use select_related and prefetch_related
leads = Lead.objects.select_related(
'assigned_to',
'created_by'
).prefetch_related(
'tags',
'attachments'
)
# Add database indexes
class Lead(models.Model):
email = models.EmailField(db_index=True)
status = models.CharField(max_length=50, db_index=True)
class Meta:
indexes = [
models.Index(fields=['organization', 'created_at']),
models.Index(fields=['organization', 'status']),
]

Caching Strategy

backend/crm/settings.py
CACHES = {
'default': {
'BACKEND': 'django_redis.cache.RedisCache',
'LOCATION': 'redis://django-crm-redis:6379/2',
'OPTIONS': {
'CLIENT_CLASS': 'django_redis.client.DefaultClient',
}
}
}
# Cache expensive queries
from django.core.cache import cache
def get_dashboard_stats(org_id):
cache_key = f'dashboard_stats_{org_id}'
stats = cache.get(cache_key)
if stats is None:
stats = calculate_dashboard_stats(org_id)
cache.set(cache_key, stats, 300) # 5 minutes
return stats

Monitoring and Logging

backend/crm/settings.py
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'formatters': {
'verbose': {
'format': '{levelname} {asctime} {module} {message}',
'style': '{',
},
},
'handlers': {
'file': {
'level': 'INFO',
'class': 'logging.handlers.RotatingFileHandler',
'filename': '/app/logs/django.log',
'maxBytes': 1024 * 1024 * 10, # 10 MB
'backupCount': 5,
'formatter': 'verbose',
},
'celery': {
'level': 'INFO',
'class': 'logging.handlers.RotatingFileHandler',
'filename': '/app/logs/celery.log',
'maxBytes': 1024 * 1024 * 10,
'backupCount': 5,
'formatter': 'verbose',
},
},
'loggers': {
'django': {
'handlers': ['file'],
'level': 'INFO',
'propagate': True,
},
'celery': {
'handlers': ['celery'],
'level': 'INFO',
'propagate': True,
},
},
}

Backup Strategy

#!/bin/bash
# backup-crm.sh - Daily backup script
BACKUP_DIR="/backups"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
# Backup database
pg_dump -h django-crm-db -U crm_user crm_db | gzip > "${BACKUP_DIR}/db-backup-${TIMESTAMP}.sql.gz"
# Backup media files
tar -czf "${BACKUP_DIR}/media-backup-${TIMESTAMP}.tar.gz" /app/media
# Backup uploaded files
tar -czf "${BACKUP_DIR}/uploads-backup-${TIMESTAMP}.tar.gz" /app/uploads
# Keep only last 30 days
find "${BACKUP_DIR}" -name "*.gz" -mtime +30 -delete
echo "Backup completed: ${TIMESTAMP}"

Scaling Considerations

Horizontal Scaling

For high-traffic deployments:

  1. Multiple Application Instances: Deploy multiple Django-CRM containers behind a load balancer
  2. Dedicated Celery Workers: Run separate containers for Celery workers with different concurrency settings
  3. Database Connection Pooling: Use PgBouncer for connection pooling
  4. CDN for Static Assets: Serve static files through a CDN
  5. Read Replicas: Set up PostgreSQL read replicas for reporting queries

Performance Optimization

# Use database connection pooling
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': 'crm_db',
'USER': 'crm_user',
'PASSWORD': 'password',
'HOST': 'django-crm-db',
'PORT': '5432',
'CONN_MAX_AGE': 600,
'OPTIONS': {
'connect_timeout': 10,
}
}
}
# Enable query optimization
MIDDLEWARE = [
'django.middleware.cache.UpdateCacheMiddleware',
# ... other middleware
'django.middleware.cache.FetchFromCacheMiddleware',
]

Conclusion

You’ve successfully deployed Django-CRM (BottleCRM) on Klutch.sh, creating a comprehensive customer relationship management system with enterprise-grade features. Your setup includes:

  • ✅ Full-stack Django REST API backend with SvelteKit frontend
  • ✅ Multi-tenant architecture with PostgreSQL Row-Level Security
  • ✅ Background task processing with Celery and Redis
  • ✅ Persistent storage for media, static files, and logs
  • ✅ Email integration for automated communications
  • ✅ JWT authentication and role-based access control
  • ✅ Complete CRM modules (leads, contacts, accounts, opportunities, cases, tasks, invoices)
  • ✅ API documentation with Swagger UI
  • ✅ Production-ready security configurations

Django-CRM provides everything startups and small businesses need to manage customer relationships effectively, from tracking leads through your sales pipeline to handling support cases and sending invoices. The multi-tenant architecture means you can serve multiple organizations from a single deployment, while PostgreSQL RLS ensures complete data isolation and security.

For more information about Django-CRM features and customization options, visit the official GitHub repository and documentation.

Additional Resources