[Moved to horilla/horilla-crm] Free and open source CRM software.
3.5K
Free and open source CRM software. Leads, opportunities, accounts, contacts, campaigns, forecasting, calling and meeting scheduling in one Django application.
The official image is now horilla/horilla-crm.
This mirror still receives every stable release, so existing docker pull horilla/crm deployments keep working — nothing breaks today. But new tags, documentation and support all target horilla/horilla-crm first.
To switch, change the image name in your compose file or deployment:
- image: horilla/crm:latest
+ image: horilla/horilla-crm:latest
Both names are built from the same commit and are byte-identical. The examples below use the new name.
| Tag | Meaning |
|---|---|
latest | Newest stable release. |
X.Y.Z | Exact release, immutable once published. Pin this in production. |
X.Y | Newest patch within a minor line. |
Architectures: linux/amd64, linux/arm64 (single multi-arch manifest — Docker picks the right one).
docker pull horilla/horilla-crm:latest
Horilla CRM needs PostgreSQL and Redis. Redis is not optional: the app uses Django Channels for real-time updates and Celery for background work, and reads REDIS_URL at startup.
# compose.yaml
services:
db:
image: postgres:16-alpine
environment:
POSTGRES_DB: horilla_db
POSTGRES_USER: horilla_user
POSTGRES_PASSWORD: change-me-db-password
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U horilla_user -d horilla_db"]
interval: 5s
retries: 10
redis:
image: redis:7-alpine
command: redis-server --requirepass change-me-redis-password
web:
image: horilla/horilla-crm:latest
depends_on:
db:
condition: service_healthy
ports:
- "8000:8000"
environment:
DEBUG: "0"
SECRET_KEY: "replace-with-50-plus-random-characters"
ALLOWED_HOSTS: "localhost,127.0.0.1"
CSRF_TRUSTED_ORIGINS: "http://localhost:8000"
DATABASE_URL: "postgres://horilla_user:change-me-db-password@db:5432/horilla_db"
REDIS_URL: "redis://:change-me-redis-password@redis:6379/0"
CELERY_BROKER_URL: "redis://:change-me-redis-password@redis:6379/0"
CELERY_RESULT_BACKEND: "redis://:change-me-redis-password@redis:6379/0"
volumes:
- media:/app/media
- staticfiles:/app/staticfiles
volumes:
postgres_data:
media:
staticfiles:
docker compose up -d
Then open http://localhost:8000.
The PostgreSQL service must be named
db. The container's startup script waits on the literal hostnamedbbefore running migrations, and that name is not configurable. If you rename the service, the container will wait and then exit — regardless of whatDATABASE_URLsays.
First boot takes a few minutes. The container applies the full migration set against the empty database before the web server binds. Watch with docker compose logs -f web; the app is ready when /health/ responds.
Generate a real SECRET_KEY:
docker run --rm --entrypoint python horilla/horilla-crm:latest \
-c "from django.core.management.utils import get_random_secret_key; print(get_random_secret_key())"
(--entrypoint python is needed because the default entrypoint waits for a database before running anything.)
These have no defaults. The container will not start without them.
| Variable | Notes |
|---|---|
SECRET_KEY | Django signing key. 50+ random characters. Not auto-generated — you must supply one, and keep it stable across restarts or every session and signed token is invalidated. |
DEBUG | 0 in production. 1 exposes tracebacks. |
ALLOWED_HOSTS | Comma-separated hostnames. Do not use * in production. |
CSRF_TRUSTED_ORIGINS | Comma-separated, must include the scheme (https://crm.example.com). |
| Variable | Notes |
|---|---|
DATABASE_URL | postgres://user:pass@db:5432/dbname. The host portion should be db to match the startup wait. |
| Variable | Default | Notes |
|---|---|---|
REDIS_URL | — | Channels layer and cache. |
CELERY_BROKER_URL | redis://127.0.0.1:6379/0 | Background task broker. Set it explicitly in containers. |
CELERY_RESULT_BACKEND | — | Task results. |
The web container does not run Celery workers itself. For background jobs — scheduled cadences, bulk email, imports — run additional containers from the same image:
celery-worker:
image: horilla/horilla-crm:latest
entrypoint: []
command: celery -A horilla worker --concurrency=2
environment: *same-as-web
celery-beat:
image: horilla/horilla-crm:latest
entrypoint: []
command: celery -A horilla beat
environment: *same-as-web
entrypoint: [] is required — the default entrypoint runs migrations and starts the web server, which is not what a worker should do.
db:5432 (30 attempts, 1s apart)migrate --noinputcollectstatic --noinputUvicorn rather than gunicorn: CRM serves WebSocket connections through Django Channels, which needs an ASGI server.
| Path | Contents |
|---|---|
/app/media | User uploads and attachments. Back this up. |
/app/staticfiles | Collected static assets. Rebuilt on every start; safe to discard. |
| Endpoint | Returns |
|---|---|
/health/ | {"status": "ok"} once the web server is serving |
The image ships a HEALTHCHECK that polls /health/ every 30s with a 60s start period. On a first boot the container may report starting for several minutes while migrations run — expected, not a failure.
docker compose exec web python manage.py createsuperuser
horilla/horilla-crm:1.15.0), not latest, so a deploy cannot pick up a new release unattended.SECRET_KEY from a secret store and keep it stable. Unlike some deployments there is no fallback generation here; a changed key logs everyone out.appuser, uid 1000). Mounted volumes must be writable by uid 1000.python:3.13-slim (Debian), multi-stage build/opt/venv; build toolchain is not present in the final imageappuser (uid 1000), never rootEach image carries OCI labels — org.opencontainers.image.version, .revision, .source — so any published image traces back to the exact commit it was built from:
docker inspect horilla/horilla-crm:latest \
--format '{{json .Config.Labels}}' | python3 -m json.tool
Built and published by GitHub Actions from horilla/horilla-crm on every release. Before any image is pushed, the pipeline:
__version__ matches the release version/health/An image that builds but does not run cannot be published.
Content type
Image
Digest
sha256:6d874bb14…
Size
203.8 MB
Last updated
4 days ago
docker pull horilla/crm