Sails CRM is a lightweight, open-source CRM system built with Django and Vue.js. Originally developed as a SaaS platform, it has been transformed into a self-hosted, open-source project.
The easiest way to get Sails CRM running is using Docker Compose.
Clone the repository:
git clone https://github.com/dbtiunov/sails-crm.git
cd sails-crm
Start the application:
docker-compose up -d
This will start the web application, database (PostgreSQL), Redis cache, and Celery workers.
Create your account:
Open http://localhost:8000 and use the Create account form. The first
user becomes the account owner automatically; no management command is needed.
The Compose configuration is intended for local evaluation and development. It enables Django debug mode and uses public example credentials; do not expose it directly to the internet.
If you prefer to install manually, ensure you have Python 3.12+, PostgreSQL, and Redis installed.
Install dependencies:
pip install -r requirements.txt
Configure environment variables:
Create a .env file or set the following environment variables:
DB_NAME, DB_USER, DB_PASSWORD, DB_HOST, DB_PORTREDIS_URLREDIS_CACHE_URL (defaults to redis://127.0.0.1:6379/1)SECRET_KEYLANGUAGE_CODE (defaults to en; use ru for Russian)TIME_ZONE (defaults to UTC)APP_URL, SUPPORT_EMAIL, ADMIN_EMAILDEFAULT_FROM_EMAIL, WELCOME_FROM_EMAIL, NOTIFY_FROM_EMAIL,
REMIND_FROM_EMAIL, SERVER_EMAILEMAIL_HOST, EMAIL_PORT, EMAIL_USE_TLS, EMAIL_HOST_USER, EMAIL_HOST_PASSWORDCopy .env.example to .env and replace the example addresses and SMTP
credentials with values for your installation. Email addresses are not
hard-coded into the application.
Run migrations and collect static files:
python manage.py migrate
python manage.py collectstatic
Start the server:
gunicorn project.wsgi:application
The repository includes docker-compose.prod.yml and a Caddy reverse proxy
that obtains and renews TLS certificates automatically. PostgreSQL and Redis
are only reachable on the private Compose network. Application data, uploaded
media, Redis data, and Caddy certificates are stored in named volumes.
A/AAAA record pointing to the server.Clone the repository on the server:
git clone https://github.com/dbtiunov/sails-crm.git
cd sails-crm
cp .env.production.example .env.production
Edit .env.production before starting. At minimum, replace DOMAIN,
LETSENCRYPT_EMAIL, SECRET_KEY, DB_PASSWORD, ALLOWED_HOSTS,
CSRF_TRUSTED_ORIGINS, APP_URL, and the SMTP settings. Generate secrets, for
example, with openssl rand -base64 48. Keep .env.production readable only
by the deployment user:
chmod 600 .env.production
Validate and start the stack:
docker compose --env-file .env.production -f docker-compose.prod.yml config
docker compose --env-file .env.production -f docker-compose.prod.yml build
docker compose --env-file .env.production -f docker-compose.prod.yml up -d
docker compose --env-file .env.production -f docker-compose.prod.yml ps
The web container waits for PostgreSQL and Redis, applies migrations, collects
static files, creates compressor bundles, and starts Gunicorn. Caddy starts
after the web healthcheck passes. Open https://YOUR_DOMAIN and register the
first user, who becomes the account owner.
Inspect logs when troubleshooting:
docker compose --env-file .env.production -f docker-compose.prod.yml logs -f web celery caddy
Back up the database and uploaded media before updating. Then pull, rebuild, and recreate the services:
git pull --ff-only
docker compose --env-file .env.production -f docker-compose.prod.yml build
docker compose --env-file .env.production -f docker-compose.prod.yml up -d
docker compose --env-file .env.production -f docker-compose.prod.yml ps
Migrations and static asset preparation run automatically when the web service starts. Review migration plans before a major update with:
docker compose --env-file .env.production -f docker-compose.prod.yml run --rm web python manage.py migrate --plan
Create a PostgreSQL backup without publishing the database port:
docker compose --env-file .env.production -f docker-compose.prod.yml exec -T db \
sh -c 'pg_dump -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Fc' > sails-crm.dump
Back up the media_data volume as well; database backups do not contain
uploaded files. Test the restore process regularly on a separate stack.
To restore a custom-format database backup into an empty database:
docker compose --env-file .env.production -f docker-compose.prod.yml exec -T db \
sh -c 'pg_restore -U "$POSTGRES_USER" -d "$POSTGRES_DB" --clean --if-exists' \
< sails-crm.dump
Production Compose enforces DEBUG=False, secure session/CSRF cookies, an
explicit host allowlist, trusted HTTPS origins, and proxy-aware HTTPS handling.
After confirming HTTPS works for the final domain, set
SECURE_HSTS_SECONDS=31536000. Enable subdomains or HSTS preload only if every
affected hostname is permanently HTTPS-capable.
media/, encrypt the backups, and test restores.The CRM frontend uses vendored Vue 1.x and JavaScript assets from static/.
There is no separate frontend build step.
Run the backend system check without external services:
python manage.py check --settings=project.settings_test
Run the test suite against PostgreSQL with Docker:
docker compose --profile test run --rm test
For a local PostgreSQL instance, the test settings accept TEST_DB_NAME,
TEST_DB_USER, TEST_DB_PASSWORD, TEST_DB_HOST, and TEST_DB_PORT.
Django creates and destroys a separate test_-prefixed database.
Third-party frontend components and their licenses are documented in
THIRD_PARTY_LICENSES.md.
English is the source language and the default UI language. Russian
translations are stored only in locale/ru/LC_MESSAGES/django.po and
djangojs.po. Django templates use {% translate %} / {% blocktranslate %},
Python uses gettext or gettext_lazy, and legacy JavaScript uses the shared
i18n adapter. The same adapter builds locale objects for Select2, Moment,
FullCalendar, datetimepicker, and daterangepicker.
After adding or changing translatable strings, update and compile the catalogs:
make i18n-update
make i18n-check
make i18n-compile
The update command extracts JavaScript calls and gettext calls embedded in the
legacy Vue template HTML while preserving source references. The check rejects
fuzzy or missing translations, fixed ru-RU, first-party Cyrillic literals,
and standalone widget locale bundles. Vendored library bodies are excluded
because they are immutable checksum-pinned third-party sources; they are never
used as translation catalogs.
Set LANGUAGE_CODE=ru to start in Russian. The language selector on auth pages
and in the CRM shell posts to /i18n/setlang/ with CSRF protection and reloads
the current URL so Django, JavaScript, and widgets use one locale.
This project is licensed under the MIT License. See the LICENSE file for details.
3 commits
JavaScript
63.2%
Python
16.3%
HTML
13.2%
CSS
7.2%
Sails CRM is a lightweight, open-source CRM system built with Django and Vue.js. Originally developed as a SaaS platform, it has been transformed into a self-hosted, open-source project.
The easiest way to get Sails CRM running is using Docker Compose.
Clone the repository:
git clone https://github.com/dbtiunov/sails-crm.git
cd sails-crm
Start the application:
docker-compose up -d
This will start the web application, database (PostgreSQL), Redis cache, and Celery workers.
Create your account:
Open http://localhost:8000 and use the Create account form. The first
user becomes the account owner automatically; no management command is needed.
The Compose configuration is intended for local evaluation and development. It enables Django debug mode and uses public example credentials; do not expose it directly to the internet.
If you prefer to install manually, ensure you have Python 3.12+, PostgreSQL, and Redis installed.
Install dependencies:
pip install -r requirements.txt
Configure environment variables:
Create a .env file or set the following environment variables:
DB_NAME, DB_USER, DB_PASSWORD, DB_HOST, DB_PORTREDIS_URLREDIS_CACHE_URL (defaults to redis://127.0.0.1:6379/1)SECRET_KEYLANGUAGE_CODE (defaults to en; use ru for Russian)TIME_ZONE (defaults to UTC)APP_URL, SUPPORT_EMAIL, ADMIN_EMAILDEFAULT_FROM_EMAIL, WELCOME_FROM_EMAIL, NOTIFY_FROM_EMAIL,
REMIND_FROM_EMAIL, SERVER_EMAILEMAIL_HOST, EMAIL_PORT, EMAIL_USE_TLS, EMAIL_HOST_USER, EMAIL_HOST_PASSWORDCopy .env.example to .env and replace the example addresses and SMTP
credentials with values for your installation. Email addresses are not
hard-coded into the application.
Run migrations and collect static files:
python manage.py migrate
python manage.py collectstatic
Start the server:
gunicorn project.wsgi:application
The repository includes docker-compose.prod.yml and a Caddy reverse proxy
that obtains and renews TLS certificates automatically. PostgreSQL and Redis
are only reachable on the private Compose network. Application data, uploaded
media, Redis data, and Caddy certificates are stored in named volumes.
A/AAAA record pointing to the server.Clone the repository on the server:
git clone https://github.com/dbtiunov/sails-crm.git
cd sails-crm
cp .env.production.example .env.production
Edit .env.production before starting. At minimum, replace DOMAIN,
LETSENCRYPT_EMAIL, SECRET_KEY, DB_PASSWORD, ALLOWED_HOSTS,
CSRF_TRUSTED_ORIGINS, APP_URL, and the SMTP settings. Generate secrets, for
example, with openssl rand -base64 48. Keep .env.production readable only
by the deployment user:
chmod 600 .env.production
Validate and start the stack:
docker compose --env-file .env.production -f docker-compose.prod.yml config
docker compose --env-file .env.production -f docker-compose.prod.yml build
docker compose --env-file .env.production -f docker-compose.prod.yml up -d
docker compose --env-file .env.production -f docker-compose.prod.yml ps
The web container waits for PostgreSQL and Redis, applies migrations, collects
static files, creates compressor bundles, and starts Gunicorn. Caddy starts
after the web healthcheck passes. Open https://YOUR_DOMAIN and register the
first user, who becomes the account owner.
Inspect logs when troubleshooting:
docker compose --env-file .env.production -f docker-compose.prod.yml logs -f web celery caddy
Back up the database and uploaded media before updating. Then pull, rebuild, and recreate the services:
git pull --ff-only
docker compose --env-file .env.production -f docker-compose.prod.yml build
docker compose --env-file .env.production -f docker-compose.prod.yml up -d
docker compose --env-file .env.production -f docker-compose.prod.yml ps
Migrations and static asset preparation run automatically when the web service starts. Review migration plans before a major update with:
docker compose --env-file .env.production -f docker-compose.prod.yml run --rm web python manage.py migrate --plan
Create a PostgreSQL backup without publishing the database port:
docker compose --env-file .env.production -f docker-compose.prod.yml exec -T db \
sh -c 'pg_dump -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Fc' > sails-crm.dump
Back up the media_data volume as well; database backups do not contain
uploaded files. Test the restore process regularly on a separate stack.
To restore a custom-format database backup into an empty database:
docker compose --env-file .env.production -f docker-compose.prod.yml exec -T db \
sh -c 'pg_restore -U "$POSTGRES_USER" -d "$POSTGRES_DB" --clean --if-exists' \
< sails-crm.dump
Production Compose enforces DEBUG=False, secure session/CSRF cookies, an
explicit host allowlist, trusted HTTPS origins, and proxy-aware HTTPS handling.
After confirming HTTPS works for the final domain, set
SECURE_HSTS_SECONDS=31536000. Enable subdomains or HSTS preload only if every
affected hostname is permanently HTTPS-capable.
media/, encrypt the backups, and test restores.The CRM frontend uses vendored Vue 1.x and JavaScript assets from static/.
There is no separate frontend build step.
Run the backend system check without external services:
python manage.py check --settings=project.settings_test
Run the test suite against PostgreSQL with Docker:
docker compose --profile test run --rm test
For a local PostgreSQL instance, the test settings accept TEST_DB_NAME,
TEST_DB_USER, TEST_DB_PASSWORD, TEST_DB_HOST, and TEST_DB_PORT.
Django creates and destroys a separate test_-prefixed database.
Third-party frontend components and their licenses are documented in
THIRD_PARTY_LICENSES.md.
English is the source language and the default UI language. Russian
translations are stored only in locale/ru/LC_MESSAGES/django.po and
djangojs.po. Django templates use {% translate %} / {% blocktranslate %},
Python uses gettext or gettext_lazy, and legacy JavaScript uses the shared
i18n adapter. The same adapter builds locale objects for Select2, Moment,
FullCalendar, datetimepicker, and daterangepicker.
After adding or changing translatable strings, update and compile the catalogs:
make i18n-update
make i18n-check
make i18n-compile
The update command extracts JavaScript calls and gettext calls embedded in the
legacy Vue template HTML while preserving source references. The check rejects
fuzzy or missing translations, fixed ru-RU, first-party Cyrillic literals,
and standalone widget locale bundles. Vendored library bodies are excluded
because they are immutable checksum-pinned third-party sources; they are never
used as translation catalogs.
Set LANGUAGE_CODE=ru to start in Russian. The language selector on auth pages
and in the CRM shell posts to /i18n/setlang/ with CSRF protection and reloads
the current URL so Django, JavaScript, and widgets use one locale.
This project is licensed under the MIT License. See the LICENSE file for details.
3 commits
JavaScript
63.2%
Python
16.3%
HTML
13.2%
CSS
7.2%