Production Horilla CRM runs PostgreSQL, Redis, Gunicorn/Uvicorn ASGI, Celery workers, and optional Nginx — typically orchestrated with Docker Compose.

This post is Part 28 of 28 in the Horilla CRM Technical Blog series.

What is production deployment for Horilla CRM?

production deployment for Horilla CRM enables you to:

  • PostgreSQL 14+ as sole prod database
  • Redis for Celery and Channels
  • ASGI for WebSocket notifications
  • Environment-driven settings and secrets
  • Horizontal scaling notes for workers and web

ASGI container entrypoint

The production container is built by the repository Dockerfile. It installs the application dependencies plus uvicorn[standard] and runs the ASGI entrypoint horilla.asgi:application:

CMD ["uvicorn", "horilla.asgi:application", "--host", "0.0.0.0", "--port", "8000"]

ASGI is required for the project’s HTTP and real-time execution model, including Channels/WebSocket handling. Do not replace this with a WSGI-only process when WebSockets are expected. The image runs as a non-root appuser, exposes port 8000, and delegates initialization to docker/entrypoint.sh. That entrypoint is the correct place to coordinate startup operations such as migrate and collectstatic; do not run those commands independently in every horizontally scaled web container without considering migration locking and deployment order.

The repository compose file defines web and PostgreSQL (db) by default, with an nginx service available through the production profile. Web mounts static and media volumes, and nginx serves those volumes as read-only paths. Nginx is a reverse proxy and static/media server; it should forward HTTP and WebSocket upgrade headers to the ASGI application. Terminate TLS at nginx or an upstream load balancer, then set Django’s proxy/security settings consistently with that topology.

services:
  web:
    build: .
    depends_on: [db]
  db:
    image: postgres:16-alpine
  nginx:
    image: nginx:alpine
    profiles: ["production"]

Redis, Celery, and the current compose gap

The current compose configuration does not define Redis or Celery services. That is an important deployment fact, not an optional documentation footnote: the default CELERY_BROKER_URL points to Redis, Channels uses Redis-backed channel layers, and background features require workers and Beat. A Compose deployment that starts only web and PostgreSQL cannot reliably provide WebSocket fan-out, scheduled tasks, or asynchronous automation unless it uses externally provisioned Redis and worker services.

For a self-contained Compose environment, add a Redis service, a Celery worker, and a Celery Beat service. Give all of them the same image/settings environment as web. Use depends_on for startup ordering, but do not mistake it for a readiness check; workers should retry broker connections and the web entrypoint should wait for the database when migrations are required.

  redis:
    image: redis:7-alpine
  celery-worker:
    build: .
    command: celery -A horilla.horilla_celery worker --loglevel=INFO
    depends_on: [db, redis]
  celery-beat:
    build: .
    command: celery -A horilla.horilla_celery beat --loglevel=INFO
    depends_on: [db, redis]

Set CELERY_BROKER_URL=redis://redis:6379/0 for these containers. Configure CHANNEL_LAYERS to use the same reachable Redis hostname; 127.0.0.1 inside the web container means the web container itself, not the Redis service. For a managed Redis deployment, omit the local Redis service and provide the managed endpoint, credentials, TLS options, and network access instead.

Required environment configuration

DATABASE_URL must point to the PostgreSQL service or managed database, with a production credential rather than the Compose development example. Set SITE_URL to the public HTTPS origin used in links, OAuth redirect registrations, and calendar webhooks. Configure CELERY_* variables consistently across web, workers, and Beat. Set appropriate ALLOWED_HOSTS, CSRF_TRUSTED_ORIGINS, and a unique secret key. ASGI deployment also requires proxy configuration that preserves WebSocket upgrade headers and has timeouts compatible with long-lived connections.

DATABASE_URL=postgres://horilla_user:strong-password@db:5432/horilla_db
SITE_URL=https://crm.example.com
CELERY_BROKER_URL=redis://redis:6379/0
CELERY_RESULT_BACKEND=redis://redis:6379/1

Never commit production values in .env files. Inject secrets through the deployment platform or a dedicated secret manager, rotate them, and restrict database and Redis network access to application services.

Static assets and release checks

The project uses WhiteNoise for static-file delivery at the Django layer, while the production nginx profile can serve the collected staticfiles volume directly. Run collectstatic as part of the release entrypoint or a dedicated release job so the versioned image and static manifest agree. Persist media separately from the container filesystem; user uploads disappear on replacement containers otherwise.

Before declaring a deployment healthy, run migrations once, verify static assets, exercise an authenticated HTTP request, establish a WebSocket connection, enqueue a Celery task, and confirm Beat emits a safe scheduled task. Check nginx access/error logs, web/ASGI logs, worker logs, database connectivity, and Redis connectivity. Docker Compose is a useful topology description, but production reliability depends on adding the missing Redis/Celery services or equivalent managed infrastructure and operating all of those processes as one coordinated application.

Benefits of Production Deployment in Horilla CRM

  • Reliable multi-tenant CRM at scale
  • Clear separation of web, worker, and beat processes
  • Documented env vars and health checks

You have traced Horilla CRM from AppLauncher to deploy. Star the repo, run the production checklist, and contribute docs or translations back to the community.

Continue the series

Previous: Part 27 — How to Use Audit History and Recycle Bin in Horilla CRM

More posts are on the Horilla Blogs; share feedback on GitHub.

Share this article